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.
Files changed (240) hide show
  1. data/.gitignore +29 -0
  2. data/History.txt +4 -0
  3. data/License-fr.txt +519 -0
  4. data/License.txt +515 -0
  5. data/Manifest.txt +245 -0
  6. data/NOTES +4 -0
  7. data/README.txt +163 -0
  8. data/Rakefile +161 -0
  9. data/TODO.txt +146 -0
  10. data/app/README.txt +24 -0
  11. data/app/Rakefile +8 -0
  12. data/app/config/ROBOT.rb +5 -0
  13. data/app/config/app.yml +91 -0
  14. data/app/config/init.rb +7 -0
  15. data/app/config/roby.yml +3 -0
  16. data/app/controllers/.gitattributes +0 -0
  17. data/app/controllers/ROBOT.rb +2 -0
  18. data/app/data/.gitattributes +0 -0
  19. data/app/planners/ROBOT/main.rb +6 -0
  20. data/app/planners/main.rb +5 -0
  21. data/app/scripts/distributed +3 -0
  22. data/app/scripts/generate/bookmarks +3 -0
  23. data/app/scripts/replay +3 -0
  24. data/app/scripts/results +3 -0
  25. data/app/scripts/run +3 -0
  26. data/app/scripts/server +3 -0
  27. data/app/scripts/shell +3 -0
  28. data/app/scripts/test +3 -0
  29. data/app/tasks/.gitattributes +0 -0
  30. data/app/tasks/ROBOT/.gitattributes +0 -0
  31. data/bin/roby +210 -0
  32. data/bin/roby-log +168 -0
  33. data/bin/roby-shell +25 -0
  34. data/doc/images/event_generalization.png +0 -0
  35. data/doc/images/exception_propagation_1.png +0 -0
  36. data/doc/images/exception_propagation_2.png +0 -0
  37. data/doc/images/exception_propagation_3.png +0 -0
  38. data/doc/images/exception_propagation_4.png +0 -0
  39. data/doc/images/exception_propagation_5.png +0 -0
  40. data/doc/images/replay_handler_error.png +0 -0
  41. data/doc/images/replay_handler_error_0.png +0 -0
  42. data/doc/images/replay_handler_error_1.png +0 -0
  43. data/doc/images/roby_cycle_overview.png +0 -0
  44. data/doc/images/roby_replay_02.png +0 -0
  45. data/doc/images/roby_replay_03.png +0 -0
  46. data/doc/images/roby_replay_04.png +0 -0
  47. data/doc/images/roby_replay_event_representation.png +0 -0
  48. data/doc/images/roby_replay_first_state.png +0 -0
  49. data/doc/images/roby_replay_relations.png +0 -0
  50. data/doc/images/roby_replay_startup.png +0 -0
  51. data/doc/images/task_event_generalization.png +0 -0
  52. data/doc/papers.rdoc +11 -0
  53. data/doc/styles/allison.css +314 -0
  54. data/doc/styles/allison.js +316 -0
  55. data/doc/styles/allison.rb +276 -0
  56. data/doc/styles/jamis.rb +593 -0
  57. data/doc/tutorials/01-GettingStarted.rdoc +86 -0
  58. data/doc/tutorials/02-GoForward.rdoc +220 -0
  59. data/doc/tutorials/03-PlannedPath.rdoc +268 -0
  60. data/doc/tutorials/04-EventPropagation.rdoc +236 -0
  61. data/doc/tutorials/05-ErrorHandling.rdoc +319 -0
  62. data/doc/tutorials/06-Overview.rdoc +40 -0
  63. data/doc/videos.rdoc +69 -0
  64. data/ext/droby/dump.cc +175 -0
  65. data/ext/droby/extconf.rb +3 -0
  66. data/ext/graph/algorithm.cc +746 -0
  67. data/ext/graph/extconf.rb +7 -0
  68. data/ext/graph/graph.cc +529 -0
  69. data/ext/graph/graph.hh +183 -0
  70. data/ext/graph/iterator_sequence.hh +102 -0
  71. data/ext/graph/undirected_dfs.hh +226 -0
  72. data/ext/graph/undirected_graph.hh +421 -0
  73. data/lib/roby.rb +41 -0
  74. data/lib/roby/app.rb +870 -0
  75. data/lib/roby/app/rake.rb +56 -0
  76. data/lib/roby/app/run.rb +14 -0
  77. data/lib/roby/app/scripts/distributed.rb +13 -0
  78. data/lib/roby/app/scripts/generate/bookmarks.rb +162 -0
  79. data/lib/roby/app/scripts/replay.rb +31 -0
  80. data/lib/roby/app/scripts/results.rb +15 -0
  81. data/lib/roby/app/scripts/run.rb +26 -0
  82. data/lib/roby/app/scripts/server.rb +18 -0
  83. data/lib/roby/app/scripts/shell.rb +88 -0
  84. data/lib/roby/app/scripts/test.rb +40 -0
  85. data/lib/roby/basic_object.rb +151 -0
  86. data/lib/roby/config.rb +5 -0
  87. data/lib/roby/control.rb +747 -0
  88. data/lib/roby/decision_control.rb +17 -0
  89. data/lib/roby/distributed.rb +32 -0
  90. data/lib/roby/distributed/base.rb +440 -0
  91. data/lib/roby/distributed/communication.rb +871 -0
  92. data/lib/roby/distributed/connection_space.rb +592 -0
  93. data/lib/roby/distributed/distributed_object.rb +206 -0
  94. data/lib/roby/distributed/drb.rb +62 -0
  95. data/lib/roby/distributed/notifications.rb +539 -0
  96. data/lib/roby/distributed/peer.rb +550 -0
  97. data/lib/roby/distributed/protocol.rb +529 -0
  98. data/lib/roby/distributed/proxy.rb +343 -0
  99. data/lib/roby/distributed/subscription.rb +311 -0
  100. data/lib/roby/distributed/transaction.rb +498 -0
  101. data/lib/roby/event.rb +897 -0
  102. data/lib/roby/exceptions.rb +234 -0
  103. data/lib/roby/executives/simple.rb +30 -0
  104. data/lib/roby/graph.rb +166 -0
  105. data/lib/roby/interface.rb +390 -0
  106. data/lib/roby/log.rb +3 -0
  107. data/lib/roby/log/chronicle.rb +303 -0
  108. data/lib/roby/log/console.rb +72 -0
  109. data/lib/roby/log/data_stream.rb +197 -0
  110. data/lib/roby/log/dot.rb +279 -0
  111. data/lib/roby/log/event_stream.rb +151 -0
  112. data/lib/roby/log/file.rb +340 -0
  113. data/lib/roby/log/gui/basic_display.ui +83 -0
  114. data/lib/roby/log/gui/chronicle.rb +26 -0
  115. data/lib/roby/log/gui/chronicle_view.rb +40 -0
  116. data/lib/roby/log/gui/chronicle_view.ui +70 -0
  117. data/lib/roby/log/gui/data_displays.rb +172 -0
  118. data/lib/roby/log/gui/data_displays.ui +155 -0
  119. data/lib/roby/log/gui/notifications.rb +26 -0
  120. data/lib/roby/log/gui/relations.rb +248 -0
  121. data/lib/roby/log/gui/relations.ui +123 -0
  122. data/lib/roby/log/gui/relations_view.rb +185 -0
  123. data/lib/roby/log/gui/relations_view.ui +149 -0
  124. data/lib/roby/log/gui/replay.rb +327 -0
  125. data/lib/roby/log/gui/replay_controls.rb +200 -0
  126. data/lib/roby/log/gui/replay_controls.ui +259 -0
  127. data/lib/roby/log/gui/runtime.rb +130 -0
  128. data/lib/roby/log/hooks.rb +185 -0
  129. data/lib/roby/log/logger.rb +202 -0
  130. data/lib/roby/log/notifications.rb +244 -0
  131. data/lib/roby/log/plan_rebuilder.rb +470 -0
  132. data/lib/roby/log/relations.rb +1056 -0
  133. data/lib/roby/log/server.rb +550 -0
  134. data/lib/roby/log/sqlite.rb +47 -0
  135. data/lib/roby/log/timings.rb +164 -0
  136. data/lib/roby/plan-object.rb +247 -0
  137. data/lib/roby/plan.rb +762 -0
  138. data/lib/roby/planning.rb +13 -0
  139. data/lib/roby/planning/loops.rb +302 -0
  140. data/lib/roby/planning/model.rb +906 -0
  141. data/lib/roby/planning/task.rb +151 -0
  142. data/lib/roby/propagation.rb +562 -0
  143. data/lib/roby/query.rb +619 -0
  144. data/lib/roby/relations.rb +583 -0
  145. data/lib/roby/relations/conflicts.rb +70 -0
  146. data/lib/roby/relations/ensured.rb +20 -0
  147. data/lib/roby/relations/error_handling.rb +23 -0
  148. data/lib/roby/relations/events.rb +9 -0
  149. data/lib/roby/relations/executed_by.rb +193 -0
  150. data/lib/roby/relations/hierarchy.rb +239 -0
  151. data/lib/roby/relations/influence.rb +10 -0
  152. data/lib/roby/relations/planned_by.rb +63 -0
  153. data/lib/roby/robot.rb +7 -0
  154. data/lib/roby/standard_errors.rb +218 -0
  155. data/lib/roby/state.rb +5 -0
  156. data/lib/roby/state/events.rb +221 -0
  157. data/lib/roby/state/information.rb +55 -0
  158. data/lib/roby/state/pos.rb +110 -0
  159. data/lib/roby/state/shapes.rb +32 -0
  160. data/lib/roby/state/state.rb +353 -0
  161. data/lib/roby/support.rb +92 -0
  162. data/lib/roby/task-operations.rb +182 -0
  163. data/lib/roby/task.rb +1618 -0
  164. data/lib/roby/test/common.rb +399 -0
  165. data/lib/roby/test/distributed.rb +214 -0
  166. data/lib/roby/test/tasks/empty_task.rb +9 -0
  167. data/lib/roby/test/tasks/goto.rb +36 -0
  168. data/lib/roby/test/tasks/simple_task.rb +23 -0
  169. data/lib/roby/test/testcase.rb +519 -0
  170. data/lib/roby/test/tools.rb +160 -0
  171. data/lib/roby/thread_task.rb +87 -0
  172. data/lib/roby/transactions.rb +462 -0
  173. data/lib/roby/transactions/proxy.rb +292 -0
  174. data/lib/roby/transactions/updates.rb +139 -0
  175. data/plugins/fault_injection/History.txt +4 -0
  176. data/plugins/fault_injection/README.txt +37 -0
  177. data/plugins/fault_injection/Rakefile +18 -0
  178. data/plugins/fault_injection/TODO.txt +0 -0
  179. data/plugins/fault_injection/app.rb +52 -0
  180. data/plugins/fault_injection/fault_injection.rb +89 -0
  181. data/plugins/fault_injection/test/test_fault_injection.rb +84 -0
  182. data/plugins/subsystems/README.txt +40 -0
  183. data/plugins/subsystems/Rakefile +18 -0
  184. data/plugins/subsystems/app.rb +171 -0
  185. data/plugins/subsystems/test/app/README +24 -0
  186. data/plugins/subsystems/test/app/Rakefile +8 -0
  187. data/plugins/subsystems/test/app/config/app.yml +71 -0
  188. data/plugins/subsystems/test/app/config/init.rb +9 -0
  189. data/plugins/subsystems/test/app/config/roby.yml +3 -0
  190. data/plugins/subsystems/test/app/planners/main.rb +20 -0
  191. data/plugins/subsystems/test/app/scripts/distributed +3 -0
  192. data/plugins/subsystems/test/app/scripts/replay +3 -0
  193. data/plugins/subsystems/test/app/scripts/results +3 -0
  194. data/plugins/subsystems/test/app/scripts/run +3 -0
  195. data/plugins/subsystems/test/app/scripts/server +3 -0
  196. data/plugins/subsystems/test/app/scripts/shell +3 -0
  197. data/plugins/subsystems/test/app/scripts/test +3 -0
  198. data/plugins/subsystems/test/app/tasks/services.rb +15 -0
  199. data/plugins/subsystems/test/test_subsystems.rb +71 -0
  200. data/test/distributed/test_communication.rb +178 -0
  201. data/test/distributed/test_connection.rb +282 -0
  202. data/test/distributed/test_execution.rb +373 -0
  203. data/test/distributed/test_mixed_plan.rb +341 -0
  204. data/test/distributed/test_plan_notifications.rb +238 -0
  205. data/test/distributed/test_protocol.rb +516 -0
  206. data/test/distributed/test_query.rb +102 -0
  207. data/test/distributed/test_remote_plan.rb +491 -0
  208. data/test/distributed/test_transaction.rb +463 -0
  209. data/test/mockups/tasks.rb +27 -0
  210. data/test/planning/test_loops.rb +380 -0
  211. data/test/planning/test_model.rb +427 -0
  212. data/test/planning/test_task.rb +106 -0
  213. data/test/relations/test_conflicts.rb +42 -0
  214. data/test/relations/test_ensured.rb +38 -0
  215. data/test/relations/test_executed_by.rb +149 -0
  216. data/test/relations/test_hierarchy.rb +158 -0
  217. data/test/relations/test_planned_by.rb +54 -0
  218. data/test/suite_core.rb +24 -0
  219. data/test/suite_distributed.rb +9 -0
  220. data/test/suite_planning.rb +3 -0
  221. data/test/suite_relations.rb +8 -0
  222. data/test/test_bgl.rb +508 -0
  223. data/test/test_control.rb +399 -0
  224. data/test/test_event.rb +894 -0
  225. data/test/test_exceptions.rb +592 -0
  226. data/test/test_interface.rb +37 -0
  227. data/test/test_log.rb +114 -0
  228. data/test/test_log_server.rb +132 -0
  229. data/test/test_plan.rb +584 -0
  230. data/test/test_propagation.rb +210 -0
  231. data/test/test_query.rb +266 -0
  232. data/test/test_relations.rb +180 -0
  233. data/test/test_state.rb +414 -0
  234. data/test/test_support.rb +16 -0
  235. data/test/test_task.rb +938 -0
  236. data/test/test_testcase.rb +122 -0
  237. data/test/test_thread_task.rb +73 -0
  238. data/test/test_transactions.rb +569 -0
  239. data/test/test_transactions_proxy.rb +198 -0
  240. 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
+