Class: Roby::TaskStructure::Dependency

Inherits:
Relations::TaskRelationGraph show all
Defined in:
lib/roby/task_structure/dependency.rb

Defined Under Namespace

Modules: Extension, ModelExtension

Instance Attribute Summary collapse

Attributes inherited from Relations::Graph

#observer, #parent, #subsets

Attributes inherited from Relations::BidirectionalDirectedAdjacencyGraph

#backward_edges, #forward_edges_with_info

Class Method Summary collapse

Instance Method Summary collapse

Methods included from Relations::Models::TaskRelationGraph

#setup_submodel

Methods inherited from Relations::Graph

#add_edge, #add_relation, #copy_subgraph_to, #copy_to, #each_child_vertex, #each_parent_vertex, #find_edge_difference, #has_edge_in_hierarchy?, #include?, #inspect, #leaf_relation?, #link, #linked?, #linked_in_hierarchy?, #merge!, #reachable?, #recursive_subsets, #remove, #remove_relation, #remove_vertex, #remove_vertex!, #replace_vertex, #root_graph, #root_relation?, #set_edge_info, #size, #subset?, #superset_of, #to_s, #try_updating_existing_edge_info, #unlink

Methods included from DRoby::Identifiable

#droby_id, #initialize_copy

Methods included from DRoby::V5::DRobyConstant::Dump

#droby_dump, #droby_marshallable?

Methods inherited from Relations::BidirectionalDirectedAdjacencyGraph

#==, [], #add_edge, #add_vertex, #clear, #dedupe, #difference, #directed?, #each_edge, #each_in_neighbour, #each_out_neighbour, #each_vertex, #edge_info, #eql?, #freeze, #has_edge?, #has_vertex?, #hash, #in_degree, #in_neighbours, #initialize_copy, #leaf?, #merge, #move_edges, #num_edges, #num_vertices, #out_degree, #out_neighbours, #propagate_transitive_closure, #remove_edge, #remove_vertex, #replace, #reverse, #reverse!, #root?, #same_structure?, #set_edge_info, #to_a, #verify_consistency, #vertices

Methods included from DRoby::V5::BidirectionalGraphDumper

#droby_dump

Constructor Details

#initialize(observer: nil) ⇒ Dependency

Returns a new instance of Dependency.



13
14
15
16
17
# File 'lib/roby/task_structure/dependency.rb', line 13

def initialize(observer: nil)
    super(observer: observer)
    @interesting_events = []
    @failing_tasks = Set.new
end

Instance Attribute Details

#failing_tasksObject (readonly)

Returns the value of attribute failing_tasks.



11
12
13
# File 'lib/roby/task_structure/dependency.rb', line 11

def failing_tasks
  @failing_tasks
end

#interesting_eventsObject (readonly)

Returns the value of attribute interesting_events.



11
12
13
# File 'lib/roby/task_structure/dependency.rb', line 11

def interesting_events
  @interesting_events
end

Class Method Details

.merge_dependency_options(opt1, opt2) ⇒ Hash

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

Returns:

  • (Hash)

    the merged options

Raises:



101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
# File 'lib/roby/task_structure/dependency.rb', line 101

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.none? { |other_m| other_m.fullfills?(m) }
            result_model << m
        end
    end
    models2.each do |m|
        next if m <= Roby::Task

        if models1.none? { |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
    result[:model].freeze

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

    result
end

.merge_fullfilled_model(model, required_models, required_arguments) ⇒ Object



57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
# File 'lib/roby/task_structure/dependency.rb', line 57

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
    tags.uniq!

    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

    [model, tags, arguments]
end

.validate_options(options, defaults = {}) ⇒ Object



85
86
87
88
89
90
91
92
93
94
# File 'lib/roby/task_structure/dependency.rb', line 85

def self.validate_options(options, defaults = {})
    defaults = Hash[model: [[Roby::Task], {}],
                    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

Instance Method Details

#check_structure(plan) ⇒ Object

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



194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
# File 'lib/roby/task_structure/dependency.rb', line 194

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 = []
    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 [] 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&.evaluate(child)
            unless has_success
                has_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&.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) ⇒ Object

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



183
184
185
186
187
188
189
# File 'lib/roby/task_structure/dependency.rb', line 183

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) ⇒ Object

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.

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



23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
# File 'lib/roby/task_structure/dependency.rb', line 23

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

    unless 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