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
+ module Roby
2
+ class BasicObject
3
+ include DRbUndumped
4
+
5
+ def initialize_copy(old) # :nodoc:
6
+ super
7
+
8
+ @remote_siblings = Hash[Distributed, Roby::Distributed::RemoteID.from_object(self)]
9
+ end
10
+
11
+ # The set of Peer objects which own this object
12
+ attribute(:owners) { [Distributed] }
13
+ # True if we own this object
14
+ def self_owned?; owners.include?(Distributed) end
15
+
16
+ # Attribute which overrides the #distribute attribute on object classes
17
+ attr_writer :distribute
18
+ # True if this object can be seen by remote hosts
19
+ def distribute?
20
+ @distribute || (@distribute.nil? && self.class.distribute?)
21
+ end
22
+
23
+ # True if instances of this class should be seen by remote hosts
24
+ def self.distribute?; !(@distribute == false) end
25
+ # Call to make the object of this class never seen by remote hosts
26
+ def self.local_only; @distribute = false end
27
+
28
+ def finalized?; !remote_siblings[Distributed] end
29
+
30
+ # The peer => remote_object hash of known siblings for this peer: if
31
+ # there is a representation of this object on a peer, then
32
+ # +remote_siblings+ includes it
33
+ attribute(:remote_siblings) { Hash[Distributed, remote_id] }
34
+
35
+ # True if we know about a sibling on +peer+
36
+ def has_sibling_on?(peer)
37
+ peer == Roby::Distributed || remote_siblings.include?(peer)
38
+ end
39
+
40
+ # Returns the object representation of +self+ on +peer+. The returned
41
+ # value is either a remote sibling (the DRbObject of the representation
42
+ # of +self+ on +peer+), or self if peer is Roby::Distributed
43
+ def sibling_on(peer)
44
+ if sibling = remote_siblings[peer] then sibling
45
+ elsif Roby::Distributed == peer then self
46
+ else
47
+ raise RemotePeerMismatch, "#{self} has no known sibling on #{peer}"
48
+ end
49
+ end
50
+
51
+ # Sets +remote_object+ as the remote siblings for +self+ on +peer+, and
52
+ # notifies peer that +self+ is the remote siblings for +remote_object+
53
+ def sibling_of(remote_object, peer)
54
+ if !distribute?
55
+ raise ArgumentError, "#{self} is local only"
56
+ end
57
+
58
+ add_sibling_for(peer, remote_object)
59
+ peer.transmit(:added_sibling, remote_object, remote_id)
60
+ end
61
+
62
+ # Called when all links to +peer+ should be removed.
63
+ def forget_peer(peer)
64
+ if remote_object = remove_sibling_for(peer)
65
+ peer.removing_proxies[remote_object] << droby_dump(nil)
66
+
67
+ if peer.connected?
68
+ peer.transmit(:removed_sibling, remote_object, self.remote_id) do
69
+ set = peer.removing_proxies[remote_object]
70
+ set.shift
71
+ if set.empty?
72
+ peer.removing_proxies.delete(remote_object)
73
+ end
74
+ yield if block_given?
75
+ end
76
+ else
77
+ peer.removing_proxies.delete(remote_object)
78
+ end
79
+ end
80
+ end
81
+
82
+ # Registers +remote_object+ as the sibling of +self+ on +peer+. Unlike
83
+ # #sibling_of, do not notify the peer about it.
84
+ def add_sibling_for(peer, remote_object)
85
+ if old_sibling = remote_siblings[peer]
86
+ if old_sibling != remote_object
87
+ raise ArgumentError, "#{self} has already a sibling for #{peer} (#{old_sibling}) #{remote_siblings}"
88
+ else
89
+ # This is OK. The same sibling information can come from
90
+ # different sources. We only check for inconsistencies
91
+ return
92
+ end
93
+ end
94
+
95
+ remote_siblings[peer] = remote_object
96
+ peer.proxies[remote_object] = self
97
+ Roby::Distributed.debug "added sibling #{remote_object.inspect} for #{self} on #{peer} (#{remote_siblings})"
98
+ end
99
+
100
+ # Remove references about the sibling registered for +peer+ and returns it
101
+ def remove_sibling_for(peer, id = nil)
102
+ if id && remote_siblings[peer] != id
103
+ return
104
+ end
105
+
106
+ if remote_object = remote_siblings.delete(peer)
107
+ peer.proxies.delete(remote_object)
108
+ peer.subscriptions.delete(remote_object)
109
+ Roby::Distributed.debug "removed sibling #{remote_object.inspect} for #{self} on #{peer}"
110
+ remote_object
111
+ end
112
+ end
113
+
114
+ # True if we explicitely want this object to be updated by our peers
115
+ def subscribed?; owners.any? { |peer| peer.subscribed?(self) if peer != Distributed } end
116
+
117
+ # Subscribe to this object on all the peers which own it.
118
+ #
119
+ # This is a blocking operation, and cannot be used in the control
120
+ # thread.
121
+ def subscribe
122
+ if !self_owned? && !subscribed?
123
+ owners.each do |peer|
124
+ peer.subscribe(self) unless peer.subcribed?(self)
125
+ end
126
+ end
127
+ end
128
+
129
+ # True if this object is maintained up-to-date
130
+ def updated?; self_owned? || owners.any?(&remote_siblings.method(:[])) end
131
+ # True if +peer+ will send us updates about this object
132
+ def updated_by?(peer); self_owned? || (remote_siblings[peer] && peer.owns?(self)) end
133
+ # True if we shall send updates for this object on +peer+
134
+ def update_on?(peer); (self_owned? || peer.owns?(self)) && remote_siblings[peer] end
135
+ # The set of peers that will get updates of this object
136
+ def updated_peers
137
+ peers = remote_siblings.keys
138
+ peers.delete(Distributed)
139
+ peers
140
+ end
141
+
142
+ # True if this object is useful for our peers
143
+ def remotely_useful?; self_owned? && remote_siblings.size > 1 end
144
+
145
+ # True if this object can be modified in the current context
146
+ def read_write?
147
+ owners.include?(Distributed) || Distributed.updating?(self)
148
+ end
149
+ end
150
+ end
151
+
@@ -0,0 +1,5 @@
1
+ module Roby
2
+ VERSION = '0.7'
3
+ ROBY_LIB_DIR = File.expand_path( File.join(File.dirname(__FILE__), '..') )
4
+ ROBY_ROOT_DIR = File.expand_path( File.join(ROBY_LIB_DIR, '..') )
5
+ end
@@ -0,0 +1,747 @@
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 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
+ raise unless @thread
496
+ return
497
+ end
498
+
499
+ yield if block_given?
500
+
501
+ @cycle_length = options[:cycle]
502
+ event_loop
503
+
504
+ ensure
505
+ if Thread.current == self.thread
506
+ Roby::Control.synchronize do
507
+ # reset the options only if we are in the control thread
508
+ @thread = nil
509
+ Control.waiting_threads.each do |th|
510
+ th.raise ControlQuitError
511
+ end
512
+ Control.finalizers.each { |blk| blk.call rescue nil }
513
+ @quit = 0
514
+ end
515
+ end
516
+ end
517
+
518
+ attr_reader :last_stop_count
519
+ def clear
520
+ Control.synchronize do
521
+ plan.missions.dup.each { |t| plan.discard(t) }
522
+ plan.keepalive.dup.each { |t| plan.auto(t) }
523
+ plan.force_gc.merge( plan.known_tasks )
524
+
525
+ quaranteened_subplan = plan.useful_task_component(nil, ValueSet.new, plan.gc_quarantine.dup)
526
+ remaining = plan.known_tasks - quaranteened_subplan
527
+
528
+ if remaining.empty?
529
+ # Have to call #garbage_collect one more to make
530
+ # sure that unneeded events are removed as well
531
+ plan.garbage_collect
532
+ # Done cleaning the tasks, clear the remains
533
+ plan.transactions.each do |trsc|
534
+ trsc.discard_transaction if trsc.self_owned?
535
+ end
536
+ plan.clear
537
+ return
538
+ end
539
+
540
+ if last_stop_count != remaining.size
541
+ if last_stop_count == 0
542
+ Roby.info "control quitting. Waiting for #{remaining.size} tasks to finish (#{plan.size} tasks still in plan)"
543
+ Roby.debug " " + remaining.to_a.join("\n ")
544
+ else
545
+ Roby.info "waiting for #{remaining.size} tasks to finish (#{plan.size} tasks still in plan)"
546
+ Roby.debug " #{remaining.to_a.join("\n ")}"
547
+ end
548
+ if plan.gc_quarantine.size != 0
549
+ Roby.info "#{plan.gc_quarantine.size} tasks in quarantine"
550
+ end
551
+ @last_stop_count = remaining.size
552
+ end
553
+ remaining
554
+ end
555
+ end
556
+
557
+ attr_reader :remaining_cycle_time
558
+ def add_timepoint(stats, name)
559
+ stats[:end] = stats[name] = Time.now - cycle_start
560
+ @remaining_cycle_time = cycle_length - stats[:end]
561
+ end
562
+ def add_expected_duration(stats, name, duration)
563
+ stats[name] = Time.now + duration - cycle_start
564
+ end
565
+
566
+ def event_loop
567
+ @last_stop_count = 0
568
+ @cycle_start = Time.now
569
+ @cycle_index = 0
570
+
571
+ gc_enable_has_argument = begin
572
+ GC.enable(true)
573
+ true
574
+ rescue; false
575
+ end
576
+ stats = Hash.new
577
+ if ObjectSpace.respond_to?(:live_objects)
578
+ stats[:live_objects] = ObjectSpace.live_objects
579
+ end
580
+
581
+ GC.start
582
+ if gc_enable_has_argument
583
+ already_disabled_gc = GC.disable
584
+ end
585
+ loop do
586
+ begin
587
+ if quitting?
588
+ thread.priority = 0
589
+ begin
590
+ return if forced_exit? || !clear
591
+ rescue Exception => e
592
+ Roby.warn "Control failed to clean up"
593
+ Roby.format_exception(e).each do |line|
594
+ Roby.warn line
595
+ end
596
+ return
597
+ end
598
+ end
599
+
600
+ while Time.now > cycle_start + cycle_length
601
+ @cycle_start += cycle_length
602
+ @cycle_index += 1
603
+ end
604
+ stats[:start] = [cycle_start.tv_sec, cycle_start.tv_usec]
605
+ stats[:cycle_index] = cycle_index
606
+ Control.synchronize { process_events(stats) }
607
+
608
+ # Record the statistics about object allocation *before* running the Ruby
609
+ # GC. It is also updated at
610
+ if ObjectSpace.respond_to?(:live_objects)
611
+ live_objects_before_gc = ObjectSpace.live_objects
612
+ end
613
+
614
+ # If the ruby interpreter we run on offers a true/false argument to
615
+ # GC.enable, we disabled the GC and just run GC.enable(true) to make
616
+ # it run immediately if needed. Then, we re-disable it just after.
617
+ if gc_enable_has_argument && remaining_cycle_time > SLEEP_MIN_TIME
618
+ GC.enable(true)
619
+ GC.disable
620
+ end
621
+ add_timepoint(stats, :ruby_gc)
622
+
623
+ if ObjectSpace.respond_to?(:live_objects)
624
+ live_objects_after_gc = ObjectSpace.live_objects
625
+ end
626
+
627
+ # Sleep if there is enough time for it
628
+ if remaining_cycle_time > SLEEP_MIN_TIME
629
+ add_expected_duration(stats, :sleep, remaining_cycle_time)
630
+ sleep(remaining_cycle_time)
631
+ end
632
+ add_timepoint(stats, :sleep)
633
+
634
+ # Add some statistics and call cycle_end
635
+ if defined? Roby::Log
636
+ stats[:log_queue_size] = Roby::Log.logged_events.size
637
+ end
638
+ stats[:plan_task_count] = plan.known_tasks.size
639
+ stats[:plan_event_count] = plan.free_events.size
640
+ process_time = Process.times
641
+ stats[:cpu_time] = (process_time.utime + process_time.stime) * 1000
642
+
643
+ if ObjectSpace.respond_to?(:live_objects)
644
+ live_objects = ObjectSpace.live_objects
645
+ stats[:object_allocation] = live_objects - stats[:live_objects] - (live_objects_after_gc - live_objects_before_gc)
646
+ stats[:live_objects] = live_objects
647
+ end
648
+
649
+ cycle_end(stats)
650
+
651
+ stats = Hash.new
652
+ stats[:live_objects] = live_objects
653
+ @cycle_start += cycle_length
654
+ @cycle_index += 1
655
+
656
+ rescue Exception => e
657
+ Roby.warn "Control quitting because of unhandled exception"
658
+ Roby.format_exception(e).each do |line|
659
+ Roby.warn line
660
+ end
661
+ quit
662
+ end
663
+ end
664
+
665
+ ensure
666
+ GC.enable if !already_disabled_gc
667
+
668
+ if !plan.known_tasks.empty?
669
+ Roby.warn "the following tasks are still present in the plan:"
670
+ plan.known_tasks.each do |t|
671
+ Roby.warn " #{t}"
672
+ end
673
+ end
674
+ end
675
+
676
+ @finalizers = []
677
+ def self.finalizers; @finalizers end
678
+
679
+ # True if the control thread is currently quitting
680
+ def quitting?; @quit > 0 end
681
+ # True if the control thread is currently quitting
682
+ def forced_exit?; @quit > 1 end
683
+ # Make control quit
684
+ def quit; @quit += 1 end
685
+
686
+ # Called at each cycle end
687
+ def cycle_end(stats)
688
+ super if defined? super
689
+
690
+ Control.at_cycle_end_handlers.each do |handler|
691
+ begin
692
+ handler.call
693
+ rescue Exception => e
694
+ Propagation.add_framework_error(e, "during cycle end handler #{handler}")
695
+ end
696
+ end
697
+ end
698
+
699
+ # If the event thread has been started in its own thread,
700
+ # wait for it to terminate
701
+ def join
702
+ thread.join if thread
703
+
704
+ rescue Interrupt
705
+ Roby::Control.synchronize do
706
+ return unless thread
707
+
708
+ Roby.logger.level = Logger::INFO
709
+ Roby.warn "received interruption request"
710
+ quit
711
+ if @quit > 2
712
+ thread.raise Interrupt, "interrupting control thread at user request"
713
+ end
714
+ end
715
+
716
+ retry
717
+ end
718
+
719
+ attr_reader :cycle_index
720
+
721
+ # Hook called when a set of tasks is being killed because of an exception
722
+ def self.fatal_exception(error, tasks)
723
+ super if defined? super
724
+ Roby.format_exception(error.exception).each do |line|
725
+ Roby.warn line
726
+ end
727
+ end
728
+ # Hook called when an exception +e+ has been handled by +task+
729
+ def self.handled_exception(e, task); super if defined? super end
730
+ end
731
+
732
+ # Get all missions that have failed
733
+ def self.check_failed_missions(plan)
734
+ result = []
735
+ for task in plan.missions
736
+ result << MissionFailedError.new(task) if task.failed?
737
+ end
738
+ result
739
+ end
740
+ Control.structure_checks << method(:check_failed_missions)
741
+ end
742
+
743
+ require 'roby/propagation'
744
+
745
+ module Roby
746
+ @control = Control.instance
747
+ end