class Roby::TaskStructure::Dependency

Attributes

failing_tasks[R]
interesting_events[R]

Public Class Methods

merge_dependency_options(opt1, opt2) click to toggle source

Merges the dependency descriptions (i.e. the relation payload), verifying that the two provided option hashes are compatible

@return [Hash] the merged options @raise [ModelViolation] if the two hashes are not compatible

# File lib/roby/task_structure/dependency.rb, line 98
def self.merge_dependency_options(opt1, opt2)
    if opt1[:remove_when_done] != opt2[:remove_when_done]
        raise Roby::ModelViolation, "incompatible dependency specification: trying to change the value of +remove_when_done+"
    end

    result = { remove_when_done: opt1[:remove_when_done], consider_in_pending: opt1[:consider_in_pending] }

    if opt1[:success] || opt2[:success]
        result[:success] =
            if !opt1[:success] then opt2[:success]
            elsif !opt2[:success] then opt1[:success]
            else
                opt1[:success].and(opt2[:success])
            end
    end

    if opt1[:failure] || opt2[:failure]
        result[:failure] =
            if !opt1[:failure] then opt2[:failure]
            elsif !opt2[:failure] then opt1[:failure]
            else
                opt1[:failure].or(opt2[:failure])
            end
    end

    # Check model compatibility
    models1, arguments1 = opt1[:model]
    models2, arguments2 = opt2[:model]

    task_model1 = models1.find { |m| m <= Roby::Task }
    task_model2 = models2.find { |m| m <= Roby::Task }
    result_model = []
    if task_model1 && task_model2
        if task_model1.fullfills?(task_model2)
            result_model << task_model1
        elsif task_model2.fullfills?(task_model1)
            result_model << task_model2
        else
            raise Roby::ModelViolation, "incompatible models #{task_model1} and #{task_model2}"
        end
    elsif task_model1
        result_model << task_model1
    elsif task_model2
        result_model << task_model2
    end
    models1.each do |m|
        next if m <= Roby::Task
        if !models2.any? { |other_m| other_m.fullfills?(m) }
            result_model << m
        end
    end
    models2.each do |m|
        next if m <= Roby::Task
        if !models1.any? { |other_m| other_m.fullfills?(m) }
            result_model << m
        end
    end

    result[:model] = [result_model]
    # Merge arguments
    result[:model][1] = arguments1.merge(arguments2) do |key, old_value, new_value|
        if old_value != new_value
            raise Roby::ModelViolation, "incompatible argument constraint #{old_value} and #{new_value} for #{key}"
        end
        old_value
    end

    # Finally, merge the roles (the easy part ;-))
    result[:roles] = opt1[:roles] | opt2[:roles]

    result
end
merge_fullfilled_model(model, required_models, required_arguments) click to toggle source
# File lib/roby/task_structure/dependency.rb, line 56
def self.merge_fullfilled_model(model, required_models, required_arguments)
    model, tags, arguments = *model

    tags = tags.dup
    required_models = Array(required_models)

    for m in required_models
        if m.kind_of?(Roby::Models::TaskServiceModel)
            tags << m
        elsif m.has_ancestor?(model)
            model = m
        elsif !model.has_ancestor?(m)
            raise Roby::ModelViolation, "inconsistency in fullfilled models: #{model} and #{m} are incompatible"
        end
    end

    arguments = arguments.merge(required_arguments) do |name, old, new| 
        if old != new
            raise Roby::ModelViolation, "inconsistency in fullfilled models: #{old} and #{new}"
        end
        old
    end

    return [model, tags, arguments]
end
new(observer: nil) click to toggle source
Calls superclass method Roby::Relations::TaskRelationGraph::new
# File lib/roby/task_structure/dependency.rb, line 12
def initialize(observer: nil)
    super(observer: observer)
    @interesting_events = Array.new
    @failing_tasks = Set.new
end
validate_options(options, defaults = Hash.new) click to toggle source
# File lib/roby/task_structure/dependency.rb, line 82
def self.validate_options(options, defaults = Hash.new)
    defaults = Hash[model: [[Roby::Task], Hash.new],
        success: nil,
        failure: nil,
        remove_when_done: true,
        consider_in_pending: true,
        roles: Set.new,
        role: nil].merge(defaults)
    Kernel.validate_options options, defaults
