roby 0.7.3 → 0.8.0

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 (236) hide show
  1. data/History.txt +7 -5
  2. data/Manifest.txt +91 -16
  3. data/README.txt +24 -24
  4. data/Rakefile +92 -64
  5. data/app/config/app.yml +42 -43
  6. data/app/config/init.rb +26 -0
  7. data/benchmark/alloc_misc.rb +123 -0
  8. data/benchmark/discovery_latency.rb +67 -0
  9. data/benchmark/garbage_collection.rb +48 -0
  10. data/benchmark/genom.rb +31 -0
  11. data/benchmark/transactions.rb +62 -0
  12. data/bin/roby +1 -1
  13. data/bin/roby-log +16 -6
  14. data/doc/guide/.gitignore +2 -0
  15. data/doc/guide/config.yaml +34 -0
  16. data/doc/guide/ext/init.rb +14 -0
  17. data/doc/guide/ext/previous_next.rb +40 -0
  18. data/doc/guide/ext/rdoc_links.rb +33 -0
  19. data/doc/guide/index.rdoc +16 -0
  20. data/doc/guide/overview.rdoc +62 -0
  21. data/doc/guide/plan_modifications.rdoc +67 -0
  22. data/doc/guide/src/abstraction/achieve_with.page +8 -0
  23. data/doc/guide/src/abstraction/forwarding.page +8 -0
  24. data/doc/guide/src/abstraction/hierarchy.page +19 -0
  25. data/doc/guide/src/abstraction/index.page +28 -0
  26. data/doc/guide/src/abstraction/task_models.page +13 -0
  27. data/doc/guide/src/basics.template +6 -0
  28. data/doc/guide/src/basics/app.page +139 -0
  29. data/doc/guide/src/basics/code_examples.page +33 -0
  30. data/doc/guide/src/basics/dry.page +69 -0
  31. data/doc/guide/src/basics/errors.page +443 -0
  32. data/doc/guide/src/basics/events.page +179 -0
  33. data/doc/guide/src/basics/hierarchy.page +275 -0
  34. data/doc/guide/src/basics/index.page +11 -0
  35. data/doc/guide/src/basics/log_replay/goForward_1.png +0 -0
  36. data/doc/guide/src/basics/log_replay/goForward_2.png +0 -0
  37. data/doc/guide/src/basics/log_replay/goForward_3.png +0 -0
  38. data/doc/guide/src/basics/log_replay/goForward_4.png +0 -0
  39. data/doc/guide/src/basics/log_replay/goForward_5.png +0 -0
  40. data/doc/guide/src/basics/log_replay/hierarchy_error_1.png +0 -0
  41. data/doc/guide/src/basics/log_replay/hierarchy_error_2.png +0 -0
  42. data/doc/guide/src/basics/log_replay/hierarchy_error_3.png +0 -0
  43. data/doc/guide/src/basics/log_replay/plan_repair_1.png +0 -0
  44. data/doc/guide/src/basics/log_replay/plan_repair_2.png +0 -0
  45. data/doc/guide/src/basics/log_replay/plan_repair_3.png +0 -0
  46. data/doc/guide/src/basics/log_replay/plan_repair_4.png +0 -0
  47. data/doc/guide/src/basics/log_replay/roby_log_main_window.png +0 -0
  48. data/doc/guide/src/basics/log_replay/roby_log_relation_window.png +0 -0
  49. data/doc/guide/src/basics/log_replay/roby_replay_event_representation.png +0 -0
  50. data/doc/guide/src/basics/plan_objects.page +71 -0
  51. data/doc/guide/src/basics/relations_display.page +203 -0
  52. data/doc/guide/src/basics/roby_cycle_overview.png +0 -0
  53. data/doc/guide/src/basics/shell.page +102 -0
  54. data/doc/guide/src/basics/summary.page +32 -0
  55. data/doc/guide/src/basics/tasks.page +357 -0
  56. data/doc/guide/src/basics_shell_header.txt +16 -0
  57. data/doc/guide/src/cycle/cycle-overview.png +0 -0
  58. data/doc/guide/src/cycle/cycle-overview.svg +208 -0
  59. data/doc/guide/src/cycle/error_handling.page +168 -0
  60. data/doc/guide/src/cycle/error_instantaneous_repair.png +0 -0
  61. data/doc/guide/src/cycle/error_instantaneous_repair.svg +1224 -0
  62. data/doc/guide/src/cycle/garbage_collection.page +10 -0
  63. data/doc/guide/src/cycle/index.page +23 -0
  64. data/doc/guide/src/cycle/propagation.page +154 -0
  65. data/doc/guide/src/cycle/propagation_diamond.png +0 -0
  66. data/doc/guide/src/cycle/propagation_diamond.svg +1279 -0
  67. data/doc/guide/src/default.css +319 -0
  68. data/doc/guide/src/default.template +74 -0
  69. data/doc/guide/src/htmldoc.metainfo +20 -0
  70. data/doc/guide/src/htmldoc.virtual +18 -0
  71. data/doc/guide/src/images/bodybg.png +0 -0
  72. data/doc/guide/src/images/contbg.png +0 -0
  73. data/doc/guide/src/images/footerbg.png +0 -0
  74. data/doc/guide/src/images/gradient1.png +0 -0
  75. data/doc/guide/src/images/gradient2.png +0 -0
  76. data/doc/guide/src/index.page +7 -0
  77. data/doc/guide/src/introduction/index.page +29 -0
  78. data/doc/guide/src/introduction/install.page +133 -0
  79. data/doc/{papers.rdoc → guide/src/introduction/publications.page} +5 -2
  80. data/doc/{videos.rdoc → guide/src/introduction/videos.page} +4 -2
  81. data/doc/guide/src/plugins/fault_tolerance.page +44 -0
  82. data/doc/guide/src/plugins/index.page +11 -0
  83. data/doc/guide/src/plugins/subsystems.page +45 -0
  84. data/doc/guide/src/relations/dependency.page +89 -0
  85. data/doc/guide/src/relations/index.page +12 -0
  86. data/doc/misc/update_github +24 -0
  87. data/doc/tutorials/02-GoForward.rdoc +3 -3
  88. data/ext/graph/graph.cc +46 -0
  89. data/lib/roby.rb +57 -22
  90. data/lib/roby/app.rb +132 -112
  91. data/lib/roby/app/plugins/rake.rb +21 -0
  92. data/lib/roby/app/rake.rb +0 -7
  93. data/lib/roby/app/run.rb +1 -1
  94. data/lib/roby/app/scripts/distributed.rb +1 -2
  95. data/lib/roby/app/scripts/generate/bookmarks.rb +1 -1
  96. data/lib/roby/app/scripts/results.rb +2 -1
  97. data/lib/roby/app/scripts/run.rb +6 -2
  98. data/lib/roby/app/scripts/shell.rb +11 -11
  99. data/lib/roby/config.rb +1 -1
  100. data/lib/roby/decision_control.rb +62 -3
  101. data/lib/roby/distributed.rb +4 -0
  102. data/lib/roby/distributed/base.rb +8 -0
  103. data/lib/roby/distributed/communication.rb +12 -8
  104. data/lib/roby/distributed/connection_space.rb +61 -44
  105. data/lib/roby/distributed/distributed_object.rb +1 -1
  106. data/lib/roby/distributed/notifications.rb +22 -30
  107. data/lib/roby/distributed/peer.rb +13 -8
  108. data/lib/roby/distributed/proxy.rb +5 -5
  109. data/lib/roby/distributed/subscription.rb +4 -4
  110. data/lib/roby/distributed/transaction.rb +3 -3
  111. data/lib/roby/event.rb +176 -110
  112. data/lib/roby/exceptions.rb +12 -4
  113. data/lib/roby/execution_engine.rb +1604 -0
  114. data/lib/roby/external_process_task.rb +225 -0
  115. data/lib/roby/graph.rb +0 -6
  116. data/lib/roby/interface.rb +221 -137
  117. data/lib/roby/log/console.rb +5 -3
  118. data/lib/roby/log/data_stream.rb +94 -16
  119. data/lib/roby/log/dot.rb +8 -8
  120. data/lib/roby/log/event_stream.rb +13 -3
  121. data/lib/roby/log/file.rb +43 -18
  122. data/lib/roby/log/gui/basic_display_ui.rb +89 -0
  123. data/lib/roby/log/gui/chronicle_view_ui.rb +90 -0
  124. data/lib/roby/log/gui/data_displays.rb +4 -5
  125. data/lib/roby/log/gui/data_displays_ui.rb +146 -0
  126. data/lib/roby/log/gui/relations.rb +18 -18
  127. data/lib/roby/log/gui/relations_ui.rb +120 -0
  128. data/lib/roby/log/gui/relations_view_ui.rb +144 -0
  129. data/lib/roby/log/gui/replay.rb +41 -13
  130. data/lib/roby/log/gui/replay_controls.rb +3 -0
  131. data/lib/roby/log/gui/replay_controls.ui +133 -110
  132. data/lib/roby/log/gui/replay_controls_ui.rb +249 -0
  133. data/lib/roby/log/hooks.rb +19 -18
  134. data/lib/roby/log/logger.rb +7 -6
  135. data/lib/roby/log/notifications.rb +4 -4
  136. data/lib/roby/log/plan_rebuilder.rb +20 -22
  137. data/lib/roby/log/relations.rb +44 -16
  138. data/lib/roby/log/server.rb +1 -4
  139. data/lib/roby/log/timings.rb +88 -19
  140. data/lib/roby/plan-object.rb +135 -11
  141. data/lib/roby/plan.rb +408 -224
  142. data/lib/roby/planning/loops.rb +32 -25
  143. data/lib/roby/planning/model.rb +157 -51
  144. data/lib/roby/planning/task.rb +47 -20
  145. data/lib/roby/query.rb +128 -92
  146. data/lib/roby/relations.rb +254 -136
  147. data/lib/roby/relations/conflicts.rb +6 -9
  148. data/lib/roby/relations/dependency.rb +358 -0
  149. data/lib/roby/relations/ensured.rb +0 -1
  150. data/lib/roby/relations/error_handling.rb +0 -1
  151. data/lib/roby/relations/events.rb +0 -2
  152. data/lib/roby/relations/executed_by.rb +26 -11
  153. data/lib/roby/relations/planned_by.rb +14 -14
  154. data/lib/roby/robot.rb +46 -0
  155. data/lib/roby/schedulers/basic.rb +34 -0
  156. data/lib/roby/standalone.rb +4 -0
  157. data/lib/roby/standard_errors.rb +21 -15
  158. data/lib/roby/state/events.rb +5 -4
  159. data/lib/roby/support.rb +107 -6
  160. data/lib/roby/task-operations.rb +23 -19
  161. data/lib/roby/task.rb +522 -148
  162. data/lib/roby/task_index.rb +80 -0
  163. data/lib/roby/test/common.rb +283 -44
  164. data/lib/roby/test/distributed.rb +53 -37
  165. data/lib/roby/test/testcase.rb +9 -204
  166. data/lib/roby/test/tools.rb +3 -3
  167. data/lib/roby/transactions.rb +154 -111
  168. data/lib/roby/transactions/proxy.rb +40 -7
  169. data/manifest.xml +20 -0
  170. data/plugins/fault_injection/README.txt +0 -3
  171. data/plugins/fault_injection/Rakefile +2 -8
  172. data/plugins/fault_injection/app.rb +1 -1
  173. data/plugins/fault_injection/fault_injection.rb +3 -3
  174. data/plugins/fault_injection/test/test_fault_injection.rb +19 -25
  175. data/plugins/subsystems/README.txt +0 -3
  176. data/plugins/subsystems/Rakefile +2 -7
  177. data/plugins/subsystems/app.rb +27 -16
  178. data/plugins/subsystems/test/app/config/init.rb +3 -0
  179. data/plugins/subsystems/test/app/planners/main.rb +1 -1
  180. data/plugins/subsystems/test/app/tasks/services.rb +1 -1
  181. data/plugins/subsystems/test/test_subsystems.rb +23 -16
  182. data/test/distributed/test_communication.rb +32 -15
  183. data/test/distributed/test_connection.rb +28 -26
  184. data/test/distributed/test_execution.rb +59 -54
  185. data/test/distributed/test_mixed_plan.rb +34 -34
  186. data/test/distributed/test_plan_notifications.rb +26 -26
  187. data/test/distributed/test_protocol.rb +57 -48
  188. data/test/distributed/test_query.rb +11 -7
  189. data/test/distributed/test_remote_plan.rb +71 -71
  190. data/test/distributed/test_transaction.rb +50 -47
  191. data/test/mockups/external_process +28 -0
  192. data/test/planning/test_loops.rb +163 -119
  193. data/test/planning/test_model.rb +3 -3
  194. data/test/planning/test_task.rb +27 -7
  195. data/test/relations/test_conflicts.rb +3 -3
  196. data/test/relations/test_dependency.rb +324 -0
  197. data/test/relations/test_ensured.rb +2 -2
  198. data/test/relations/test_executed_by.rb +94 -19
  199. data/test/relations/test_planned_by.rb +11 -9
  200. data/test/suite_core.rb +6 -3
  201. data/test/suite_distributed.rb +1 -0
  202. data/test/suite_planning.rb +1 -0
  203. data/test/suite_relations.rb +2 -2
  204. data/test/tasks/test_external_process.rb +126 -0
  205. data/test/{test_thread_task.rb → tasks/test_thread_task.rb} +17 -20
  206. data/test/test_bgl.rb +21 -1
  207. data/test/test_event.rb +229 -155
  208. data/test/test_exceptions.rb +79 -80
  209. data/test/test_execution_engine.rb +987 -0
  210. data/test/test_gui.rb +1 -1
  211. data/test/test_interface.rb +11 -5
  212. data/test/test_log.rb +18 -7
  213. data/test/test_log_server.rb +1 -0
  214. data/test/test_plan.rb +229 -395
  215. data/test/test_query.rb +193 -35
  216. data/test/test_relations.rb +88 -8
  217. data/test/test_state.rb +55 -37
  218. data/test/test_support.rb +1 -1
  219. data/test/test_task.rb +371 -218
  220. data/test/test_testcase.rb +32 -16
  221. data/test/test_transactions.rb +211 -170
  222. data/test/test_transactions_proxy.rb +37 -19
  223. metadata +169 -71
  224. data/.gitignore +0 -29
  225. data/doc/styles/allison.css +0 -314
  226. data/doc/styles/allison.js +0 -316
  227. data/doc/styles/allison.rb +0 -276
  228. data/doc/styles/jamis.rb +0 -593
  229. data/lib/roby/control.rb +0 -746
  230. data/lib/roby/executives/simple.rb +0 -30
  231. data/lib/roby/propagation.rb +0 -562
  232. data/lib/roby/relations/hierarchy.rb +0 -239
  233. data/lib/roby/transactions/updates.rb +0 -139
  234. data/test/relations/test_hierarchy.rb +0 -158
  235. data/test/test_control.rb +0 -399
  236. data/test/test_propagation.rb +0 -210
