roby 0.7
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/.gitignore +29 -0
- data/History.txt +4 -0
- data/License-fr.txt +519 -0
- data/License.txt +515 -0
- data/Manifest.txt +245 -0
- data/NOTES +4 -0
- data/README.txt +163 -0
- data/Rakefile +161 -0
- data/TODO.txt +146 -0
- data/app/README.txt +24 -0
- data/app/Rakefile +8 -0
- data/app/config/ROBOT.rb +5 -0
- data/app/config/app.yml +91 -0
- data/app/config/init.rb +7 -0
- data/app/config/roby.yml +3 -0
- data/app/controllers/.gitattributes +0 -0
- data/app/controllers/ROBOT.rb +2 -0
- data/app/data/.gitattributes +0 -0
- data/app/planners/ROBOT/main.rb +6 -0
- data/app/planners/main.rb +5 -0
- data/app/scripts/distributed +3 -0
- data/app/scripts/generate/bookmarks +3 -0
- data/app/scripts/replay +3 -0
- data/app/scripts/results +3 -0
- data/app/scripts/run +3 -0
- data/app/scripts/server +3 -0
- data/app/scripts/shell +3 -0
- data/app/scripts/test +3 -0
- data/app/tasks/.gitattributes +0 -0
- data/app/tasks/ROBOT/.gitattributes +0 -0
- data/bin/roby +210 -0
- data/bin/roby-log +168 -0
- data/bin/roby-shell +25 -0
- data/doc/images/event_generalization.png +0 -0
- data/doc/images/exception_propagation_1.png +0 -0
- data/doc/images/exception_propagation_2.png +0 -0
- data/doc/images/exception_propagation_3.png +0 -0
- data/doc/images/exception_propagation_4.png +0 -0
- data/doc/images/exception_propagation_5.png +0 -0
- data/doc/images/replay_handler_error.png +0 -0
- data/doc/images/replay_handler_error_0.png +0 -0
- data/doc/images/replay_handler_error_1.png +0 -0
- data/doc/images/roby_cycle_overview.png +0 -0
- data/doc/images/roby_replay_02.png +0 -0
- data/doc/images/roby_replay_03.png +0 -0
- data/doc/images/roby_replay_04.png +0 -0
- data/doc/images/roby_replay_event_representation.png +0 -0
- data/doc/images/roby_replay_first_state.png +0 -0
- data/doc/images/roby_replay_relations.png +0 -0
- data/doc/images/roby_replay_startup.png +0 -0
- data/doc/images/task_event_generalization.png +0 -0
- data/doc/papers.rdoc +11 -0
- data/doc/styles/allison.css +314 -0
- data/doc/styles/allison.js +316 -0
- data/doc/styles/allison.rb +276 -0
- data/doc/styles/jamis.rb +593 -0
- data/doc/tutorials/01-GettingStarted.rdoc +86 -0
- data/doc/tutorials/02-GoForward.rdoc +220 -0
- data/doc/tutorials/03-PlannedPath.rdoc +268 -0
- data/doc/tutorials/04-EventPropagation.rdoc +236 -0
- data/doc/tutorials/05-ErrorHandling.rdoc +319 -0
- data/doc/tutorials/06-Overview.rdoc +40 -0
- data/doc/videos.rdoc +69 -0
- data/ext/droby/dump.cc +175 -0
- data/ext/droby/extconf.rb +3 -0
- data/ext/graph/algorithm.cc +746 -0
- data/ext/graph/extconf.rb +7 -0
- data/ext/graph/graph.cc +529 -0
- data/ext/graph/graph.hh +183 -0
- data/ext/graph/iterator_sequence.hh +102 -0
- data/ext/graph/undirected_dfs.hh +226 -0
- data/ext/graph/undirected_graph.hh +421 -0
- data/lib/roby.rb +41 -0
- data/lib/roby/app.rb +870 -0
- data/lib/roby/app/rake.rb +56 -0
- data/lib/roby/app/run.rb +14 -0
- data/lib/roby/app/scripts/distributed.rb +13 -0
- data/lib/roby/app/scripts/generate/bookmarks.rb +162 -0
- data/lib/roby/app/scripts/replay.rb +31 -0
- data/lib/roby/app/scripts/results.rb +15 -0
- data/lib/roby/app/scripts/run.rb +26 -0
- data/lib/roby/app/scripts/server.rb +18 -0
- data/lib/roby/app/scripts/shell.rb +88 -0
- data/lib/roby/app/scripts/test.rb +40 -0
- data/lib/roby/basic_object.rb +151 -0
- data/lib/roby/config.rb +5 -0
- data/lib/roby/control.rb +747 -0
- data/lib/roby/decision_control.rb +17 -0
- data/lib/roby/distributed.rb +32 -0
- data/lib/roby/distributed/base.rb +440 -0
- data/lib/roby/distributed/communication.rb +871 -0
- data/lib/roby/distributed/connection_space.rb +592 -0
- data/lib/roby/distributed/distributed_object.rb +206 -0
- data/lib/roby/distributed/drb.rb +62 -0
- data/lib/roby/distributed/notifications.rb +539 -0
- data/lib/roby/distributed/peer.rb +550 -0
- data/lib/roby/distributed/protocol.rb +529 -0
- data/lib/roby/distributed/proxy.rb +343 -0
- data/lib/roby/distributed/subscription.rb +311 -0
- data/lib/roby/distributed/transaction.rb +498 -0
- data/lib/roby/event.rb +897 -0
- data/lib/roby/exceptions.rb +234 -0
- data/lib/roby/executives/simple.rb +30 -0
- data/lib/roby/graph.rb +166 -0
- data/lib/roby/interface.rb +390 -0
- data/lib/roby/log.rb +3 -0
- data/lib/roby/log/chronicle.rb +303 -0
- data/lib/roby/log/console.rb +72 -0
- data/lib/roby/log/data_stream.rb +197 -0
- data/lib/roby/log/dot.rb +279 -0
- data/lib/roby/log/event_stream.rb +151 -0
- data/lib/roby/log/file.rb +340 -0
- data/lib/roby/log/gui/basic_display.ui +83 -0
- data/lib/roby/log/gui/chronicle.rb +26 -0
- data/lib/roby/log/gui/chronicle_view.rb +40 -0
- data/lib/roby/log/gui/chronicle_view.ui +70 -0
- data/lib/roby/log/gui/data_displays.rb +172 -0
- data/lib/roby/log/gui/data_displays.ui +155 -0
- data/lib/roby/log/gui/notifications.rb +26 -0
- data/lib/roby/log/gui/relations.rb +248 -0
- data/lib/roby/log/gui/relations.ui +123 -0
- data/lib/roby/log/gui/relations_view.rb +185 -0
- data/lib/roby/log/gui/relations_view.ui +149 -0
- data/lib/roby/log/gui/replay.rb +327 -0
- data/lib/roby/log/gui/replay_controls.rb +200 -0
- data/lib/roby/log/gui/replay_controls.ui +259 -0
- data/lib/roby/log/gui/runtime.rb +130 -0
- data/lib/roby/log/hooks.rb +185 -0
- data/lib/roby/log/logger.rb +202 -0
- data/lib/roby/log/notifications.rb +244 -0
- data/lib/roby/log/plan_rebuilder.rb +470 -0
- data/lib/roby/log/relations.rb +1056 -0
- data/lib/roby/log/server.rb +550 -0
- data/lib/roby/log/sqlite.rb +47 -0
- data/lib/roby/log/timings.rb +164 -0
- data/lib/roby/plan-object.rb +247 -0
- data/lib/roby/plan.rb +762 -0
- data/lib/roby/planning.rb +13 -0
- data/lib/roby/planning/loops.rb +302 -0
- data/lib/roby/planning/model.rb +906 -0
- data/lib/roby/planning/task.rb +151 -0
- data/lib/roby/propagation.rb +562 -0
- data/lib/roby/query.rb +619 -0
- data/lib/roby/relations.rb +583 -0
- data/lib/roby/relations/conflicts.rb +70 -0
- data/lib/roby/relations/ensured.rb +20 -0
- data/lib/roby/relations/error_handling.rb +23 -0
- data/lib/roby/relations/events.rb +9 -0
- data/lib/roby/relations/executed_by.rb +193 -0
- data/lib/roby/relations/hierarchy.rb +239 -0
- data/lib/roby/relations/influence.rb +10 -0
- data/lib/roby/relations/planned_by.rb +63 -0
- data/lib/roby/robot.rb +7 -0
- data/lib/roby/standard_errors.rb +218 -0
- data/lib/roby/state.rb +5 -0
- data/lib/roby/state/events.rb +221 -0
- data/lib/roby/state/information.rb +55 -0
- data/lib/roby/state/pos.rb +110 -0
- data/lib/roby/state/shapes.rb +32 -0
- data/lib/roby/state/state.rb +353 -0
- data/lib/roby/support.rb +92 -0
- data/lib/roby/task-operations.rb +182 -0
- data/lib/roby/task.rb +1618 -0
- data/lib/roby/test/common.rb +399 -0
- data/lib/roby/test/distributed.rb +214 -0
- data/lib/roby/test/tasks/empty_task.rb +9 -0
- data/lib/roby/test/tasks/goto.rb +36 -0
- data/lib/roby/test/tasks/simple_task.rb +23 -0
- data/lib/roby/test/testcase.rb +519 -0
- data/lib/roby/test/tools.rb +160 -0
- data/lib/roby/thread_task.rb +87 -0
- data/lib/roby/transactions.rb +462 -0
- data/lib/roby/transactions/proxy.rb +292 -0
- data/lib/roby/transactions/updates.rb +139 -0
- data/plugins/fault_injection/History.txt +4 -0
- data/plugins/fault_injection/README.txt +37 -0
- data/plugins/fault_injection/Rakefile +18 -0
- data/plugins/fault_injection/TODO.txt +0 -0
- data/plugins/fault_injection/app.rb +52 -0
- data/plugins/fault_injection/fault_injection.rb +89 -0
- data/plugins/fault_injection/test/test_fault_injection.rb +84 -0
- data/plugins/subsystems/README.txt +40 -0
- data/plugins/subsystems/Rakefile +18 -0
- data/plugins/subsystems/app.rb +171 -0
- data/plugins/subsystems/test/app/README +24 -0
- data/plugins/subsystems/test/app/Rakefile +8 -0
- data/plugins/subsystems/test/app/config/app.yml +71 -0
- data/plugins/subsystems/test/app/config/init.rb +9 -0
- data/plugins/subsystems/test/app/config/roby.yml +3 -0
- data/plugins/subsystems/test/app/planners/main.rb +20 -0
- data/plugins/subsystems/test/app/scripts/distributed +3 -0
- data/plugins/subsystems/test/app/scripts/replay +3 -0
- data/plugins/subsystems/test/app/scripts/results +3 -0
- data/plugins/subsystems/test/app/scripts/run +3 -0
- data/plugins/subsystems/test/app/scripts/server +3 -0
- data/plugins/subsystems/test/app/scripts/shell +3 -0
- data/plugins/subsystems/test/app/scripts/test +3 -0
- data/plugins/subsystems/test/app/tasks/services.rb +15 -0
- data/plugins/subsystems/test/test_subsystems.rb +71 -0
- data/test/distributed/test_communication.rb +178 -0
- data/test/distributed/test_connection.rb +282 -0
- data/test/distributed/test_execution.rb +373 -0
- data/test/distributed/test_mixed_plan.rb +341 -0
- data/test/distributed/test_plan_notifications.rb +238 -0
- data/test/distributed/test_protocol.rb +516 -0
- data/test/distributed/test_query.rb +102 -0
- data/test/distributed/test_remote_plan.rb +491 -0
- data/test/distributed/test_transaction.rb +463 -0
- data/test/mockups/tasks.rb +27 -0
- data/test/planning/test_loops.rb +380 -0
- data/test/planning/test_model.rb +427 -0
- data/test/planning/test_task.rb +106 -0
- data/test/relations/test_conflicts.rb +42 -0
- data/test/relations/test_ensured.rb +38 -0
- data/test/relations/test_executed_by.rb +149 -0
- data/test/relations/test_hierarchy.rb +158 -0
- data/test/relations/test_planned_by.rb +54 -0
- data/test/suite_core.rb +24 -0
- data/test/suite_distributed.rb +9 -0
- data/test/suite_planning.rb +3 -0
- data/test/suite_relations.rb +8 -0
- data/test/test_bgl.rb +508 -0
- data/test/test_control.rb +399 -0
- data/test/test_event.rb +894 -0
- data/test/test_exceptions.rb +592 -0
- data/test/test_interface.rb +37 -0
- data/test/test_log.rb +114 -0
- data/test/test_log_server.rb +132 -0
- data/test/test_plan.rb +584 -0
- data/test/test_propagation.rb +210 -0
- data/test/test_query.rb +266 -0
- data/test/test_relations.rb +180 -0
- data/test/test_state.rb +414 -0
- data/test/test_support.rb +16 -0
- data/test/test_task.rb +938 -0
- data/test/test_testcase.rb +122 -0
- data/test/test_thread_task.rb +73 -0
- data/test/test_transactions.rb +569 -0
- data/test/test_transactions_proxy.rb +198 -0
- metadata +570 -0
data/lib/roby/query.rb
ADDED
|
@@ -0,0 +1,619 @@
|
|
|
1
|
+
require 'roby/plan'
|
|
2
|
+
require 'roby/transactions'
|
|
3
|
+
require 'roby/state/information'
|
|
4
|
+
|
|
5
|
+
module Roby
|
|
6
|
+
class Task
|
|
7
|
+
# Returns a TaskMatcher object
|
|
8
|
+
def self.match
|
|
9
|
+
TaskMatcher.new
|
|
10
|
+
end
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
# This class represents a predicate which can be used to filter tasks. To
|
|
14
|
+
# filter plan-related properties, use Query.
|
|
15
|
+
#
|
|
16
|
+
# A TaskMatcher object is a AND combination of various tests against tasks.
|
|
17
|
+
class TaskMatcher
|
|
18
|
+
attr_reader :model, :arguments
|
|
19
|
+
attr_reader :predicates, :neg_predicates, :owners
|
|
20
|
+
|
|
21
|
+
attr_reader :improved_information
|
|
22
|
+
attr_reader :needed_information
|
|
23
|
+
|
|
24
|
+
# Initializes an empty TaskMatcher object
|
|
25
|
+
def initialize
|
|
26
|
+
@predicates = ValueSet.new
|
|
27
|
+
@neg_predicates = ValueSet.new
|
|
28
|
+
@owners = Array.new
|
|
29
|
+
@improved_information = ValueSet.new
|
|
30
|
+
@needed_information = ValueSet.new
|
|
31
|
+
@interruptible = nil
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
# Shortcut to set both model and argument
|
|
35
|
+
def which_fullfills(model, arguments = nil)
|
|
36
|
+
with_model(model).with_model_arguments(arguments || {})
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
# Find by model
|
|
40
|
+
def with_model(model)
|
|
41
|
+
@model = model
|
|
42
|
+
self
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
# Find by arguments defined by the model
|
|
46
|
+
def with_model_arguments(arguments)
|
|
47
|
+
if !model
|
|
48
|
+
raise ArgumentError, "set model first"
|
|
49
|
+
end
|
|
50
|
+
with_arguments(arguments.slice(*model.arguments))
|
|
51
|
+
self
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
# Find by argument (exact matching)
|
|
55
|
+
def with_arguments(arguments)
|
|
56
|
+
@arguments ||= Hash.new
|
|
57
|
+
self.arguments.merge!(arguments) do |k, old, new|
|
|
58
|
+
if old != new
|
|
59
|
+
raise ArgumentError, "a constraint has already been set on the #{k} argument"
|
|
60
|
+
end
|
|
61
|
+
old
|
|
62
|
+
end
|
|
63
|
+
self
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
# Find tasks which improves information contained in +info+
|
|
67
|
+
def which_improves(*info)
|
|
68
|
+
improved_information.merge(info.to_value_set)
|
|
69
|
+
self
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
# Find tasks which need information contained in +info+
|
|
73
|
+
def which_needs(*info)
|
|
74
|
+
needed_information.merge(info.to_value_set)
|
|
75
|
+
self
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
# Finds by owners. The set of owner is added to any owner already
|
|
79
|
+
# added. Do
|
|
80
|
+
#
|
|
81
|
+
# matcher.owners.clear
|
|
82
|
+
#
|
|
83
|
+
# to remove all owners
|
|
84
|
+
def owned_by(*ids)
|
|
85
|
+
@owners |= ids
|
|
86
|
+
self
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
# Finds tasks which we own ourselves.
|
|
90
|
+
def self_owned
|
|
91
|
+
owned_by(Roby::Distributed)
|
|
92
|
+
self
|
|
93
|
+
end
|
|
94
|
+
|
|
95
|
+
class << self
|
|
96
|
+
def declare_class_methods(*names) # :nodoc:
|
|
97
|
+
names.each do |name|
|
|
98
|
+
raise "no instance method #{name} on TaskMatcher" unless TaskMatcher.method_defined?(name)
|
|
99
|
+
TaskMatcher.singleton_class.send(:define_method, name) do |*args|
|
|
100
|
+
TaskMatcher.new.send(name, *args)
|
|
101
|
+
end
|
|
102
|
+
end
|
|
103
|
+
end
|
|
104
|
+
|
|
105
|
+
# For each name in +names+, define a #name and a #not_name method.
|
|
106
|
+
# If the first is called, the matcher will match only tasks whose
|
|
107
|
+
# #name? method returns true. If the second is called, the
|
|
108
|
+
# opposite will be done.
|
|
109
|
+
def match_predicates(*names)
|
|
110
|
+
names.each do |name|
|
|
111
|
+
class_eval <<-EOD
|
|
112
|
+
def #{name}
|
|
113
|
+
if neg_predicates.include?(:#{name}?)
|
|
114
|
+
raise ArgumentError, "trying to match (#{name}? & !#{name}?)"
|
|
115
|
+
end
|
|
116
|
+
predicates << :#{name}?
|
|
117
|
+
self
|
|
118
|
+
end
|
|
119
|
+
def not_#{name}
|
|
120
|
+
if predicates.include?(:#{name}?)
|
|
121
|
+
raise ArgumentError, "trying to match (#{name}? & !#{name}?)"
|
|
122
|
+
end
|
|
123
|
+
neg_predicates << :#{name}?
|
|
124
|
+
self
|
|
125
|
+
end
|
|
126
|
+
EOD
|
|
127
|
+
end
|
|
128
|
+
declare_class_methods(*names)
|
|
129
|
+
declare_class_methods(*names.map { |n| "not_#{n}" })
|
|
130
|
+
end
|
|
131
|
+
end
|
|
132
|
+
match_predicates :executable, :abstract, :partially_instanciated, :fully_instanciated,
|
|
133
|
+
:pending, :running, :finished, :success, :failed, :interruptible, :finishing
|
|
134
|
+
|
|
135
|
+
# True if +task+ matches all the criteria defined on this object.
|
|
136
|
+
def ===(task)
|
|
137
|
+
return unless task.kind_of?(Roby::Task)
|
|
138
|
+
if model
|
|
139
|
+
return unless task.fullfills?(model)
|
|
140
|
+
end
|
|
141
|
+
if arguments
|
|
142
|
+
return unless task.arguments.slice(*arguments.keys) == arguments
|
|
143
|
+
end
|
|
144
|
+
|
|
145
|
+
for info in improved_information
|
|
146
|
+
return false if !task.improves?(info)
|
|
147
|
+
end
|
|
148
|
+
for info in needed_information
|
|
149
|
+
return false if !task.needs?(info)
|
|
150
|
+
end
|
|
151
|
+
for pred in predicates
|
|
152
|
+
return false if !task.send(pred)
|
|
153
|
+
end
|
|
154
|
+
for pred in neg_predicates
|
|
155
|
+
return false if task.send(pred)
|
|
156
|
+
end
|
|
157
|
+
|
|
158
|
+
return false if !owners.empty? && !(task.owners - owners).empty?
|
|
159
|
+
true
|
|
160
|
+
end
|
|
161
|
+
|
|
162
|
+
STATE_PREDICATES = [:pending?, :running?, :finished?, :success?, :failed?].to_value_set
|
|
163
|
+
|
|
164
|
+
# Filters the tasks in +initial_set+ by using the information in
|
|
165
|
+
# +task_index+, and returns the result. The resulting set must
|
|
166
|
+
# include all tasks in +initial_set+ which match with #===, but can
|
|
167
|
+
# include tasks which do not match #===
|
|
168
|
+
def filter(initial_set, task_index)
|
|
169
|
+
if model
|
|
170
|
+
initial_set &= task_index.by_model[model]
|
|
171
|
+
end
|
|
172
|
+
|
|
173
|
+
if !owners.empty?
|
|
174
|
+
for o in owners
|
|
175
|
+
if candidates = task_index.by_owner[o]
|
|
176
|
+
initial_set &= candidates
|
|
177
|
+
else
|
|
178
|
+
return ValueSet.new
|
|
179
|
+
end
|
|
180
|
+
end
|
|
181
|
+
end
|
|
182
|
+
|
|
183
|
+
for pred in (predicates & STATE_PREDICATES)
|
|
184
|
+
initial_set &= task_index.by_state[pred]
|
|
185
|
+
end
|
|
186
|
+
|
|
187
|
+
for pred in (neg_predicates & STATE_PREDICATES)
|
|
188
|
+
initial_set -= task_index.by_state[pred]
|
|
189
|
+
end
|
|
190
|
+
|
|
191
|
+
initial_set
|
|
192
|
+
end
|
|
193
|
+
|
|
194
|
+
# Enumerates all tasks of +plan+ which match this TaskMatcher object
|
|
195
|
+
def each(plan, &block)
|
|
196
|
+
plan.query_each(plan.query_result_set(self), &block)
|
|
197
|
+
self
|
|
198
|
+
end
|
|
199
|
+
|
|
200
|
+
# Define singleton classes. For instance, calling TaskMatcher.which_fullfills is equivalent
|
|
201
|
+
# to TaskMatcher.new.which_fullfills
|
|
202
|
+
declare_class_methods :which_fullfills,
|
|
203
|
+
:with_model, :with_arguments,
|
|
204
|
+
:which_needs, :which_improves,
|
|
205
|
+
:owned_by, :self_owned
|
|
206
|
+
|
|
207
|
+
# Returns the negation of this predicate
|
|
208
|
+
def negate; NegateTaskMatcher.new(self) end
|
|
209
|
+
# Combines this predicate with another using a AND logical operation
|
|
210
|
+
def &(other); AndTaskMatcher.new(self, other) end
|
|
211
|
+
# Combines this predicate with another using an OR logical operation
|
|
212
|
+
def |(other); OrTaskMatcher.new(self, other) end
|
|
213
|
+
end
|
|
214
|
+
|
|
215
|
+
# A query is a predicate on both the task internal properties, and their
|
|
216
|
+
# plan-related properties as well.
|
|
217
|
+
class Query < TaskMatcher
|
|
218
|
+
# The plan this query acts on
|
|
219
|
+
attr_reader :plan
|
|
220
|
+
|
|
221
|
+
# Create a query object on the given plan
|
|
222
|
+
def initialize(plan)
|
|
223
|
+
@plan = plan
|
|
224
|
+
super()
|
|
225
|
+
@plan_predicates = Array.new
|
|
226
|
+
@neg_plan_predicates = Array.new
|
|
227
|
+
end
|
|
228
|
+
|
|
229
|
+
# The set of tasks which match in plan. This is a cached value, so use
|
|
230
|
+
# #reset to actually recompute this set.
|
|
231
|
+
def result_set
|
|
232
|
+
@result_set ||= plan.query_result_set(self)
|
|
233
|
+
end
|
|
234
|
+
|
|
235
|
+
# #result_set is a cached value. Call this method to reinitialize,
|
|
236
|
+
# making sure the result set is recomputed next time #result_set is
|
|
237
|
+
# called.
|
|
238
|
+
def reset
|
|
239
|
+
@result_set = nil
|
|
240
|
+
self
|
|
241
|
+
end
|
|
242
|
+
|
|
243
|
+
# The set of predicates of Plan which must return true for #=== to
|
|
244
|
+
# return true
|
|
245
|
+
attr_reader :plan_predicates
|
|
246
|
+
# The set of predicates of Plan which must return false for #=== to
|
|
247
|
+
# return true.
|
|
248
|
+
attr_reader :neg_plan_predicates
|
|
249
|
+
|
|
250
|
+
class << self
|
|
251
|
+
# For each name in +names+, define the #name and #not_name methods
|
|
252
|
+
# on Query objects. When one of these methods is called on a Query
|
|
253
|
+
# object, plan.name?(task) must return true (resp. false) for the
|
|
254
|
+
# task to match.
|
|
255
|
+
def match_plan_predicates(*names)
|
|
256
|
+
names.each do |name|
|
|
257
|
+
class_eval <<-EOD
|
|
258
|
+
def #{name}
|
|
259
|
+
if neg_plan_predicates.include?(:#{name}?)
|
|
260
|
+
raise ArgumentError, "trying to match (#{name}? & !#{name}?)"
|
|
261
|
+
end
|
|
262
|
+
plan_predicates << :#{name}?
|
|
263
|
+
self
|
|
264
|
+
end
|
|
265
|
+
def not_#{name}
|
|
266
|
+
if plan_predicates.include?(:#{name}?)
|
|
267
|
+
raise ArgumentError, "trying to match (#{name}? & !#{name}?)"
|
|
268
|
+
end
|
|
269
|
+
neg_plan_predicates << :#{name}?
|
|
270
|
+
self
|
|
271
|
+
end
|
|
272
|
+
EOD
|
|
273
|
+
end
|
|
274
|
+
end
|
|
275
|
+
end
|
|
276
|
+
match_plan_predicates :mission, :permanent
|
|
277
|
+
|
|
278
|
+
# Returns the set of tasks from the query for which no parent in
|
|
279
|
+
# +relation+ can be found in the query itself
|
|
280
|
+
def roots(relation)
|
|
281
|
+
@result_set = plan.query_roots(result_set, relation)
|
|
282
|
+
self
|
|
283
|
+
end
|
|
284
|
+
|
|
285
|
+
# True if +task+ matches the query. Call #result_set to have the set of
|
|
286
|
+
# tasks which match in the given plan.
|
|
287
|
+
def ===(task)
|
|
288
|
+
return unless super
|
|
289
|
+
|
|
290
|
+
for pred in plan_predicates
|
|
291
|
+
return unless plan.send(pred, task)
|
|
292
|
+
end
|
|
293
|
+
for neg_pred in neg_plan_predicates
|
|
294
|
+
return if plan.send(neg_pred, task)
|
|
295
|
+
end
|
|
296
|
+
true
|
|
297
|
+
end
|
|
298
|
+
|
|
299
|
+
# Iterates on all the tasks in the given plan which match the query
|
|
300
|
+
def each(&block)
|
|
301
|
+
plan.query_each(result_set, &block)
|
|
302
|
+
self
|
|
303
|
+
end
|
|
304
|
+
include Enumerable
|
|
305
|
+
end
|
|
306
|
+
|
|
307
|
+
# TaskIndex objects are used to maintain a set of tasks as classified sets,
|
|
308
|
+
# speeding up query operations. See Plan#task_index.
|
|
309
|
+
class TaskIndex
|
|
310
|
+
# A model => ValueSet map of the tasks for each model
|
|
311
|
+
attr_reader :by_model
|
|
312
|
+
# A state => ValueSet map of tasks given their state. The state is
|
|
313
|
+
# a symbol in [:pending, :starting, :running, :finishing,
|
|
314
|
+
# :finished]
|
|
315
|
+
attr_reader :by_state
|
|
316
|
+
# A peer => ValueSet map of tasks given their owner.
|
|
317
|
+
attr_reader :by_owner
|
|
318
|
+
# The set of tasks which have an event which is being repaired
|
|
319
|
+
attr_reader :repaired_tasks
|
|
320
|
+
|
|
321
|
+
def initialize
|
|
322
|
+
@by_model = Hash.new { |h, k| h[k] = ValueSet.new }
|
|
323
|
+
@by_state = Hash.new
|
|
324
|
+
TaskMatcher::STATE_PREDICATES.each do |state_name|
|
|
325
|
+
by_state[state_name] = ValueSet.new
|
|
326
|
+
end
|
|
327
|
+
@by_owner = Hash.new
|
|
328
|
+
@task_state = Hash.new
|
|
329
|
+
@repaired_tasks = ValueSet.new
|
|
330
|
+
end
|
|
331
|
+
|
|
332
|
+
# Add a new task to this index
|
|
333
|
+
def add(task)
|
|
334
|
+
for klass in task.model.ancestors
|
|
335
|
+
by_model[klass] << task
|
|
336
|
+
end
|
|
337
|
+
by_state[:pending?] << task
|
|
338
|
+
for owner in task.owners
|
|
339
|
+
add_owner(task, owner)
|
|
340
|
+
end
|
|
341
|
+
end
|
|
342
|
+
|
|
343
|
+
# Updates the index to reflect that +new_owner+ now owns +task+
|
|
344
|
+
def add_owner(task, new_owner)
|
|
345
|
+
(by_owner[new_owner] ||= ValueSet.new) << task
|
|
346
|
+
end
|
|
347
|
+
|
|
348
|
+
# Updates the index to reflect that +peer+ no more owns +task+
|
|
349
|
+
def remove_owner(task, peer)
|
|
350
|
+
if set = by_owner[peer]
|
|
351
|
+
set.delete(task)
|
|
352
|
+
if set.empty?
|
|
353
|
+
by_owner.delete(peer)
|
|
354
|
+
end
|
|
355
|
+
end
|
|
356
|
+
end
|
|
357
|
+
|
|
358
|
+
# Updates the index to reflect a change of state for +task+
|
|
359
|
+
def set_state(task, new_state)
|
|
360
|
+
for state_set in by_state
|
|
361
|
+
state_set.last.delete(task)
|
|
362
|
+
end
|
|
363
|
+
by_state[new_state] << task
|
|
364
|
+
if new_state == :success? || new_state == :failed?
|
|
365
|
+
by_state[:finished?] << task
|
|
366
|
+
end
|
|
367
|
+
end
|
|
368
|
+
|
|
369
|
+
# Remove all references of +task+ from the index.
|
|
370
|
+
def remove(task)
|
|
371
|
+
for klass in task.model.ancestors
|
|
372
|
+
by_model[klass].delete(task)
|
|
373
|
+
end
|
|
374
|
+
for state_set in by_state
|
|
375
|
+
state_set.last.delete(task)
|
|
376
|
+
end
|
|
377
|
+
for owner in task.owners
|
|
378
|
+
remove_owner(task, owner)
|
|
379
|
+
end
|
|
380
|
+
end
|
|
381
|
+
end
|
|
382
|
+
|
|
383
|
+
# This task combines multiple task matching predicates through a OR boolean
|
|
384
|
+
# operator.
|
|
385
|
+
class OrTaskMatcher < TaskMatcher
|
|
386
|
+
# Create a new OrTaskMatcher object combining the given predicates.
|
|
387
|
+
def initialize(*ops)
|
|
388
|
+
@ops = ops
|
|
389
|
+
super()
|
|
390
|
+
end
|
|
391
|
+
|
|
392
|
+
# Filters as much as non-matching tasks as possible out of +task_set+,
|
|
393
|
+
# based on the information in +task_index+
|
|
394
|
+
def filter(task_set, task_index)
|
|
395
|
+
result = ValueSet.new
|
|
396
|
+
for child in @ops
|
|
397
|
+
result.merge child.filter(task_set, task_index)
|
|
398
|
+
end
|
|
399
|
+
result
|
|
400
|
+
end
|
|
401
|
+
|
|
402
|
+
# Add a new predicate to the combination
|
|
403
|
+
def <<(op); @ops << op end
|
|
404
|
+
# True if the task matches at least one of the underlying predicates
|
|
405
|
+
def ===(task)
|
|
406
|
+
return unless @ops.any? { |op| op === task }
|
|
407
|
+
super
|
|
408
|
+
end
|
|
409
|
+
end
|
|
410
|
+
|
|
411
|
+
# Negate a given task-matching predicate
|
|
412
|
+
class NegateTaskMatcher < TaskMatcher
|
|
413
|
+
# Create a new TaskMatcher which matches if and only if +op+ does not
|
|
414
|
+
def initialize(op)
|
|
415
|
+
@op = op
|
|
416
|
+
super()
|
|
417
|
+
end
|
|
418
|
+
|
|
419
|
+
# Filters as much as non-matching tasks as possible out of +task_set+,
|
|
420
|
+
# based on the information in +task_index+
|
|
421
|
+
def filter(initial_set, task_index)
|
|
422
|
+
# WARNING: the value returned by filter is a SUPERSET of the
|
|
423
|
+
# possible values for the query. Therefore, the result of
|
|
424
|
+
# NegateTaskMatcher#filter is NOT
|
|
425
|
+
#
|
|
426
|
+
# initial_set - @op.filter(...)
|
|
427
|
+
initial_set
|
|
428
|
+
end
|
|
429
|
+
|
|
430
|
+
# True if the task matches at least one of the underlying predicates
|
|
431
|
+
def ===(task)
|
|
432
|
+
return if @op === task
|
|
433
|
+
super
|
|
434
|
+
end
|
|
435
|
+
end
|
|
436
|
+
|
|
437
|
+
# This task combines multiple task matching predicates through a AND boolean
|
|
438
|
+
# operator.
|
|
439
|
+
class AndTaskMatcher < TaskMatcher
|
|
440
|
+
# Create a new AndTaskMatcher object combining the given predicates.
|
|
441
|
+
def initialize(*ops)
|
|
442
|
+
@ops = ops
|
|
443
|
+
super()
|
|
444
|
+
end
|
|
445
|
+
|
|
446
|
+
# Filters as much as non-matching tasks as possible out of +task_set+,
|
|
447
|
+
# based on the information in +task_index+
|
|
448
|
+
def filter(task_set, task_index)
|
|
449
|
+
result = task_set
|
|
450
|
+
for child in @ops
|
|
451
|
+
result &= child.filter(task_set, task_index)
|
|
452
|
+
end
|
|
453
|
+
result
|
|
454
|
+
end
|
|
455
|
+
|
|
456
|
+
# Add a new predicate to the combination
|
|
457
|
+
def <<(op); @ops << op end
|
|
458
|
+
# True if the task matches at least one of the underlying predicates
|
|
459
|
+
def ===(task)
|
|
460
|
+
return unless @ops.all? { |op| op === task }
|
|
461
|
+
super
|
|
462
|
+
end
|
|
463
|
+
end
|
|
464
|
+
|
|
465
|
+
class Plan
|
|
466
|
+
# Returns a Query object on this plan
|
|
467
|
+
def find_tasks(model = nil, args = nil)
|
|
468
|
+
q = Query.new(self)
|
|
469
|
+
if model || args
|
|
470
|
+
q.which_fullfills(model, args)
|
|
471
|
+
end
|
|
472
|
+
q
|
|
473
|
+
end
|
|
474
|
+
|
|
475
|
+
# Called by TaskMatcher#result_set and Query#result_set to get the set
|
|
476
|
+
# of tasks matching +matcher+
|
|
477
|
+
def query_result_set(matcher)
|
|
478
|
+
result = ValueSet.new
|
|
479
|
+
for task in matcher.filter(known_tasks, task_index)
|
|
480
|
+
result << task if matcher === task
|
|
481
|
+
end
|
|
482
|
+
result
|
|
483
|
+
end
|
|
484
|
+
|
|
485
|
+
# Called by TaskMatcher#each and Query#each to return the result of
|
|
486
|
+
# this query on +self+
|
|
487
|
+
def query_each(result_set, &block)
|
|
488
|
+
for task in result_set
|
|
489
|
+
yield(task)
|
|
490
|
+
end
|
|
491
|
+
end
|
|
492
|
+
|
|
493
|
+
# Given the result set of +query+, returns the subset of tasks which
|
|
494
|
+
# have no parent in +query+
|
|
495
|
+
def query_roots(result_set, relation)
|
|
496
|
+
children = ValueSet.new
|
|
497
|
+
found = ValueSet.new
|
|
498
|
+
for task in result_set
|
|
499
|
+
next if children.include?(task)
|
|
500
|
+
task_children = task.generated_subgraph(relation)
|
|
501
|
+
found -= task_children
|
|
502
|
+
children.merge(task_children)
|
|
503
|
+
found << task
|
|
504
|
+
end
|
|
505
|
+
found
|
|
506
|
+
end
|
|
507
|
+
end
|
|
508
|
+
|
|
509
|
+
class Transaction
|
|
510
|
+
# Returns two sets of tasks, [plan, transaction]. The union of the two
|
|
511
|
+
# is the component that would be returned by
|
|
512
|
+
# +relation.generated_subgraphs(*seeds)+ if the transaction was
|
|
513
|
+
# committed
|
|
514
|
+
def merged_generated_subgraphs(relation, plan_seeds, transaction_seeds)
|
|
515
|
+
plan_set = ValueSet.new
|
|
516
|
+
transaction_set = ValueSet.new
|
|
517
|
+
plan_seeds = plan_seeds.to_value_set
|
|
518
|
+
transaction_seeds = transaction_seeds.to_value_set
|
|
519
|
+
|
|
520
|
+
loop do
|
|
521
|
+
old_transaction_set = transaction_set.dup
|
|
522
|
+
transaction_set.merge(transaction_seeds)
|
|
523
|
+
for new_set in relation.generated_subgraphs(transaction_seeds, false)
|
|
524
|
+
transaction_set.merge(new_set)
|
|
525
|
+
end
|
|
526
|
+
|
|
527
|
+
if old_transaction_set.size != transaction_set.size
|
|
528
|
+
for o in (transaction_set - old_transaction_set)
|
|
529
|
+
if o.respond_to?(:__getobj__)
|
|
530
|
+
o.__getobj__.each_child_object(relation) do |child|
|
|
531
|
+
plan_seeds << child unless self[child, false]
|
|
532
|
+
end
|
|
533
|
+
end
|
|
534
|
+
end
|
|
535
|
+
end
|
|
536
|
+
transaction_seeds.clear
|
|
537
|
+
|
|
538
|
+
plan_set.merge(plan_seeds)
|
|
539
|
+
plan_seeds.each do |seed|
|
|
540
|
+
relation.each_dfs(seed, BGL::Graph::TREE) do |_, dest, _, kind|
|
|
541
|
+
next if plan_set.include?(dest)
|
|
542
|
+
if self[dest, false]
|
|
543
|
+
proxy = wrap(dest, false)
|
|
544
|
+
unless transaction_set.include?(proxy)
|
|
545
|
+
transaction_seeds << proxy
|
|
546
|
+
end
|
|
547
|
+
relation.prune # transaction branches must be developed inside the transaction
|
|
548
|
+
else
|
|
549
|
+
plan_set << dest
|
|
550
|
+
end
|
|
551
|
+
end
|
|
552
|
+
end
|
|
553
|
+
break if transaction_seeds.empty?
|
|
554
|
+
|
|
555
|
+
plan_seeds.clear
|
|
556
|
+
end
|
|
557
|
+
|
|
558
|
+
[plan_set, transaction_set]
|
|
559
|
+
end
|
|
560
|
+
|
|
561
|
+
# Returns [plan_set, transaction_set], where the first is the set of
|
|
562
|
+
# plan tasks matching +matcher+ and the second the set of transaction
|
|
563
|
+
# tasks matching it. The two sets are disjoint.
|
|
564
|
+
def query_result_set(matcher)
|
|
565
|
+
plan_set = ValueSet.new
|
|
566
|
+
for task in plan.query_result_set(matcher)
|
|
567
|
+
plan_set << task unless self[task, false]
|
|
568
|
+
end
|
|
569
|
+
|
|
570
|
+
transaction_set = super
|
|
571
|
+
[plan_set, transaction_set]
|
|
572
|
+
end
|
|
573
|
+
|
|
574
|
+
# Yields tasks in the result set of +query+. Unlike Query#result_set,
|
|
575
|
+
# all the tasks are included in the transaction
|
|
576
|
+
def query_each(result_set)
|
|
577
|
+
plan_set, trsc_set = result_set
|
|
578
|
+
plan_set.each { |task| yield(self[task]) }
|
|
579
|
+
trsc_set.each { |task| yield(task) }
|
|
580
|
+
end
|
|
581
|
+
|
|
582
|
+
# Given the result set of +query+, returns the subset of tasks which
|
|
583
|
+
# have no parent in +query+
|
|
584
|
+
def query_roots(result_set, relation)
|
|
585
|
+
plan_set , trsc_set = *result_set
|
|
586
|
+
plan_result , trsc_result = ValueSet.new , ValueSet.new
|
|
587
|
+
plan_children , trsc_children = ValueSet.new , ValueSet.new
|
|
588
|
+
|
|
589
|
+
for task in plan_set
|
|
590
|
+
next if plan_children.include?(task)
|
|
591
|
+
task_plan_children, task_trsc_children =
|
|
592
|
+
merged_generated_subgraphs(relation, [task], [])
|
|
593
|
+
|
|
594
|
+
plan_result -= task_plan_children
|
|
595
|
+
trsc_result -= task_trsc_children
|
|
596
|
+
plan_children.merge(task_plan_children)
|
|
597
|
+
trsc_children.merge(task_trsc_children)
|
|
598
|
+
|
|
599
|
+
plan_result << task
|
|
600
|
+
end
|
|
601
|
+
|
|
602
|
+
for task in trsc_set
|
|
603
|
+
next if trsc_children.include?(task)
|
|
604
|
+
task_plan_children, task_trsc_children =
|
|
605
|
+
merged_generated_subgraphs(relation, [], [task])
|
|
606
|
+
|
|
607
|
+
plan_result -= task_plan_children
|
|
608
|
+
trsc_result -= task_trsc_children
|
|
609
|
+
plan_children.merge(task_plan_children)
|
|
610
|
+
trsc_children.merge(task_trsc_children)
|
|
611
|
+
|
|
612
|
+
trsc_result << task
|
|
613
|
+
end
|
|
614
|
+
|
|
615
|
+
[plan_result, trsc_result]
|
|
616
|
+
end
|
|
617
|
+
end
|
|
618
|
+
end
|
|
619
|
+
|