roby 0.7

Sign up to get free protection for your applications and to get access to all the features.
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/plan.rb ADDED
@@ -0,0 +1,762 @@
1
+ require 'roby/event'
2
+ require 'roby/task'
3
+ require 'roby/relations'
4
+ require 'roby/basic_object'
5
+
6
+ module Roby
7
+ # A plan object is a collection of tasks and events. In plans, tasks can be
8
+ # +missions+ (#missions, #mission?), which means that they are the final
9
+ # goals of the system. A task is +useful+ if it helps into the realization
10
+ # of a mission (it is linked to a mission by #hierarchy_relation or one
11
+ # of the #service_relations), and is not useful otherwise. #garbage_collect
12
+ # removes the tasks that are not useful.
13
+ #
14
+ # The following event hooks are defined:
15
+ # * #inserted
16
+ # * #discarded
17
+ # * #discovered_tasks
18
+ # * #discovered_events
19
+ # * #replaced
20
+ # * #added_transaction
21
+ # * #removed_transaction
22
+ # * #garbage
23
+ # * #finalized_task
24
+ # * #finalized_event
25
+ #
26
+ class Plan < BasicObject
27
+ extend Logger::Hierarchy
28
+ extend Logger::Forward
29
+
30
+ # The task index for this plan
31
+ attr_reader :task_index
32
+
33
+ # The list of tasks that are included in this plan
34
+ attr_reader :known_tasks
35
+ # The set of events that are defined by #known_tasks
36
+ attr_reader :task_events
37
+ # The list of the robot's missions. Do not change that set directly, use
38
+ # #insert and #discard instead.
39
+ attr_reader :missions
40
+ # The list of events that are not included in a task
41
+ attr_reader :free_events
42
+ # The list of tasks that are kept outside GC. Do not change that set
43
+ # directly, use #permanent and #auto instead.
44
+ attr_reader :keepalive
45
+
46
+ # A map of event => task repairs. Whenever an exception is found,
47
+ # exception propagation checks that no repair is defined for that
48
+ # particular event or for events that are forwarded by it.
49
+ #
50
+ # See also #add_repair and #remove_repair
51
+ attr_reader :repairs
52
+
53
+ # A set of tasks which are useful (and as such would not been garbage
54
+ # collected), but we want to GC anyway
55
+ attr_reader :force_gc
56
+
57
+ # A set of task for which GC should not be attempted, either because
58
+ # they are not interruptible or because their start or stop command
59
+ # failed
60
+ attr_reader :gc_quarantine
61
+
62
+ # The set of transactions which are built on top of this plan
63
+ attr_reader :transactions
64
+
65
+ # If this object is the main plan, checks if we are subscribed to
66
+ # the whole remote plan
67
+ def sibling_on?(peer)
68
+ if Roby.plan == self then peer.remote_plan
69
+ else super
70
+ end
71
+ end
72
+
73
+ def initialize
74
+ @missions = ValueSet.new
75
+ @keepalive = ValueSet.new
76
+ @known_tasks = ValueSet.new
77
+ @free_events = ValueSet.new
78
+ @task_events = ValueSet.new
79
+ @force_gc = ValueSet.new
80
+ @gc_quarantine = ValueSet.new
81
+ @transactions = ValueSet.new
82
+ @repairs = Hash.new
83
+
84
+ @task_index = Roby::TaskIndex.new
85
+
86
+ super() if defined? super
87
+ end
88
+
89
+ def inspect
90
+ "#<#{to_s}: missions=#{missions.to_s} tasks=#{known_tasks.to_s} events=#{free_events.to_s} transactions=#{transactions.to_s}>"
91
+ end
92
+
93
+ # call-seq:
94
+ # plan.partition_event_task(objects) => events, tasks
95
+ #
96
+ def partition_event_task(objects)
97
+ if objects.respond_to?(:to_task) then return nil, [objects.to_task]
98
+ elsif objects.respond_to?(:to_event) then return [objects.to_event], nil
99
+ elsif !objects.respond_to?(:each)
100
+ raise TypeError, "expecting a task, event, or a collection of tasks and events, got #{objects}"
101
+ end
102
+
103
+ evts, tasks = objects.partition do |o|
104
+ if o.respond_to?(:to_event) then true
105
+ elsif o.respond_to?(:to_task) then false
106
+ else raise ArgumentError, "found #{o || 'nil'} which is neither a task nor an event"
107
+ end
108
+ end
109
+ return evts, tasks
110
+ end
111
+
112
+ # If this plan is a toplevel plan, returns self. If it is a
113
+ # transaction, returns the underlying plan
114
+ def real_plan
115
+ ret = self
116
+ while ret.respond_to?(:plan)
117
+ ret = ret.plan
118
+ end
119
+ ret
120
+ end
121
+
122
+ # Inserts a new mission in the plan. Its child tree is automatically
123
+ # inserted too. Returns the plan
124
+ def insert(task)
125
+ return if @missions.include?(task)
126
+
127
+ @missions << task
128
+ discover(task)
129
+ task.mission = true if task.self_owned?
130
+ inserted(task)
131
+ self
132
+ end
133
+ # Hook called when +tasks+ have been inserted in this plan
134
+ def inserted(tasks); super if defined? super end
135
+
136
+ # Forbid the GC to take out +task+
137
+ def permanent(task)
138
+ @keepalive << task
139
+ discover(task)
140
+ end
141
+
142
+ # Make GC finalize +task+ if it is not useful anymore
143
+ def auto(task); @keepalive.delete(task) end
144
+
145
+ def edit
146
+ if block_given?
147
+ Roby::Control.synchronize do
148
+ yield
149
+ end
150
+ end
151
+ end
152
+
153
+ def permanent?(task); @keepalive.include?(task) end
154
+
155
+ # Removes the task in +tasks+ from the list of missions
156
+ def discard(task)
157
+ @missions.delete(task)
158
+ discover(task)
159
+ task.mission = false if task.self_owned?
160
+
161
+ discarded(task)
162
+ self
163
+ end
164
+ # Hook called when +tasks+ have been discarded from this plan
165
+ def discarded(tasks); super if defined? super end
166
+
167
+ def owns?(object)
168
+ (object.owners - owners).empty?
169
+ end
170
+
171
+ # Remove all tasks
172
+ def clear
173
+ @known_tasks.each { |t| t.clear_relations }
174
+ @known_tasks.clear
175
+ @free_events.each { |e| e.clear_relations }
176
+ @free_events.clear
177
+ @missions.clear
178
+ @keepalive.clear
179
+ end
180
+
181
+ def handle_replace(from, to)
182
+ return if from == to
183
+
184
+ # Check that +to+ is valid in all hierarchy relations where +from+ is a child
185
+ if !to.fullfills?(*from.fullfilled_model)
186
+ raise InvalidReplace.new(from, to, "to does not fullfills #{from.fullfilled_model}")
187
+ end
188
+
189
+ # Check that +to+ is in the same execution state than +from+
190
+ if !to.compatible_state?(from)
191
+ raise InvalidReplace.new(from, to, "state. #{from.running?}, #{to.running?}")
192
+ end
193
+
194
+ # Swap the subplans of +from+ and +to+
195
+ yield(from, to)
196
+
197
+ replaced(from, to)
198
+ if mission?(from)
199
+ discard(from)
200
+ insert(to)
201
+ elsif permanent?(from)
202
+ auto(from)
203
+ permanent(to)
204
+ else
205
+ discover(to)
206
+ end
207
+ end
208
+
209
+ def replace_task(from, to)
210
+ handle_replace(from, to) do
211
+ from.replace_by(to)
212
+ end
213
+ end
214
+
215
+ def replace(from, to)
216
+ handle_replace(from, to) do
217
+ from.replace_subplan_by(to)
218
+ end
219
+ end
220
+
221
+ # Hook called when +to+ has replaced +from+ in this plan
222
+ def replaced(from, to); super if defined? super end
223
+
224
+ # Check that this is an executable plan. This is always true for
225
+ # plain Plan objects and false for transcations
226
+ def executable?; true end
227
+
228
+ # call-seq:
229
+ # plan.discover([t1, t2, ...]) => plan
230
+ #
231
+ # Updates Plan#known_tasks with either the child tree of the tasks in
232
+ # +objects+
233
+ def discover(objects)
234
+ event_seeds, tasks = partition_event_task(objects)
235
+ event_seeds = (event_seeds || ValueSet.new).to_value_set
236
+
237
+ if tasks
238
+ tasks = tasks.to_value_set
239
+ new_tasks = useful_task_component(nil, tasks, tasks)
240
+ unless new_tasks.empty?
241
+ old_task_events = task_events.dup
242
+ new_tasks = discover_task_set(new_tasks)
243
+ event_seeds.merge(task_events - old_task_events)
244
+ end
245
+ end
246
+
247
+ if !event_seeds.empty?
248
+ events = event_seeds.dup
249
+
250
+ # now, we include the set of free events that are linked to
251
+ # +new_tasks+ in +events+
252
+ EventStructure.each_root_relation do |rel|
253
+ components = rel.generated_subgraphs(event_seeds, false)
254
+ components.concat rel.reverse.generated_subgraphs(event_seeds, false)
255
+ for c in components
256
+ events.merge(c.to_value_set)
257
+ end
258
+ end
259
+
260
+ discover_event_set(events - task_events - free_events)
261
+ end
262
+
263
+ self
264
+ end
265
+
266
+ # Add +events+ to the set of known events and call discovered_events
267
+ # for the new events
268
+ #
269
+ # This is for internal use, use #discover instead
270
+ def discover_event_set(events)
271
+ events = events.difference(free_events)
272
+ events.delete_if do |e|
273
+ if !e.root_object?
274
+ true
275
+ else
276
+ e.plan = self
277
+ false
278
+ end
279
+ end
280
+
281
+ unless events.empty?
282
+ free_events.merge(events)
283
+ discovered_events(events)
284
+ end
285
+
286
+ events
287
+ end
288
+
289
+ # Add +tasks+ to the set of known tasks and call discovered_tasks for
290
+ # the new tasks
291
+ #
292
+ # This is for internal use, use #discover instead
293
+ def discover_task_set(tasks)
294
+ tasks = tasks.difference(known_tasks)
295
+ for t in tasks
296
+ t.plan = self
297
+ task_events.merge t.bound_events.values.to_value_set
298
+ task_index.add t
299
+ end
300
+ known_tasks.merge tasks
301
+ discovered_tasks(tasks)
302
+
303
+ for t in tasks
304
+ t.instantiate_model_event_relations
305
+ end
306
+ tasks
307
+ end
308
+
309
+ # DEPRECATED. Use #discovered_tasks instead
310
+ def discovered(tasks); super if defined? super end
311
+ # Hook called when new tasks have been discovered in this plan
312
+ def discovered_tasks(tasks)
313
+ discovered(tasks)
314
+ super if defined? super
315
+ end
316
+ # Hook called when new events have been discovered in this plan
317
+ def discovered_events(events)
318
+ super if defined? super
319
+ end
320
+
321
+ # Hook called when a new transaction has been built on top of this plan
322
+ def added_transaction(trsc); super if defined? super end
323
+ # Removes the transaction +trsc+ from the list of known transactions
324
+ # built on this plan
325
+ def remove_transaction(trsc)
326
+ transactions.delete(trsc)
327
+ removed_transaction(trsc)
328
+ end
329
+ # Hook called when a new transaction has been built on top of this plan
330
+ def removed_transaction(trsc); super if defined? super end
331
+
332
+ # Merges the set of tasks that are useful for +seeds+ into +useful_set+.
333
+ # Only the tasks that are in +complete_set+ are included.
334
+ def useful_task_component(complete_set, useful_set, seeds)
335
+ old_useful_set = useful_set.dup
336
+ for rel in TaskStructure.relations
337
+ next unless rel.root_relation?
338
+ for subgraph in rel.generated_subgraphs(seeds, false)
339
+ useful_set.merge(subgraph)
340
+ end
341
+ end
342
+
343
+ if complete_set
344
+ useful_set &= complete_set
345
+ end
346
+
347
+ if useful_set.size == old_useful_set.size || (complete_set && useful_set.size == complete_set.size)
348
+ useful_set
349
+ else
350
+ useful_task_component(complete_set, useful_set, (useful_set - old_useful_set))
351
+ end
352
+ end
353
+
354
+ # Returns the set of useful tasks in this plan
355
+ def locally_useful_tasks
356
+ # Remove all missions that are finished
357
+ for finished_mission in (@missions & task_index.by_state[:finished?])
358
+ if !task_index.repaired_tasks.include?(finished_mission)
359
+ discard(finished_mission)
360
+ end
361
+ end
362
+ for finished_permanent in (@keepalive & task_index.by_state[:finished?])
363
+ if !task_index.repaired_tasks.include?(finished_permanent)
364
+ auto(finished_permanent)
365
+ end
366
+ end
367
+
368
+ # Create the set of tasks which must be kept as-is
369
+ seeds = @missions | @keepalive
370
+ for trsc in transactions
371
+ seeds.merge trsc.proxy_objects.keys.to_value_set
372
+ end
373
+
374
+ return ValueSet.new if seeds.empty?
375
+
376
+ # Compute the set of LOCAL tasks which serve the seeds. The set of
377
+ # locally_useful_tasks is the union of the seeds and of this one
378
+ useful_task_component(local_tasks, seeds & local_tasks, seeds) | seeds
379
+ end
380
+
381
+ def local_tasks
382
+ task_index.by_owner[Roby::Distributed] || ValueSet.new
383
+ end
384
+
385
+ def remote_tasks
386
+ if local_tasks = task_index.by_owner[Roby::Distributed]
387
+ known_tasks - local_tasks
388
+ else
389
+ known_tasks
390
+ end
391
+ end
392
+
393
+ # Returns the set of unused tasks
394
+ def unneeded_tasks
395
+ # Get the set of local tasks that are serving one of our own missions or
396
+ # permanent tasks
397
+ useful = self.locally_useful_tasks
398
+
399
+ # Append to that the set of tasks that are useful for our peers and
400
+ # include the set of local tasks that are serving tasks in
401
+ # +remotely_useful+
402
+ remotely_useful = Distributed.remotely_useful_objects(remote_tasks, true, nil)
403
+ serving_remote = useful_task_component(local_tasks, useful & local_tasks, remotely_useful)
404
+
405
+ useful.merge remotely_useful
406
+ useful.merge serving_remote
407
+
408
+ (known_tasks - useful)
409
+ end
410
+
411
+ # Computes the set of useful tasks and checks that +task+ is in it.
412
+ # This is quite slow. It is here for debugging purposes. Do not use it
413
+ # in production code
414
+ def useful_task?(task)
415
+ known_tasks.include?(task) && !unneeded_tasks.include?(task)
416
+ end
417
+
418
+ def useful_event_component(useful_events)
419
+ current_size = useful_events.size
420
+ for rel in EventStructure.relations
421
+ next unless rel.root_relation?
422
+
423
+ for subgraph in rel.components(free_events, false)
424
+ subgraph = subgraph.to_value_set
425
+ if subgraph.intersects?(useful_events) || subgraph.intersects?(task_events)
426
+ useful_events.merge(subgraph)
427
+ if useful_events.include_all?(free_events)
428
+ return free_events
429
+ end
430
+ end
431
+ end
432
+
433
+ if useful_events.include_all?(free_events)
434
+ return free_events
435
+ end
436
+ end
437
+
438
+ if current_size != useful_events.size
439
+ useful_event_component(useful_events)
440
+ else
441
+ useful_events
442
+ end
443
+ end
444
+
445
+ # Computes the set of events that are useful in the plan Events are
446
+ # 'useful' when they are chained to a task.
447
+ def useful_events
448
+ return ValueSet.new if free_events.empty?
449
+ (free_events & useful_event_component(ValueSet.new))
450
+ end
451
+
452
+ # The set of events that can be removed from the plan
453
+ def unneeded_events
454
+ useful_events = self.useful_events
455
+
456
+ result = (free_events - useful_events)
457
+ result.delete_if do |ev|
458
+ transactions.any? { |trsc| trsc.wrap(ev, false) }
459
+ end
460
+ result
461
+ end
462
+
463
+ # Checks if +task+ is included in this plan
464
+ def include?(object); @known_tasks.include?(object) || @free_events.include?(object) end
465
+ # Checks if +task+ is a mission of this plan
466
+ def mission?(task); @missions.include?(task) end
467
+ # Count of tasks in this plan
468
+ def size; @known_tasks.size end
469
+ # Returns true if there is no task in this plan
470
+ def empty?; @known_tasks.empty? end
471
+ # Iterates on all tasks
472
+ def each_task; @known_tasks.each { |t| yield(t) } end
473
+
474
+ # Install a plan repair for +failure_point+ with +task+. If +task+ is
475
+ # pending, it is started.
476
+ #
477
+ # See also #repairs and #remove_repair
478
+ def add_repair(failure_point, task)
479
+ if !failure_point.kind_of?(Event)
480
+ raise TypeError, "failure point #{failure_point} should be an event"
481
+ elsif task.plan && task.plan != self
482
+ raise ArgumentError, "wrong plan: #{task} is in #{task.plan}, not #{self}"
483
+ elsif repairs.has_key?(failure_point)
484
+ raise ArgumentError, "there is already a plan repair defined for #{failure_point}: #{repairs[failure_point]}"
485
+ elsif !task.plan
486
+ discover(task)
487
+ end
488
+
489
+ repairs[failure_point] = task
490
+ if failure_point.generator.respond_to?(:task)
491
+ task_index.repaired_tasks << failure_point.generator.task
492
+ end
493
+
494
+ if task.pending?
495
+ Roby.once { task.start! }
496
+ end
497
+ end
498
+
499
+ # Removes +task+ from the set of active plan repairs.
500
+ #
501
+ # See also #repairs and #add_repair
502
+ def remove_repair(task)
503
+ repairs.delete_if do |ev, repair|
504
+ if repair == task
505
+ if ev.generator.respond_to?(:task)
506
+ task_index.repaired_tasks.delete(ev.generator.task)
507
+ end
508
+ true
509
+ end
510
+ end
511
+ end
512
+
513
+ # Return all repairs which apply on +event+
514
+ def repairs_for(event)
515
+ result = Hash.new
516
+
517
+ if event.generator.respond_to?(:task)
518
+ equivalent_generators = event.generator.generated_subgraph(EventStructure::Forwarding)
519
+
520
+ history = event.generator.task.history
521
+ id = event.propagation_id
522
+ index = history.index(event)
523
+ while index < history.size
524
+ ev = history[index]
525
+ break if ev.propagation_id != id
526
+
527
+ if equivalent_generators.include?(ev.generator) &&
528
+ (task = repairs[ev])
529
+
530
+ result[ev] = task
531
+ end
532
+
533
+ index += 1
534
+ end
535
+ elsif task = repairs[event]
536
+ result[event] = task
537
+ end
538
+
539
+ result
540
+ end
541
+
542
+ # Returns +object+ if object is a plan object from this plan, or if
543
+ # it has no plan yet (in which case it is added to the plan first).
544
+ # Otherwise, raises ArgumentError.
545
+ #
546
+ # This method is provided for consistency with Transaction#[]
547
+ def [](object)
548
+ if object.plan != self
549
+ raise ArgumentError, "#{object} is not from #{plan}"
550
+ elsif !object.plan
551
+ discover(object)
552
+ end
553
+ object
554
+ end
555
+
556
+ def self.can_gc?(task)
557
+ if task.starting? then true # wait for the task to be started before deciding ...
558
+ elsif task.running? && !task.finishing?
559
+ task.event(:stop).controlable?
560
+ else true
561
+ end
562
+ end
563
+
564
+ # Kills and removes all unneeded tasks
565
+ def garbage_collect(force_on = nil)
566
+ if force_on && !force_on.empty?
567
+ force_gc.merge(force_on.to_value_set)
568
+ end
569
+
570
+ # The set of tasks for which we queued stop! at this cycle
571
+ # #finishing? is false until the next event propagation cycle
572
+ finishing = ValueSet.new
573
+ did_something = true
574
+ while did_something
575
+ did_something = false
576
+
577
+ tasks = unneeded_tasks | force_gc
578
+ local_tasks = self.local_tasks & tasks
579
+ remote_tasks = tasks - local_tasks
580
+
581
+ # Remote tasks are simply removed, regardless of other concerns
582
+ for t in remote_tasks
583
+ Plan.debug { "GC: removing the remote task #{t}" }
584
+ remove_object(t)
585
+ end
586
+
587
+ break if local_tasks.empty?
588
+
589
+ if local_tasks.all? { |t| t.pending? || t.finished? }
590
+ local_tasks.each do |t|
591
+ Plan.debug { "GC: #{t} is not running, removed" }
592
+ garbage(t)
593
+ remove_object(t)
594
+ end
595
+ break
596
+ end
597
+
598
+ # Mark all root local_tasks as garbage
599
+ roots = nil
600
+ 2.times do |i|
601
+ roots = local_tasks.find_all do |t|
602
+ if t.root?
603
+ garbage(t)
604
+ true
605
+ else
606
+ Plan.debug { "GC: ignoring #{t}, it is not root" }
607
+ false
608
+ end
609
+ end
610
+
611
+ break if i == 1 || !roots.empty?
612
+
613
+ # There is a cycle somewhere. Try to break it by removing
614
+ # weak relations within elements of local_tasks
615
+ Plan.debug "cycle found, removing weak relations"
616
+
617
+ local_tasks.each do |t|
618
+ next if t.root?
619
+ t.each_graph do |rel|
620
+ rel.remove(t) if rel.weak?
621
+ end
622
+ end
623
+ end
624
+
625
+ (roots.to_value_set - finishing - gc_quarantine).each do |local_task|
626
+ if local_task.pending?
627
+ Plan.info "GC: removing pending task #{local_task}"
628
+ remove_object(local_task)
629
+ did_something = true
630
+ elsif local_task.starting?
631
+ # wait for task to be started before killing it
632
+ Plan.debug { "GC: #{local_task} is starting" }
633
+ elsif local_task.finished?
634
+ Plan.debug { "GC: #{local_task} is not running, removed" }
635
+ remove_object(local_task)
636
+ did_something = true
637
+ elsif !local_task.finishing?
638
+ if local_task.event(:stop).controlable?
639
+ Plan.debug { "GC: queueing #{local_task}/stop" }
640
+ if !local_task.respond_to?(:stop!)
641
+ Plan.fatal "something fishy: #{local_task}/stop is controlable but there is no #stop! method"
642
+ gc_quarantine << local_task
643
+ else
644
+ finishing << local_task
645
+ Roby::Control.once do
646
+ Plan.debug { "GC: stopping #{local_task}" }
647
+ local_task.stop!(nil)
648
+ end
649
+ end
650
+ else
651
+ Plan.warn "GC: ignored #{local_task}, it cannot be stopped"
652
+ gc_quarantine << local_task
653
+ end
654
+ elsif local_task.finishing?
655
+ Plan.debug { "GC: waiting for #{local_task} to finish" }
656
+ else
657
+ Plan.warn "GC: ignored #{local_task}"
658
+ end
659
+ end
660
+ end
661
+
662
+ unneeded_events.each do |event|
663
+ remove_object(event)
664
+ end
665
+ end
666
+
667
+ def remove_object(object)
668
+ if !object.root_object?
669
+ raise ArgumentError, "cannot remove #{object} which is a non-root object"
670
+ elsif object.plan != self
671
+ if known_tasks.include?(object) || free_events.include?(object)
672
+ raise ArgumentError, "#{object} is included in #{self} but #plan == #{object.plan}"
673
+ elsif !object.plan
674
+ if object.removed_at
675
+ raise ArgumentError, "#{object} has been removed at\n #{object.removed_at.join("\n ")}"
676
+ else
677
+ raise ArgumentError, "#{object} has not been included in this plan"
678
+ end
679
+ end
680
+ raise ArgumentError, "#{object} is not in #{self}: #plan == #{object.plan}"
681
+ end
682
+
683
+ # Remove relations first. This is needed by transaction since
684
+ # removing relations may need wrapping some new objects, and in
685
+ # that case these new objects will be discovered as well
686
+ object.clear_relations
687
+
688
+ @free_events.delete(object)
689
+ @missions.delete(object)
690
+ @known_tasks.delete(object)
691
+ @keepalive.delete(object)
692
+ force_gc.delete(object)
693
+
694
+ object.plan = nil
695
+ object.removed_at = caller
696
+
697
+ case object
698
+ when EventGenerator
699
+ finalized_event(object)
700
+
701
+ when Task
702
+ task_index.remove(object)
703
+
704
+ for ev in object.bound_events.values
705
+ task_events.delete(ev)
706
+ finalized_event(ev)
707
+ end
708
+ finalized_task(object)
709
+
710
+ else
711
+ raise ArgumentError, "unknown object type #{object}"
712
+ end
713
+
714
+ self
715
+ end
716
+
717
+ # Backward compatibility
718
+ def remove_task(t) # :nodoc:
719
+ remove_object(t)
720
+ end
721
+
722
+ # Hook called when +task+ is marked as garbage. It will be garbage
723
+ # collected as soon as possible
724
+ def garbage(task)
725
+ # Remove all signals that go *to* the task
726
+ #
727
+ # While we want events which come from the task to be properly
728
+ # forwarded, the signals that go to the task are to be ignored
729
+ if task.self_owned?
730
+ task.each_event do |ev|
731
+ ev.parent_objects(EventStructure::Signal).each do |signalling_event|
732
+ signalling_event.remove_signal ev
733
+ end
734
+ end
735
+ end
736
+
737
+ super if defined? super
738
+ end
739
+
740
+ # backward compatibility
741
+ def finalized(task) # :nodoc:
742
+ super if defined? super
743
+ end
744
+ # Hook called when +task+ has been removed from this plan
745
+ def finalized_task(task)
746
+ super if defined? super
747
+ finalized(task)
748
+ end
749
+ # Hook called when +event+ has been removed from this plan
750
+ def finalized_event(event); super if defined? super end
751
+
752
+ # Replace +task+ with a fresh copy of itself
753
+ def respawn(task)
754
+ new_task = task.class.new(task.arguments.dup)
755
+
756
+ replace_task(task, new_task)
757
+ Control.once { new_task.start!(nil) }
758
+ new_task
759
+ end
760
+ end
761
+ end
762
+