@@ -1,746 +0,0 @@
1
- require 'roby'
2
- require 'utilrb/exception/full_message'
3
-
4
- require 'drb'
5
- require 'set'
6
-
7
- module Roby
8
- class Pool < Queue
9
- def initialize(klass)
10
- @klass = klass
11
- super()
12
- end
13
-
14
- def pop
15
- value = super(true) rescue nil
16
- value || @klass.new
17
- end
18
- end
19
-
20
- @mutexes = Pool.new(Mutex)
21
- @condition_variables = Pool.new(ConditionVariable)
22
- class << self
23
- # Returns the only one Control object
24
- attr_reader :control
25
- # Returns the executed plan. This is equivalent to
26
- # Roby.control.plan
27
- attr_reader :plan
28
-
29
- def every(duration, &block)
30
- Control.every(duration, &block)
31
- end
32
- def each_cycle(&block)
33
- Control.each_cycle(&block)
34
- end
35
-
36
- # Returns the control thread or, if control is not in a separate
37
- # thread, Thread.main
38
- def control_thread
39
- Control.instance.thread || Thread.main
40
- end
41
-
42
- # True if the current thread is the control thread
43
- #
44
- # See #outside_control? for a discussion of the use of #inside_control?
45
- # and #outside_control? when testing the threading context
46
- def inside_control?
47
- t = Control.instance.thread
48
- !t || t == Thread.current
49
- end
50
-
51
- # True if the current thread is not control thread, or if
52
- # there is not control thread. When you check the current
53
- # thread context, always use a negated form. Do not do
54
- #
55
- # if Roby.inside_control?
56
- # ERROR
57
- # end
58
- #
59
- # Do instead
60
- #
61
- # if !Roby.outside_control?
62
- # ERROR
63
- # end
64
- #
65
- # Since the first form will fail if there is no control thread, while
66
- # the second form will work. Use the first form only if you require
67
- # that there actually IS a control thread.
68
- def outside_control?
69
- t = Control.instance.thread
70
- !t || t != Thread.current
71
- end
72
-
73
- # A pool of mutexes (as a Queue)
74
- attr_reader :mutexes
75
- # A pool of condition variables (as a Queue)
76
- attr_reader :condition_variables
77
-
78
- # call-seq:
79
- # condition_variable => cv
80
- # condition_variable(true) => cv, mutex
81
- # condition_variable { |cv| ... } => value returned by the block
82
- # condition_variable(true) { |cv, mutex| ... } => value returned by the block
83
- #
84
- # Get a condition variable object from the Roby.condition_variables
85
- # pool and, if mutex is not true, a Mutex object
86
- #
87
- # If a block is given, the two objects are yield and returned into the
88
- # pool after the block has returned. In that case, the method returns
89
- # the value returned by the block
90
- def condition_variable(mutex = false)
91
- cv = condition_variables.pop
92
-
93
- if block_given?
94
- begin
95
- if mutex
96
- mt = mutexes.pop
97
- yield(cv, mt)
98
- else
99
- yield(cv)
100
- end
101
-
102
- ensure
103
- return_condition_variable(cv, mt)
104
- end
105
- else
106
- if mutex
107
- return cv, mutexes.pop
108
- else
109
- return cv
110
- end
111
- end
112
- end
113
-
114
- # Execute the given block inside the control thread, and returns when
115
- # it has finished. The return value is the value returned by the block
116
- def execute
117
- if Roby.inside_control?
118
- return Roby::Control.synchronize { yield }
119
- end
120
-
121
- cv = condition_variable
122
-
123
- return_value = nil
124
- Roby::Control.synchronize do
125
- if !Roby.control.running?
126
- raise "control thread not running"
127
- end
128
-
129
- caller_thread = Thread.current
130
- Control.waiting_threads << caller_thread
131
-
132
- Roby::Control.once do
133
- begin
134
- return_value = yield
135
- cv.broadcast
136
- rescue Exception => e
137
- caller_thread.raise e, e.message, e.backtrace
138
- end
139
- Control.waiting_threads.delete(caller_thread)
140
- end
141
- cv.wait(Roby::Control.mutex)
142
- end
143
- return_value
144
-
145
- ensure
146
- return_condition_variable(cv)
147
- end
148
-
149
- # Execute the given block in the control thread, but don't wait for its
150
- # completion like Roby.execute does
151
- def once
152
- Roby::Control.once { yield }
153
- end
154
- def wait_one_cycle
155
- Roby.control.wait_one_cycle
156
- end
157
-
158
- # Stops the current thread until the given even is emitted
159
- def wait_until(ev)
160
- if Roby.inside_control?
161
- raise ThreadMismatch, "cannot use #wait_until in control thread"
162
- end
163
-
164
- condition_variable(true) do |cv, mt|
165
- caller_thread = Thread.current
166
-
167
- mt.synchronize do
168
- Roby::Control.once do
169
- ev.if_unreachable(true) do |reason|
170
- caller_thread.raise UnreachableEvent.new(ev, reason)
171
- end
172
- ev.on do
173
- mt.synchronize { cv.broadcast }
174
- end
175
- yield
176
- end
177
- cv.wait(mt)
178
- end
179
- end
180
- end
181
-
182
- # Returns a ConditionVariable and optionally a Mutex into the
183
- # Roby.condition_variables and Roby.mutexes pools
184
- def return_condition_variable(cv, mutex = nil)
185
- condition_variables.push cv
186
- if mutex
187
- mutexes.push mutex
188
- end
189
- nil
190
- end
191
- end
192
-
193
- # This singleton class is the central object: it handles the event loop,
194
- # event propagation and exception propagation.
195
- class Control
196
- include Singleton
197
-
198
- @mutex = Mutex.new
199
- class << self
200
- attr_reader :mutex
201
-
202
- def taken_mutex?; Thread.current[:control_mutex_locked] end
203
-
204
- # Implements a recursive behaviour on Control.mutex
205
- def synchronize
206
- if Thread.current[:control_mutex_locked]
207
- yield
208
- else
209
- mutex.lock
210
- begin
211
- Thread.current[:control_mutex_locked] = true
212
- yield
213
- ensure
214
- Thread.current[:control_mutex_locked] = false
215
- mutex.unlock
216
- end
217
- end
218
- end
219
- end
220
-
221
- # Do not sleep or call Thread#pass if there is less that
222
- # this much time left in the cycle
223
- SLEEP_MIN_TIME = 0.01
224
-
225
- # The priority of the control thread
226
- THREAD_PRIORITY = 10
227
-
228
- # If true, abort if an unhandled exception is found
229
- attr_accessor :abort_on_exception
230
- # If true, abort if an application exception is found
231
- attr_accessor :abort_on_application_exception
232
- # If true, abort if a framework exception is found
233
- attr_accessor :abort_on_framework_exception
234
-
235
- @event_processing = []
236
- @structure_checks = []
237
- class << self
238
- # List of procs which are called at each event cycle
239
- attr_reader :event_processing
240
-
241
- # List of procs to be called for task structure checking
242
- #
243
- # The blocks return a set of exceptions or nil. The exception
244
- # *must* respond to #task or #generator to know from which task the
245
- # problem comes.
246
- attr_reader :structure_checks
247
- end
248
-
249
- # The plan being executed
250
- attr_reader :plan
251
- # A set of planners declared in this application
252
- attr_reader :planners
253
-
254
- def initialize
255
- super
256
- @quit = 0
257
- @thread = nil
258
- @cycle_index = 0
259
- @cycle_start = Time.now
260
- @cycle_length = 0
261
- @planners = []
262
- @last_stop_count = 0
263
- @plan = Plan.new
264
- Roby.instance_variable_set(:@plan, @plan)
265
- plan.extend Roby::Propagation::ExecutablePlanChanged
266
- end
267
-
268
- # Perform the structure checking step by calling the procs registered
269
- # in Control::structure_checks. These procs are supposed to return a
270
- # collection of exception objects, or nil if no error has been found
271
- def structure_checking
272
- # Do structure checking and gather the raised exceptions
273
- exceptions = {}
274
- for prc in Control.structure_checks
275
- begin
276
- new_exceptions = prc.call(plan)
277
- rescue Exception => e
278
- Propagation.add_framework_error(e, 'structure checking')
279
- end
280
- next unless new_exceptions
281
-
282
- [*new_exceptions].each do |e, tasks|
283
- e = Propagation.to_execution_exception(e)
284
- exceptions[e] = tasks
285
- end
286
- end
287
- exceptions
288
- end
289
-
290
- # Abort the control loop because of +exceptions+
291
- def reraise(exceptions)
292
- if exceptions.size == 1
293
- e = exceptions.first
294
- if e.kind_of?(ExecutionException)
295
- e = e.exception
296
- end
297
- raise e, e.message, e.backtrace
298
- else
299
- raise Aborting.new(exceptions)
300
- end
301
- end
302
-
303
- # Process the pending events. The time at each event loop step
304
- # is saved into +stats+.
305
- def process_events(stats = {})
306
- Thread.current[:application_exceptions] = []
307
-
308
- add_timepoint(stats, :real_start)
309
-
310
- # Gather new events and propagate them
311
- events_errors = Propagation.propagate_events(Control.event_processing)
312
- add_timepoint(stats, :events)
313
-
314
- # HACK: events_errors is sometime nil here. It shouldn't
315
- events_errors ||= []
316
-
317
- # Generate exceptions from task structure
318
- structure_errors = structure_checking
319
- add_timepoint(stats, :structure_check)
320
-
321
- # Propagate the errors. Note that the plan repairs are taken into
322
- # account in Propagation.propagate_exceptions drectly. We keep
323
- # event and structure errors separate since in the first case there
324
- # is not two-stage handling (all errors that have not been handled
325
- # are fatal), and in the second case we call #structure_checking
326
- # again to get the remaining errors
327
- events_errors = Propagation.propagate_exceptions(events_errors)
328
- Propagation.propagate_exceptions(structure_errors)
329
- add_timepoint(stats, :exception_propagation)
330
-
331
- # Get the remaining problems in the plan structure, and act on it
332
- fatal_structure_errors = Propagation.remove_inhibited_exceptions(structure_checking)
333
- fatal_errors = fatal_structure_errors.to_a + events_errors
334
- kill_tasks = fatal_errors.inject(ValueSet.new) do |kill_tasks, (error, tasks)|
335
- tasks ||= [*error.task]
336
- for parent in [*tasks]
337
- new_tasks = parent.reverse_generated_subgraph(TaskStructure::Hierarchy) - plan.force_gc
338
- if !new_tasks.empty?
339
- Control.fatal_exception(error, new_tasks)
340
- end
341
- kill_tasks.merge(new_tasks)
342
- end
343
- kill_tasks
344
- end
345
- add_timepoint(stats, :exceptions_fatal)
346
-
347
- plan.garbage_collect(kill_tasks)
348
- add_timepoint(stats, :garbage_collect)
349
-
350
- application_errors = Thread.current[:application_exceptions]
351
- Thread.current[:application_exceptions] = nil
352
- for error, origin in application_errors
353
- Propagation.add_framework_error(error, origin)
354
- end
355
- add_timepoint(stats, :application_errors)
356
-
357
- if abort_on_exception && !quitting? && !fatal_errors.empty?
358
- reraise(fatal_errors.map { |e, _| e })
359
- end
360
-
361
- ensure
362
- Thread.current[:application_exceptions] = nil
363
- end
364
-
365
- # Blocks until at least once execution cycle has been done
366
- def wait_one_cycle
367
- current_cycle = Roby.execute { Roby.control.cycle_index }
368
- while current_cycle == Roby.execute { Roby.control.cycle_index }
369
- raise ControlQuitError if !Roby.control.running?
370
- sleep(Roby.control.cycle_length)
371
- end
372
- end
373
-
374
- @process_once = Queue.new
375
- @at_cycle_end_handlers = Array.new
376
- @process_every = Array.new
377
- @waiting_threads = Array.new
378
- class << self
379
- # A list of threads which are currently waitiing for the control thread
380
- # (see for instance Roby.execute)
381
- #
382
- # Control#run will raise ControlQuitError on this threads if they
383
- # are still waiting while the control is quitting
384
- attr_reader :waiting_threads
385
- # A list of blocks to be called at the beginning of the next event loop
386
- attr_reader :process_once
387
- # Calls all pending procs in +process_once+
388
- def call_once # :nodoc:
389
- while !process_once.empty?
390
- p = process_once.pop
391
- begin
392
- p.call
393
- rescue Exception => e
394
- Propagation.add_framework_error(e, "call once in #{p}")
395
- end
396
- end
397
- end
398
- Control.event_processing << Control.method(:call_once)
399
-
400
- # Call block once before event processing
401
- def once(&block); process_once.push block end
402
- # Call +block+ at each cycle
403
- def each_cycle(&block); Control.event_processing << block end
404
-
405
- # A set of blocks that are called at each cycle end
406
- attr_reader :at_cycle_end_handlers
407
-
408
- # Call +block+ at the end of the execution cycle
409
- def at_cycle_end(&block)
410
- Control.at_cycle_end_handlers << block
411
- end
412
-
413
- # A set of blocks which are called every cycle
414
- attr_reader :process_every
415
-
416
- # Call +block+ every +duration+ seconds. Note that +duration+ is
417
- # round up to the cycle size (time between calls is *at least* duration)
418
- def every(duration, &block)
419
- Control.once do
420
- block.call
421
- process_every << [block, Roby.control.cycle_start, duration]
422
- end
423
- block.object_id
424
- end
425
-
426
- def remove_periodic_handler(id)
427
- Roby.execute do
428
- process_every.delete_if { |spec| spec[0].object_id == id }
429
- end
430
- end
431
-
432
- def call_every # :nodoc:
433
- now = Roby.control.cycle_start
434
- length = Roby.control.cycle_length
435
- process_every.map! do |block, last_call, duration|
436
- begin
437
- # Check if the nearest timepoint is the beginning of
438
- # this cycle or of the next cycle
439
- if !last_call || (duration - (now - last_call)) < length / 2
440
- block.call
441
- last_call = now
442
- end
443
- rescue Exception => e
444
- Propagation.add_framework_error(e, "#call_every, in #{block}")
445
- end
446
- [block, last_call, duration]
447
- end
448
- end
449
- Control.event_processing << Control.method(:call_every)
450
- end
451
-
452
-
453
- attr_accessor :thread
454
- def running?; !!@thread end
455
-
456
- # The cycle length in seconds
457
- attr_reader :cycle_length
458
-
459
- # The starting point of this cycle
460
- attr_reader :cycle_start
461
-
462
- # The number of this cycle since the beginning
463
- attr_reader :cycle_index
464
-
465
- # Main event loop. Valid options are
466
- # cycle:: the cycle duration in seconds (default: 0.1)
467
- # drb:: address of the DRuby server if one should be started (default: nil)
468
- # detach:: if true, start in its own thread (default: false)
469
- def run(options = {})
470
- if running?
471
- raise "there is already a control running in thread #{@thread}"
472
- end
473
-
474
- options = validate_options options,
475
- :cycle => 0.1, :detach => false
476
-
477
- @quit = 0
478
- if !options[:detach]
479
- @thread = Thread.current
480
- @thread.priority = THREAD_PRIORITY
481
- end
482
-
483
- if options[:detach]
484
- # Start the control thread and wait for @thread to be set
485
- Roby.condition_variable(true) do |cv, mt|
486
- mt.synchronize do
487
- Thread.new do
488
- run(options.merge(:detach => false)) do
489
- mt.synchronize { cv.signal }
490
- end
491
- end
492
- cv.wait(mt)
493
- end
494
- end
495
- return
496
- end
497
-
498
- yield if block_given?
499
-
500
- @cycle_length = options[:cycle]
501
- event_loop
502
-
503
- ensure
504
- if Thread.current == self.thread
505
- Roby::Control.synchronize do
506
- # reset the options only if we are in the control thread
507
- @thread = nil
508
- Control.waiting_threads.each do |th|
509
- th.raise ControlQuitError
510
- end
511
- Control.finalizers.each { |blk| blk.call rescue nil }
512
- @quit = 0
513
- end
514
- end
515
- end
516
-
517
- attr_reader :last_stop_count
518
- def clear
519
- Control.synchronize do
520
- plan.missions.dup.each { |t| plan.discard(t) }
521
- plan.keepalive.dup.each { |t| plan.auto(t) }
522
- plan.force_gc.merge( plan.known_tasks )
523
-
524
- quaranteened_subplan = plan.useful_task_component(nil, ValueSet.new, plan.gc_quarantine.dup)
525
- remaining = plan.known_tasks - quaranteened_subplan
526
-
527
- if remaining.empty?
528
- # Have to call #garbage_collect one more to make
529
- # sure that unneeded events are removed as well
530
- plan.garbage_collect
531
- # Done cleaning the tasks, clear the remains
532
- plan.transactions.each do |trsc|
533
- trsc.discard_transaction if trsc.self_owned?
534
- end
535
- plan.clear
536
- return
537
- end
538
-
539
- if last_stop_count != remaining.size
540
- if last_stop_count == 0
541
- Roby.info "control quitting. Waiting for #{remaining.size} tasks to finish (#{plan.size} tasks still in plan)"
542
- Roby.debug " " + remaining.to_a.join("\n ")
543
- else
544
- Roby.info "waiting for #{remaining.size} tasks to finish (#{plan.size} tasks still in plan)"
545
- Roby.debug " #{remaining.to_a.join("\n ")}"
546
- end
547
- if plan.gc_quarantine.size != 0
548
- Roby.info "#{plan.gc_quarantine.size} tasks in quarantine"
549
- end
550
- @last_stop_count = remaining.size
551
- end
552
- remaining
553
- end
554
- end
555
-
556
- attr_reader :remaining_cycle_time
557
- def add_timepoint(stats, name)
558
- stats[:end] = stats[name] = Time.now - cycle_start
559
- @remaining_cycle_time = cycle_length - stats[:end]
560
- end
561
- def add_expected_duration(stats, name, duration)
562
- stats[name] = Time.now + duration - cycle_start
563
- end
564
-
565
- def event_loop
566
- @last_stop_count = 0
567
- @cycle_start = Time.now
568
- @cycle_index = 0
569
-
570
- gc_enable_has_argument = begin
571
- GC.enable(true)
572
- true
573
- rescue; false
574
- end
575
- stats = Hash.new
576
- if ObjectSpace.respond_to?(:live_objects)
577
- stats[:live_objects] = ObjectSpace.live_objects
578
- end
579
-
580
- GC.start
581
- if gc_enable_has_argument
582
- already_disabled_gc = GC.disable
583
- end
584
- loop do
585
- begin
586
- if quitting?
587
- thread.priority = 0
588
- begin
589
- return if forced_exit? || !clear
590
- rescue Exception => e
591
- Roby.warn "Control failed to clean up"
592
- Roby.format_exception(e).each do |line|
593
- Roby.warn line
594
- end
595
- return
596
- end
597
- end
598
-
599
- while Time.now > cycle_start + cycle_length
600
- @cycle_start += cycle_length
601
- @cycle_index += 1
602
- end
603
- stats[:start] = [cycle_start.tv_sec, cycle_start.tv_usec]
604
- stats[:cycle_index] = cycle_index
605
- Control.synchronize { process_events(stats) }
606
-
607
- # Record the statistics about object allocation *before* running the Ruby
608
- # GC. It is also updated at
609
- if ObjectSpace.respond_to?(:live_objects)
610
- live_objects_before_gc = ObjectSpace.live_objects
611
- end
612
-
613
- # If the ruby interpreter we run on offers a true/false argument to
614
- # GC.enable, we disabled the GC and just run GC.enable(true) to make
615
- # it run immediately if needed. Then, we re-disable it just after.
616
- if gc_enable_has_argument && remaining_cycle_time > SLEEP_MIN_TIME
617
- GC.enable(true)
618
- GC.disable
619
- end
620
- add_timepoint(stats, :ruby_gc)
621
-
622
- if ObjectSpace.respond_to?(:live_objects)
623
- live_objects_after_gc = ObjectSpace.live_objects
624
- end
625
-
626
- # Sleep if there is enough time for it
627
- if remaining_cycle_time > SLEEP_MIN_TIME
628
- add_expected_duration(stats, :sleep, remaining_cycle_time)
629
- sleep(remaining_cycle_time)
630
- end
631
- add_timepoint(stats, :sleep)
632
-
633
- # Add some statistics and call cycle_end
634
- if defined? Roby::Log
635
- stats[:log_queue_size] = Roby::Log.logged_events.size
636
- end
637
- stats[:plan_task_count] = plan.known_tasks.size
638
- stats[:plan_event_count] = plan.free_events.size
639
- process_time = Process.times
640
- stats[:cpu_time] = (process_time.utime + process_time.stime) * 1000
641
-
642
- if ObjectSpace.respond_to?(:live_objects)
643
- live_objects = ObjectSpace.live_objects
644
- stats[:object_allocation] = live_objects - stats[:live_objects] - (live_objects_after_gc - live_objects_before_gc)
645
- stats[:live_objects] = live_objects
646
- end
647
-
648
- cycle_end(stats)
649
-
650
- stats = Hash.new
651
- stats[:live_objects] = live_objects
652
- @cycle_start += cycle_length
653
- @cycle_index += 1
654
-
655
- rescue Exception => e
656
- Roby.warn "Control quitting because of unhandled exception"
657
- Roby.format_exception(e).each do |line|
658
- Roby.warn line
659
- end
660
- quit
661
- end
662
- end
663
-
664
- ensure
665
- GC.enable if !already_disabled_gc
666
-
667
- if !plan.known_tasks.empty?
668
- Roby.warn "the following tasks are still present in the plan:"
669
- plan.known_tasks.each do |t|
670
- Roby.warn " #{t}"
671
- end
672
- end
673
- end
674
-
675
- @finalizers = []
676
- def self.finalizers; @finalizers end
677
-
678
- # True if the control thread is currently quitting
679
- def quitting?; @quit > 0 end
680
- # True if the control thread is currently quitting
681
- def forced_exit?; @quit > 1 end
682
- # Make control quit
683
- def quit; @quit += 1 end
684
-
685
- # Called at each cycle end
686
- def cycle_end(stats)
687
- super if defined? super
688
-
689
- Control.at_cycle_end_handlers.each do |handler|
690
- begin
691
- handler.call
692
- rescue Exception => e
693
- Propagation.add_framework_error(e, "during cycle end handler #{handler}")
694
- end
695
- end
696
- end
697
-
698
- # If the event thread has been started in its own thread,
699
- # wait for it to terminate
700
- def join
701
- thread.join if thread
702
-
703
- rescue Interrupt
704
- Roby::Control.synchronize do
705
- return unless thread
706
-
707
- Roby.logger.level = Logger::INFO
708
- Roby.warn "received interruption request"
709
- quit
710
- if @quit > 2
711
- thread.raise Interrupt, "interrupting control thread at user request"
712
- end
713
- end
714
-
715
- retry
716
- end
717
-
718
- attr_reader :cycle_index
719
-
720
- # Hook called when a set of tasks is being killed because of an exception
721
- def self.fatal_exception(error, tasks)
722
- super if defined? super
723
- Roby.format_exception(error.exception).each do |line|
724
- Roby.warn line
725
- end
726
- end
727
- # Hook called when an exception +e+ has been handled by +task+
728
- def self.handled_exception(e, task); super if defined? super end
729
- end
730
-
731
- # Get all missions that have failed
732
- def self.check_failed_missions(plan)
733
- result = []
734
- for task in plan.missions
735
- result << MissionFailedError.new(task) if task.failed?
736
- end
737
- result
738
- end
739
- Control.structure_checks << method(:check_failed_missions)
740
- end
741
-
742
- require 'roby/propagation'
743
-
744
- module Roby
745
- @control = Control.instance
746
- end