end

Public Instance Methods

check_structure(plan) click to toggle source

Checks the structure of plan w.r.t. the constraints of the hierarchy relations. It returns an array of ChildFailedError for all failed hierarchy relations

# File lib/roby/task_structure/dependency.rb, line 186
def check_structure(plan)
    # The Set in #interesting_events is also referenced
    # *separately* in EventStructure.gather_events. We therefore have to
    # keep it (and can't use #partition). Yuk
    events = Array.new
    interesting_events.delete_if do |ev|
        if ev.plan == plan
            events << ev
            true
        else !ev.plan
        end
    end
    tasks = Set.new
    failing_tasks.delete_if do |task|
        if task.plan == plan
            tasks << task
            true
        else !task.plan
        end
    end
    return Array.new if events.empty? && tasks.empty?

    result = []

    # Get the set of tasks for which a possible failure has been
    # registered The tasks that are failing the hierarchy requirements
    # are registered in Hierarchy.failing_tasks.
    events.each do |event|
        task = event.task
        tasks << task

        if event.symbol == :start # also add the children
            task.each_child do |child_task, _|
                tasks << child_task
            end
        end
    end

    for child in tasks
        # Check if the task has been removed from the plan
        next unless child.plan

        removed_parents = []
        child.each_parent_task do |parent|
            next if parent.finished?
            next unless parent.self_owned?

            options = parent[child, Dependency]
            success = options[:success]
            failure = options[:failure]

            has_success = success && success.evaluate(child)
            if !has_success
                has_failure = failure && failure.evaluate(child)
            end

            error = nil
            if has_success
                if options[:remove_when_done]
                    # Must not delete it here as we are iterating over the
                    # parents
                    removed_parents << parent
                end
            elsif has_failure
                explanation = failure.explain_true(child)
                error = Roby::ChildFailedError.new(parent, child, explanation, :failed_event)
            elsif success && success.static?(child)
                explanation = success.explain_static(child)
                error = Roby::ChildFailedError.new(parent, child, explanation, :unreachable_success)
            end

            if error
                if parent.running?
                    result << error
                    failing_tasks << child
                elsif options[:consider_in_pending] && plan.control.pending_dependency_failed(parent, child, error)
                    result << error
                    failing_tasks << child
                end
            end
        end
        for parent in removed_parents
            parent.remove_child child
        end
    end

    result
end
merge_info(parent, child, opt1, opt2) click to toggle source

Called by the relation management when two dependency relations need to be merged

@see Dependency.merge_dependency_options

# File lib/roby/task_structure/dependency.rb, line 175
def merge_info(parent, child, opt1, opt2)
    result = Dependency.merge_dependency_options(opt1, opt2)
    update_triggers_for(parent, child, result)
    result
rescue Exception => e
    raise e, e.message + " while updating the dependency information for #{parent} -> #{child}", e.backtrace
end
update_triggers_for(parent, child, info) click to toggle source

@api private

Updates the dependency internal data to trigger errors / success when relevant events are emitted

# File lib/roby/task_structure/dependency.rb, line 22
def update_triggers_for(parent, child, info)
    events = Set.new
    if info[:success]
        for event_name in info[:success].required_events
            events << child.event(event_name)
        end
    end

    if info[:failure]
        for event_name in info[:failure].required_events
            events << child.event(event_name)
        end
    end

    if !events.empty?
        parent.start_event.on(on_replace: :drop) do |ev|
            ev.plan.task_relation_graph_for(self.class).interesting_events << ev.generator
        end
        events.each do |e|
            e.if_unreachable do |reason, ev|
                # The actualy graph of 'ev' might be different than self
                # ... re-resolve
                ev.plan.task_relation_graph_for(self.class).interesting_events << ev
            end
            e.on(on_replace: :drop) do |ev|
                ev.plan.task_relation_graph_for(self.class).interesting_events << ev.generator
            end
        end
    end

    # Initial triggers
    failing_tasks << child
end