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
@@ -0,0 +1,151 @@
1
+ require 'roby/task'
2
+ require 'roby/relations/planned_by'
3
+ require 'roby/control'
4
+ require 'roby/transactions'
5
+
6
+ module Roby
7
+ # An asynchronous planning task using Ruby threads
8
+ class PlanningTask < Roby::Task
9
+ attr_reader :planner, :transaction
10
+
11
+ arguments :planner_model, :method_name,
12
+ :method_options, :planned_model,
13
+ :planning_owners
14
+
15
+ def self.filter_options(options)
16
+ task_options, method_options = Kernel.filter_options options,
17
+ :planner_model => nil,
18
+ :method_name => nil,
19
+ :method_options => {},
20
+ :planned_model => nil,
21
+ :planning_owners => nil
22
+
23
+ if !task_options[:planner_model]
24
+ raise ArgumentError, "missing required argument 'planner_model'"
25
+ elsif !task_options[:method_name]
26
+ raise ArgumentError, "missing required argument 'method_name'"
27
+ end
28
+ task_options[:planned_model] ||= nil
29
+ task_options[:method_options] ||= Hash.new
30
+ task_options[:method_options].merge! method_options
31
+ task_options[:planning_owners] ||= nil
32
+ task_options
33
+ end
34
+
35
+ def initialize(options)
36
+ task_options = PlanningTask.filter_options(options)
37
+ super(task_options)
38
+ end
39
+
40
+ def planned_model
41
+ arguments[:planned_model] ||= planner_model.model_of(method_name, method_options).returns || Roby::Task
42
+ end
43
+
44
+
45
+ def to_s
46
+ "#{super}[#{method_name}:#{method_options}] -> #{@planned_task || "nil"}"
47
+ end
48
+
49
+ def planned_task
50
+ task = planned_tasks.find { true }
51
+ if !task && pending?
52
+ task = planned_model.new
53
+ task.planned_by self
54
+ task.executable = false
55
+ end
56
+
57
+ task
58
+ end
59
+
60
+ # The thread that is running the planner
61
+ attr_reader :thread
62
+ # The transaction in which we build the new plan. It gets committed on
63
+ # success.
64
+ attr_reader :transaction
65
+ # The planner result. It is either an exception or a task object
66
+ attr_reader :result
67
+
68
+ # Starts planning
69
+ event :start do |context|
70
+ emit :start
71
+
72
+ if planning_owners
73
+ @transaction = Distributed::Transaction.new(plan)
74
+ planning_owners.each do |peer|
75
+ transaction.add_owner peer
76
+ end
77
+ else
78
+ @transaction = Transaction.new(plan)
79
+ end
80
+ @planner = planner_model.new(transaction)
81
+
82
+ @thread = Thread.new do
83
+ Thread.current.priority = 0
84
+ planning_thread(context)
85
+ end
86
+ end
87
+
88
+ def planning_thread(context)
89
+ result_task = planner.send(method_name, method_options.merge(:context => context))
90
+
91
+ # Don't replace the planning task with ourselves if the
92
+ # transaction specifies another planning task
93
+ if !result_task.planning_task
94
+ result_task.planned_by transaction[self]
95
+ end
96
+
97
+ if placeholder = planned_task
98
+ placeholder = transaction[placeholder]
99
+ transaction.replace(placeholder, result_task)
100
+ placeholder.remove_planning_task transaction[self]
101
+ end
102
+
103
+ # If the transaction is distributed, and is not proposed to all
104
+ # owners, do it
105
+ transaction.propose
106
+ transaction.commit_transaction
107
+
108
+ @result = result_task
109
+ end
110
+
111
+ # Polls for the planning thread end
112
+ poll do
113
+ if thread.alive?
114
+ return
115
+ end
116
+
117
+ # Check if the transaction has been committed. If it is not the
118
+ # case, assume that the thread failed
119
+ if transaction.freezed?
120
+ emit :success
121
+ else
122
+ error = begin
123
+ thread.value
124
+ rescue Exception => e
125
+ @result = e
126
+ end
127
+
128
+ transaction.discard_transaction
129
+ emit :failed, error
130
+ end
131
+ end
132
+
133
+ # Stops the planning thread
134
+ event :stop do |context|
135
+ planner.stop
136
+ end
137
+ on :stop do
138
+ @transaction = nil
139
+ @planner = nil
140
+ @thread = nil
141
+ end
142
+
143
+ class TransactionProxy < Roby::Transactions::Task
144
+ proxy_for PlanningTask
145
+ def_delegator :@__getobj__, :planner
146
+ def_delegator :@__getobj__, :method_name
147
+ def_delegator :@__getobj__, :method_options
148
+ end
149
+ end
150
+ end
151
+
@@ -0,0 +1,562 @@
1
+ require 'roby/support'
2
+ require 'roby/exceptions'
3
+ require 'utilrb/exception/full_message'
4
+ require 'utilrb/unbound_method'
5
+ require 'roby/relations/error_handling'
6
+
7
+ # This module contains all code necessary for the propagation steps during
8
+ # execution. This includes event and exception propagation
9
+ #
10
+ # == Event propagation
11
+ # Event propagation is based on three event relations:
12
+ #
13
+ # * Signal describes the commands that must be called when an event occurs. The
14
+ # signalled event command is called when the signalling events are emitted. If
15
+ # more than one event are signalling the same event in the same execution
16
+ # cycle, the command will be called only once
17
+ # * Forwarding describes the events that must be emitted whenever a source
18
+ # event is. It is to be used as a way to define event aliases (for instance
19
+ # 'stop' is an alias for 'success'), because a task is stopped when it has
20
+ # finished with success. Unlike with signals, if more than one event is
21
+ # forwarded to the same event in the same cycle, the target event will be
22
+ # emitted as many times as the incoming events.
23
+ # * Precedence is a graph which constrains the order in which propagation is
24
+ # done. If there is a a => b edge in Precedence, and if both events are either
25
+ # called and/or forwarded in the same cycle, then 'a' will be propagated before
26
+ # 'b'. All edges in Signal and Forwarding are present in Precedence
27
+ #
28
+ # == Exception propagation
29
+ #
30
+ module Roby::Propagation
31
+ extend Logger::Hierarchy
32
+ extend Logger::Forward
33
+
34
+ @@propagation_id = 0
35
+ def self.propagation_id; Thread.current[:propagation_id] end
36
+
37
+ # If we are currently in the propagation stage
38
+ def self.gathering?; !!Thread.current[:propagation] end
39
+ # The set of source events for the current propagation action. This is a
40
+ # mix of EventGenerator and Event objects.
41
+ def self.sources; Thread.current[:propagation_sources] end
42
+ # The set of events extracted from PropagationException.sources
43
+ def self.source_events
44
+ result = ValueSet.new
45
+ for ev in Thread.current[:propagation_sources]
46
+ if ev.respond_to?(:generator)
47
+ result << ev
48
+ end
49
+ end
50
+ result
51
+ end
52
+
53
+ # The set of generators extracted from Propagation.sources
54
+ def self.source_generators
55
+ result = ValueSet.new
56
+ for ev in Thread.current[:propagation_sources]
57
+ result << if ev.respond_to?(:generator)
58
+ ev.generator
59
+ else
60
+ ev
61
+ end
62
+ end
63
+ result
64
+ end
65
+
66
+ @@delayed_events = []
67
+ def self.delayed_events; @@delayed_events end
68
+ def self.add_event_delay(time, forward, source, signalled, context)
69
+ delayed_events << [time, forward, source, signalled, context]
70
+ end
71
+ def self.execute_delayed_events
72
+ reftime = Time.now
73
+ delayed_events.delete_if do |time, forward, source, signalled, context|
74
+ if time < reftime
75
+ add_event_propagation(forward, [source], signalled, context, nil)
76
+ true
77
+ end
78
+ end
79
+ end
80
+ module RemoveDelayedOnFinalized
81
+ def finalized_event(event)
82
+ super if defined? super
83
+ Roby::Propagation.delayed_events.delete_if { |_, _, _, signalled, _| signalled == event }
84
+ end
85
+ end
86
+ Roby::Plan.include RemoveDelayedOnFinalized
87
+ Roby::Control.event_processing << Roby::Propagation.method(:execute_delayed_events)
88
+
89
+ # Begin an event propagation stage
90
+ def self.gather_propagation(initial_set = Hash.new)
91
+ raise InternalError, "nested call to #gather_propagation" if gathering?
92
+ Thread.current[:propagation] = initial_set
93
+
94
+ propagation_context(nil) { yield }
95
+
96
+ return Thread.current[:propagation]
97
+ ensure
98
+ Thread.current[:propagation] = nil
99
+ end
100
+
101
+ def self.to_execution_exception(error)
102
+ Roby::ExecutionException.new(error)
103
+ end
104
+
105
+ def self.add_error(e)
106
+ if Thread.current[:propagation_exceptions]
107
+ plan_exception = to_execution_exception(e)
108
+ Thread.current[:propagation_exceptions] << plan_exception
109
+ else
110
+ if e.respond_to?(:error) && e.error
111
+ add_framework_error(e.error, "error outside error handling")
112
+ else
113
+ add_framework_error(e, "error outside error handling")
114
+ end
115
+ end
116
+ end
117
+
118
+ def self.gather_framework_errors(source)
119
+ yield
120
+ rescue Exception => e
121
+ add_framework_error(e, source)
122
+ end
123
+
124
+ def self.add_framework_error(error, source)
125
+ if Thread.current[:application_exceptions]
126
+ Thread.current[:application_exceptions] << [error, source]
127
+ elsif Roby.control.abort_on_application_exception || error.kind_of?(SignalException)
128
+ raise error, "in #{source}: #{error.message}", error.backtrace
129
+ else
130
+ Roby.error "Application error in #{source}: #{error.full_message}"
131
+ end
132
+ end
133
+
134
+ # Sets the source_event and source_generator variables according
135
+ # to +source+. +source+ is the +from+ argument of #add_event_propagation
136
+ def self.propagation_context(sources)
137
+ raise InternalError, "not in a gathering context in #fire" unless gathering?
138
+
139
+ if sources
140
+ current_sources = sources
141
+ Thread.current[:propagation_sources] = sources
142
+ else
143
+ Thread.current[:propagation_sources] = []
144
+ end
145
+
146
+ yield Thread.current[:propagation]
147
+
148
+ ensure
149
+ Thread.current[:propagation_sources] = sources
150
+ end
151
+
152
+ # Adds a propagation to the next propagation step. More specifically, it
153
+ # adds either forwarding or signalling the set of Event objects +from+ to
154
+ # the +signalled+ event generator, with the context +context+
155
+ def self.add_event_propagation(forward, from, signalled, context, timespec)
156
+ if signalled.plan != Roby.plan
157
+ raise Roby::EventNotExecutable.new(signalled), "#{signalled} not in main plan"
158
+ end
159
+
160
+ step = (Thread.current[:propagation][signalled] ||= [nil, nil])
161
+ from = [nil] unless from && !from.empty?
162
+
163
+ step = if forward then (step[0] ||= [])
164
+ else (step[1] ||= [])
165
+ end
166
+
167
+ from.each do |ev|
168
+ step << ev << context << timespec
169
+ end
170
+ end
171
+
172
+ # Calls its block in a #gather_propagation context and propagate events
173
+ # that have been called and/or emitted by the block
174
+ #
175
+ # If a block is given, it is called with the initial set of events: the
176
+ # events we should consider as already emitted in the following propagation.
177
+ # +seeds+ si a list of procs which should be called to initiate the propagation
178
+ # (i.e. build an initial set of events)
179
+ def self.propagate_events(seeds = [])
180
+ if Thread.current[:propagation_exceptions]
181
+ raise InternalError, "recursive call to propagate_events"
182
+ end
183
+
184
+ Thread.current[:propagation_id] = (@@propagation_id += 1)
185
+ Thread.current[:propagation_exceptions] = []
186
+
187
+ initial_set = []
188
+ next_step = gather_propagation do
189
+ gather_framework_errors('initial set setup') { yield(initial_set) } if block_given?
190
+ gather_framework_errors('distributed events') { Roby::Distributed.process_pending }
191
+ seeds.each do |s|
192
+ gather_framework_errors("seed #{s}") { s.call }
193
+ end
194
+ end
195
+
196
+ # Problem with postponed: the object is included in already_seen while it
197
+ # has not been fired
198
+ already_seen = initial_set.to_set
199
+
200
+ while !next_step.empty?
201
+ next_step = event_propagation_step(next_step, already_seen)
202
+ end
203
+ Thread.current[:propagation_exceptions]
204
+
205
+ ensure
206
+ Thread.current[:propagation_id] = nil
207
+ Thread.current[:propagation_exceptions] = nil
208
+ end
209
+
210
+ def self.validate_timespec(timespec)
211
+ if timespec
212
+ timespec = validate_options timespec, [:delay, :at]
213
+ end
214
+ end
215
+ def self.make_delay(timeref, source, signalled, timespec)
216
+ if delay = timespec[:delay] then timeref + delay
217
+ elsif at = timespec[:at] then at
218
+ else
219
+ raise ArgumentError, "invalid timespec #{timespec}"
220
+ end
221
+ end
222
+
223
+ @event_ordering = Array.new
224
+ @event_priorities = Hash.new
225
+ class << self
226
+ # The topological ordering of events w.r.t. the Precedence relation
227
+ attr_reader :event_ordering
228
+ # The event => index hash which give the propagation priority for each
229
+ # event
230
+ attr_reader :event_priorities
231
+ end
232
+
233
+ # This module hooks in plan modifications to clear the event ordering cache
234
+ # (Propagation.event_ordering) when needed.
235
+ #
236
+ # It is included in the main plan by Control#initialize
237
+ module ExecutablePlanChanged
238
+ def discovered_events(objects)
239
+ super if defined? super
240
+ Roby::Propagation.event_ordering.clear
241
+ end
242
+ def discovered_tasks(objects)
243
+ super if defined? super
244
+ Roby::Propagation.event_ordering.clear
245
+ end
246
+ end
247
+
248
+ # This module hooks in event relation modifications to clear the event
249
+ # ordering cache (Propagation.event_ordering) when needed.
250
+ module EventPrecedenceChanged
251
+ def added_child_object(child, relations, info)
252
+ super if defined? super
253
+ if relations.include?(Roby::EventStructure::Precedence) && plan == Roby.plan
254
+ Roby::Propagation.event_ordering.clear
255
+ end
256
+ end
257
+ def removed_child_object(child, relations)
258
+ super if defined? super
259
+ if relations.include?(Roby::EventStructure::Precedence) && plan == Roby.plan
260
+ Roby::Propagation.event_ordering.clear
261
+ end
262
+ end
263
+ end
264
+ Roby::EventGenerator.include EventPrecedenceChanged
265
+
266
+ # Determines the event in +current_step+ which should be signalled now.
267
+ # Removes it from the set and returns the event and the associated
268
+ # propagation information
269
+ def self.next_event(pending)
270
+ if event_ordering.empty?
271
+ Roby::EventStructure::Precedence.topological_sort(event_ordering)
272
+ event_priorities.clear
273
+ i = 0
274
+ for ev in event_ordering
275
+ event_priorities[ev] = i
276
+ i += 1
277
+ end
278
+ end
279
+
280
+ signalled, min = nil, event_ordering.size
281
+ for propagation_step in pending
282
+ event = propagation_step[0]
283
+ if priority = event_priorities[event]
284
+ if priority < min
285
+ signalled = event
286
+ min = priority
287
+ end
288
+ else
289
+ signalled = event
290
+ break
291
+ end
292
+ end
293
+ [signalled, *pending.delete(signalled)]
294
+ end
295
+
296
+ def self.prepare_propagation(signalled, forward, info)
297
+ timeref = Time.now
298
+
299
+ source_events, source_generators, context = ValueSet.new, ValueSet.new, []
300
+
301
+ delayed = true
302
+ info.each_slice(3) do |src, ctxt, time|
303
+ if time && (delay = make_delay(timeref, src, signalled, time))
304
+ add_event_delay(delay, forward, src, signalled, ctxt)
305
+ next
306
+ end
307
+
308
+ delayed = false
309
+
310
+ # Merge identical signals. Needed because two different event handlers
311
+ # can both call #emit, and two signals are set up
312
+ if src
313
+ if src.respond_to?(:generator)
314
+ source_events << src
315
+ source_generators << src.generator
316
+ else
317
+ source_generators << src
318
+ end
319
+ end
320
+ if ctxt
321
+ context.concat ctxt
322
+ end
323
+ end
324
+
325
+ unless delayed
326
+ [source_events, source_generators, (context unless context.empty?)]
327
+ end
328
+ end
329
+
330
+ # Propagate one step
331
+ #
332
+ # +current_step+ describes all pending emissions and calls. +already_seen+
333
+ # is obsolete and is not used anymore.
334
+ #
335
+ # This method calls Propagation.next_event to get the description of the
336
+ # next event to call. If there are signals going to this event, they are
337
+ # processed and the forwardings will be treated in the next step.
338
+ #
339
+ # The method returns the next set of pending emissions and calls, adding
340
+ # the forwardings and signals that the propagation of the considered event
341
+ # have added.
342
+ def self.event_propagation_step(current_step, already_seen)
343
+ signalled, forward_info, call_info = next_event(current_step)
344
+
345
+ next_step = nil
346
+ if call_info
347
+ source_events, source_generators, context = prepare_propagation(signalled, false, call_info)
348
+ if source_events
349
+ for source_ev in source_events
350
+ source_ev.generator.signalling(source_ev, signalled)
351
+ end
352
+
353
+ if signalled.self_owned?
354
+ next_step = gather_propagation(current_step) do
355
+ propagation_context(source_events | source_generators) do |result|
356
+ begin
357
+ signalled.call_without_propagation(context)
358
+ rescue Roby::LocalizedError => e
359
+ add_error(e)
360
+ rescue Exception => e
361
+ add_error(Roby::CommandFailed.new(e, signalled))
362
+ end
363
+ end
364
+ end
365
+ end
366
+ end
367
+
368
+ if forward_info
369
+ next_step ||= Hash.new
370
+ next_step[signalled] ||= []
371
+ next_step[signalled][0] ||= []
372
+ next_step[signalled][0].concat forward_info
373
+ end
374
+
375
+ elsif forward_info
376
+ source_events, source_generators, context = prepare_propagation(signalled, true, forward_info)
377
+ if source_events
378
+ for source_ev in source_events
379
+ source_ev.generator.forwarding(source_ev, signalled)
380
+ end
381
+
382
+ # If the destination event is not owned, but if the peer is not
383
+ # connected, the event is our responsibility now.
384
+ if signalled.self_owned? || !signalled.owners.any? { |peer| peer != Roby::Distributed && peer.connected? }
385
+ next_step = gather_propagation(current_step) do
386
+ propagation_context(source_events | source_generators) do |result|
387
+ begin
388
+ signalled.emit_without_propagation(context)
389
+ rescue Roby::LocalizedError => e
390
+ add_error(e)
391
+ rescue Exception => e
392
+ add_error(Roby::EmissionFailed.new(e, signalled))
393
+ end
394
+ end
395
+ end
396
+ end
397
+ end
398
+ end
399
+
400
+ current_step.merge!(next_step) if next_step
401
+ current_step
402
+ end
403
+
404
+ # Checks if +error+ is being repaired in the corresponding plan. Note that
405
+ # +error+ is supposed to be the original exception, not the corresponding
406
+ # ExecutionException object
407
+ def self.remove_inhibited_exceptions(exceptions)
408
+ exceptions.find_all do |e, _|
409
+ error = e.exception
410
+ if !error.respond_to?(:failed_event) ||
411
+ !(failure_point = error.failed_event)
412
+ true
413
+ else
414
+ Roby.plan.repairs_for(failure_point).empty?
415
+ end
416
+ end
417
+ end
418
+
419
+ def self.remove_useless_repairs
420
+ plan = Roby.plan
421
+
422
+ finished_repairs = plan.repairs.dup.delete_if { |_, task| task.starting? || task.running? }
423
+ for repair in finished_repairs
424
+ plan.remove_repair(repair[1])
425
+ end
426
+
427
+ finished_repairs
428
+ end
429
+
430
+ # Performs exception propagation for the given ExecutionException objects
431
+ # Returns all exceptions which have found no handlers in the task hierarchy
432
+ def self.propagate_exceptions(exceptions)
433
+ fatal = [] # the list of exceptions for which no handler has been found
434
+
435
+ # Remove finished repairs. Those are still considered during this cycle,
436
+ # as it is possible that some actions have been scheduled for the
437
+ # beginning of the next cycle through Roby.once
438
+ finished_repairs = remove_useless_repairs
439
+ # Remove remove exceptions for which a repair exists
440
+ exceptions = remove_inhibited_exceptions(exceptions)
441
+
442
+ # Install new repairs based on the HandledBy task relation. If a repair
443
+ # is installed, remove the exception from the set of errors to handle
444
+ exceptions.delete_if do |e, _|
445
+ # Check for handled_by relations which would be able to handle +e+
446
+ error = e.exception
447
+ next unless (failed_event = error.failed_event)
448
+ next unless (failed_task = error.failed_task)
449
+ next if finished_repairs.has_key?(failed_event)
450
+
451
+ failed_generator = error.failed_generator
452
+
453
+ repair = failed_task.find_error_handler do |repairing_task, event_set|
454
+ event_set.find do |repaired_generator|
455
+ repaired_generator = failed_task.event(repaired_generator)
456
+
457
+ !repairing_task.finished? &&
458
+ (repaired_generator == failed_generator ||
459
+ Roby::EventStructure::Forwarding.reachable?(failed_generator, repaired_generator))
460
+ end
461
+ end
462
+
463
+ if repair
464
+ failed_task.plan.add_repair(failed_event, repair)
465
+ true
466
+ else
467
+ false
468
+ end
469
+ end
470
+
471
+ while !exceptions.empty?
472
+ by_task = Hash.new { |h, k| h[k] = Array.new }
473
+ by_task = exceptions.inject(by_task) do |by_task, (e, parents)|
474
+ unless e.task
475
+ Roby.log_exception(e.exception, Roby, :fatal)
476
+ raise NotImplementedError, "we do not yet handle exceptions from external event generators. Got #{e.exception.full_message}"
477
+ end
478
+ parents ||= e.task.parent_objects(Roby::TaskStructure::Hierarchy)
479
+
480
+ has_parent = false
481
+ [*parents].each do |parent|
482
+ next if parent.finished?
483
+
484
+ if has_parent # we have more than one parent
485
+ e = e.fork
486
+ end
487
+
488
+ parent_exceptions = by_task[parent]
489
+ if s = parent_exceptions.find { |s| s.siblings.include?(e) }
490
+ s.merge(e)
491
+ else parent_exceptions << e
492
+ end
493
+
494
+ has_parent = true
495
+ end
496
+
497
+ # Add unhandled exceptions to the fatal set. Merge siblings
498
+ # exceptions if possible
499
+ unless has_parent
500
+ if s = fatal.find { |s| s.siblings.include?(e) }
501
+ s.merge(e)
502
+ else fatal << e
503
+ end
504
+ end
505
+
506
+ by_task
507
+ end
508
+
509
+ parent_trees = by_task.map do |task, _|
510
+ [task, task.reverse_generated_subgraph(Roby::TaskStructure::Hierarchy)]
511
+ end
512
+
513
+ # Handle the exception in all tasks that are in no other parent trees
514
+ new_exceptions = ValueSet.new
515
+ by_task.each do |task, task_exceptions|
516
+ if parent_trees.find { |t, tree| t != task && tree.include?(task) }
517
+ task_exceptions.each { |e| new_exceptions << [e, [task]] }
518
+ next
519
+ end
520
+
521
+ task_exceptions.each do |e|
522
+ next if e.handled?
523
+ handled = task.handle_exception(e)
524
+
525
+ if handled
526
+ Roby::Control.handled_exception(e, task)
527
+ e.handled = true
528
+ else
529
+ # We do not have the framework to handle concurrent repairs
530
+ # For now, the first handler is the one ...
531
+ new_exceptions << e
532
+ e.trace << task
533
+ end
534
+ end
535
+ end
536
+
537
+ exceptions = new_exceptions
538
+ end
539
+
540
+ # Call global exception handlers for exceptions in +fatal+. Return the
541
+ # set of still unhandled exceptions
542
+ fatal.
543
+ find_all { |e| !e.handled? }.
544
+ reject { |e| Roby.handle_exception(e) }
545
+ end
546
+ end
547
+
548
+ module Roby
549
+ @exception_handlers = Array.new
550
+ class << self
551
+ attr_reader :exception_handlers
552
+ def each_exception_handler(&iterator); exception_handlers.each(&iterator) end
553
+ # define_method(:each_exception_handler, &Roby::Propagation.exception_handlers.method(:each))
554
+ def on_exception(*matchers, &handler)
555
+ check_arity(handler, 2)
556
+ exception_handlers.unshift [matchers, handler]
557
+ end
558
+ include ExceptionHandlingObject
559
+ end
560
+ end
561
+
562
+