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/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
+