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,206 @@
1
+ module Roby
2
+ module Distributed
3
+ # Module included in objects distributed across multiple pDBs
4
+ module DistributedObject
5
+ attribute(:mutex) { Mutex.new }
6
+ attribute(:synchro_call) { ConditionVariable.new }
7
+
8
+ # Makes this object owned by the local DB. This is equivalent to
9
+ # object.self_owned = true
10
+ def self_owned; self.self_owned = true end
11
+
12
+ # Adds or removes the local DB from the list of owners. This is
13
+ # equivalent to calling add_peer(Distributed) and
14
+ # remove_peer(Distributed)
15
+ def self_owned=(flag)
16
+ if flag then add_owner(Distributed)
17
+ else remove_owner(Distributed)
18
+ end
19
+ end
20
+
21
+ # Add the Peer +peer+ to the list of owners
22
+ def add_owner(peer, distributed = true)
23
+ return if owners.include?(peer)
24
+ if distributed
25
+ if !self_owned?
26
+ raise OwnershipError, "not object owner"
27
+ end
28
+
29
+ call_siblings(:add_owner, self, peer)
30
+ added_owner(peer)
31
+ else
32
+ owners << peer
33
+ if plan
34
+ plan.task_index.add_owner(self, peer)
35
+ end
36
+ Distributed.debug { "added owner to #{self}: #{owners.to_a}" }
37
+ end
38
+ end
39
+ def added_owner(peer); super if defined? super end
40
+
41
+ # Removes +peer+ from the list of owners. Raises OwnershipError if
42
+ # there are modified tasks in this transaction which are owned by
43
+ # +peer+
44
+ def remove_owner(peer, distributed = true)
45
+ return unless owners.include?(peer)
46
+
47
+ if distributed
48
+ results = call_siblings(:prepare_remove_owner, self, peer)
49
+ if error = results.values.find { |error| error }
50
+ raise error
51
+ end
52
+ call_siblings(:remove_owner, self, peer)
53
+ else
54
+ owners.delete(peer)
55
+ removed_owner(peer)
56
+ if plan
57
+ plan.task_index.remove_owner(self, peer)
58
+ end
59
+ Distributed.debug { "removed owner to #{self}: #{owners.to_a}" }
60
+ end
61
+ nil
62
+ end
63
+ def prepare_remove_owner(peer); super if defined? super end
64
+ def removed_owner(peer); super if defined? super end
65
+
66
+ def owner=(peer)
67
+ add_owner(peer)
68
+ owners.each do |owner|
69
+ remove_owner(owner) unless owner == peer
70
+ end
71
+ end
72
+
73
+
74
+ def call_siblings(m, *args)
75
+ Distributed.call_peers(updated_peers.dup << Distributed, m, *args)
76
+ end
77
+
78
+ def call_owners(*args) # :nodoc:
79
+ raise OwnershipError, "not owner" if !self_owned?
80
+
81
+ if owners.any? { |peer| !has_sibling_on?(peer) }
82
+ raise InvalidRemoteOperation, "cannot do #{args} if the object is not distributed on all its owners"
83
+ end
84
+
85
+ Distributed.call_peers(owners, *args)
86
+ end
87
+ end
88
+
89
+ # Calls +args+ on all peers and returns a { peer => return_value } hash
90
+ # of all the values returned by each peer
91
+ def self.call_peers(calling, m, *args)
92
+ Distributed.debug { "distributed call of #{m}(#{args}) on #{calling}" }
93
+
94
+ # This is a tricky procedure. Let's describe what is done here:
95
+ # * we send the required message to the peers listed in +calling+,
96
+ # and wait for all of them to have finished
97
+ # * since there is a coordination requirement, once a peer have
98
+ # processed its call we stop processing any of the messages it
99
+ # sends. We therefore block the RX thread of this peer using
100
+ # the block_communication condition variable
101
+
102
+ result = Hash.new
103
+ call_local = calling.include?(Distributed)
104
+ synchro, mutex = Roby.condition_variable(true)
105
+
106
+ mutex.synchronize do
107
+ waiting_for = calling.size
108
+ waiting_for -= 1 if call_local
109
+
110
+ calling.each do |peer|
111
+ next if peer == Distributed
112
+
113
+ callback = Proc.new do |peer_result|
114
+ mutex.synchronize do
115
+ result[peer] = peer.local_object(peer_result)
116
+ waiting_for -= 1
117
+ Distributed.debug { "reply for #{m}(#{args.join(", ")}) from #{peer}, #{waiting_for} remaining" }
118
+ if waiting_for == 0
119
+ synchro.broadcast
120
+ end
121
+ peer.disable_rx
122
+ end
123
+ end
124
+ peer.queue_call false, m, args, callback, Thread.current
125
+ end
126
+
127
+ unless waiting_for == 0
128
+ Distributed.debug "waiting for our peers to complete the call"
129
+ synchro.wait(mutex)
130
+ end
131
+ end
132
+
133
+ if call_local
134
+ Distributed.debug "processing locally ..."
135
+ result[Distributed] = Distributed.call(m, *args)
136
+ end
137
+ result
138
+
139
+ ensure
140
+ for peer in calling
141
+ peer.enable_rx if peer != Distributed
142
+ end
143
+ Roby.return_condition_variable(synchro, mutex)
144
+ end
145
+
146
+ class PeerServer
147
+ # Message sent when our remote peer requests that we create a local
148
+ # representation for one of its objects. It therefore creates a
149
+ # sibling for +marshalled_object+, which is a representation of a
150
+ # distributed object present on our peer.
151
+ #
152
+ # It calls #created_sibling on +marshalled_object+ with the new
153
+ # created sibling, to allow for specific operations to be done on
154
+ # it.
155
+ def create_sibling(marshalled_object)
156
+ object_remote_id = peer.remote_object(marshalled_object)
157
+ if sibling = peer.proxies[object_remote_id]
158
+ raise ArgumentError, "#{marshalled_object} has already a sibling (#{sibling})"
159
+ end
160
+
161
+ sibling = marshalled_object.sibling(peer)
162
+ peer.subscriptions << object_remote_id
163
+ marshalled_object.created_sibling(peer, sibling)
164
+ nil
165
+ end
166
+
167
+ # Message received when +owner+ is a peer which now owns +object+
168
+ def add_owner(object, owner)
169
+ peer.local_object(object).add_owner(peer.local_object(owner), false)
170
+ nil
171
+ end
172
+ # Message received when +owner+ does not own +object+ anymore
173
+ def remove_owner(object, owner)
174
+ peer.local_object(object).remove_owner(peer.local_object(owner), false)
175
+ nil
176
+ end
177
+ # Message received before #remove_owner, to verify if the removal
178
+ # operation can be done or not.
179
+ def prepare_remove_owner(object, owner)
180
+ peer.local_object(object).prepare_remove_owner(peer.local_object(owner))
181
+ nil
182
+ rescue
183
+ $!
184
+ end
185
+ end
186
+
187
+ class Peer
188
+ # Creates a sibling for +object+ on the peer, and returns the corresponding
189
+ # DRbObject
190
+ def create_sibling(object)
191
+ unless object.kind_of?(DistributedObject)
192
+ raise TypeError, "cannot create a sibling for a non-distributed object"
193
+ end
194
+
195
+ call(:create_sibling, object)
196
+ subscriptions << object.sibling_on(self)
197
+ Roby::Control.synchronize do
198
+ local_server.subscribe(object)
199
+ end
200
+
201
+ synchro_point
202
+ end
203
+ end
204
+ end
205
+ end
206
+
@@ -0,0 +1,62 @@
1
+ require 'rinda/rinda'
2
+ require 'rinda/ring'
3
+
4
+ require 'roby/distributed/protocol'
5
+
6
+ # This file contains extension to dRuby and Rinda classes which are needed to
7
+ # make Distributed Roby work
8
+ #
9
+ # Some are direct modification of the standard library (through reopening classes),
10
+ # others are made by subclassing the standard library.
11
+
12
+ module Rinda
13
+ class NotifyTemplateEntry
14
+ def pop(nonblock = false)
15
+ raise RequestExpiredError if @done
16
+ it = @queue.pop(nonblock) rescue nil
17
+ @done = true if it && it[0] == 'close'
18
+ return it
19
+ end
20
+ end
21
+ end
22
+
23
+ module Roby::Distributed
24
+ # Reimplements Rinda::RingServer, removing the tuplespace intermediate and
25
+ # the creation of most threads. This is done for performance reasons.
26
+ class RingServer < Rinda::RingServer
27
+ attr_reader :bind, :port
28
+
29
+ # Added a :bind option
30
+ def initialize(ts, options = {})
31
+ options = validate_options options, :bind => Socket.gethostname, :port => Rinda::Ring_PORT
32
+
33
+ @bind = options[:bind]
34
+ @port = options[:port]
35
+
36
+ @ts = ts
37
+ @soc = UDPSocket.new
38
+ @soc.bind options[:bind], options[:port]
39
+ @service = service
40
+ end
41
+
42
+ def service
43
+ Thread.new do
44
+ Thread.current.priority = 0
45
+ begin
46
+ loop do
47
+ msg = @soc.recv(1024)
48
+ tuple, timeout = Marshal.load(msg)
49
+ tuple[1].call(@ts) rescue nil
50
+ end
51
+ rescue Interrupt
52
+ end
53
+ end
54
+ end
55
+
56
+ def close
57
+ @service.raise Interrupt, "interrupting RingServer"
58
+ @soc.close
59
+ end
60
+ end
61
+ end
62
+
@@ -0,0 +1,539 @@
1
+ module Roby
2
+ module Distributed
3
+ # Returns the set of edges for which both sides are in +objects+. The
4
+ # set if formatted as [object, relations, ...] where +relations+ is the
5
+ # output of relations_of
6
+ def self.subgraph_of(objects)
7
+ return [] if objects.size < 2
8
+
9
+ relations = []
10
+
11
+ objects = objects.dup
12
+ objects.delete_if do |obj|
13
+ obj_relations = relations_of(obj) do |related_object|
14
+ objects.include?(related_object)
15
+ end
16
+ relations << obj << obj_relations
17
+ true
18
+ end
19
+
20
+ relations
21
+ end
22
+
23
+ # call-seq:
24
+ # relations_of(object) => relations
25
+ # relations_of(object) { |object| ... } => relations
26
+ #
27
+ # Relations to be sent to the remote host if +object+ is in a plan. The
28
+ # returned array if formatted as
29
+ # [ [graph, parents, children], [graph, ..] ]
30
+ # where +parents+ is the set of parents of +objects+ in +graph+ and
31
+ # +children+ the set of children
32
+ #
33
+ # +parents+ and +children+ are formatted as
34
+ # [object, info, object, info, ...]
35
+ #
36
+ # If a block is given, a new parent or child is added only if the block
37
+ # returns true
38
+ def self.relations_of(object)
39
+ result = []
40
+ # For transaction proxies, never send non-discovered relations to
41
+ # remote hosts
42
+ Roby::Distributed.each_object_relation(object) do |graph|
43
+ next unless graph.distribute?
44
+ parents = []
45
+ object.each_parent_object(graph) do |parent|
46
+ next unless parent.distribute?
47
+ next unless yield(parent) if block_given?
48
+ parents << parent << parent[object, graph]
49
+ end
50
+ children = []
51
+ object.each_child_object(graph) do |child|
52
+ next unless child.distribute?
53
+ next unless yield(child) if block_given?
54
+ children << child << object[child, graph]
55
+ end
56
+ result << graph << parents << children
57
+ end
58
+
59
+ result
60
+ end
61
+
62
+ # Set of hooks which send Plan updates to remote hosts
63
+ module PlanModificationHooks
64
+ # Hook called when a new task is marked as mission. It sends a
65
+ # PeerServer#plan_set_mission message to the remote host.
66
+ #
67
+ # Note that plan will have called the #discovered_tasks hook
68
+ # beforehand
69
+ def inserted(task)
70
+ super if defined? super
71
+ return unless task.distribute? && task.self_owned?
72
+
73
+ unless Distributed.updating?(self) || Distributed.updating?(task)
74
+ Distributed.each_updated_peer(self, task) do |peer|
75
+ peer.transmit(:plan_set_mission, self, task, true)
76
+ end
77
+ Distributed.trigger(task)
78
+ end
79
+ end
80
+
81
+ # Hook called when a new task is not a mission anymore. It sends a
82
+ # PeerServer#plan_set_mission message to the remote host.
83
+ def discarded(task)
84
+ super if defined? super
85
+ return unless task.distribute? && task.self_owned?
86
+
87
+ unless Distributed.updating?(self) || Distributed.updating?(task)
88
+ Distributed.each_updated_peer(self, task) do |peer|
89
+ peer.transmit(:plan_set_mission, self, task, false)
90
+ end
91
+ end
92
+ end
93
+
94
+ # Common implementation for the #discovered_events and
95
+ # #discovered_tasks hooks. It sends PeerServer#plan_discover for
96
+ # all tasks which can be shared among plan managers
97
+ def self.discovered_objects(plan, objects)
98
+ unless Distributed.updating?(plan)
99
+ relations = nil
100
+ Distributed.each_updated_peer(plan) do |peer|
101
+ # Compute +objects+ and +relations+ only if there is a
102
+ # peer to update
103
+ unless relations
104
+ objects = objects.find_all { |t| t.distribute? && t.self_owned? && t.root_object? && !Distributed.updating?(t) }
105
+ return if objects.empty?
106
+ relations = Distributed.subgraph_of(objects)
107
+ end
108
+ peer.transmit(:plan_discover, plan, objects, relations)
109
+ end
110
+ Distributed.trigger(*objects)
111
+ end
112
+ end
113
+ # New tasks have been discovered in the plan.
114
+ #
115
+ # See PlanModificationHooks.discovered_objects
116
+ def discovered_tasks(tasks)
117
+ super if defined? super
118
+ PlanModificationHooks.discovered_objects(self, tasks)
119
+ end
120
+ # New free events have been discovered in the plan.
121
+ #
122
+ # See PlanModificationHooks.discovered_objects
123
+ def discovered_events(events)
124
+ super if defined? super
125
+ PlanModificationHooks.discovered_objects(self, events)
126
+ end
127
+
128
+ # Hook called when +from+ has been replaced by +to+ in the plan.
129
+ # It sends a PeerServer#plan_replace message
130
+ def replaced(from, to)
131
+ super if defined? super
132
+ if (from.distribute? && to.distribute?) && (to.self_owned? || from.self_owned?)
133
+ unless Distributed.updating?(self) || Distributed.updating_all?([from, to])
134
+ Distributed.each_updated_peer(from) do |peer|
135
+ peer.transmit(:plan_replace, self, from, to)
136
+ end
137
+ end
138
+ end
139
+ end
140
+
141
+ # Common implementation for the #finalized_task and
142
+ # PeerServer#finalized_event hooks. It sends the plan_remove_object message.
143
+ def self.finalized_object(plan, object)
144
+ return unless object.distribute? && object.root_object?
145
+
146
+ Distributed.keep.delete(object)
147
+
148
+ if object.self_owned?
149
+ Distributed.clean_triggered(object)
150
+
151
+ if !Distributed.updating?(plan)
152
+ Distributed.peers.each_value do |peer|
153
+ if peer.connected?
154
+ peer.transmit(:plan_remove_object, plan, object)
155
+ end
156
+ end
157
+ end
158
+
159
+ if object.remotely_useful?
160
+ Distributed.removed_objects << object
161
+ end
162
+ else
163
+ object.remote_siblings.keys.each do |peer|
164
+ object.forget_peer(peer) unless peer == Roby::Distributed
165
+ end
166
+ end
167
+ end
168
+ # Hook called when a task has been removed from the plan.
169
+ #
170
+ # See PlanModificationHooks.finalized_object
171
+ def finalized_task(task)
172
+ super if defined? super
173
+ PlanModificationHooks.finalized_object(self, task)
174
+ end
175
+ # Hook called when a free event has been removed from the plan.
176
+ #
177
+ # See PlanModificationHooks.finalized_object
178
+ def finalized_event(event)
179
+ super if defined? super
180
+ PlanModificationHooks.finalized_object(self, event)
181
+ end
182
+ end
183
+ Plan.include PlanModificationHooks
184
+
185
+ class PeerServer
186
+ # Message received when +task+ has become a mission (flag = true),
187
+ # or has become a non-mission (flag = false) in +plan+.
188
+ def plan_set_mission(plan, task, flag)
189
+ plan = peer.local_object(plan)
190
+ task = peer.local_object(task)
191
+ if plan.owns?(task)
192
+ if flag
193
+ plan.insert(task)
194
+ else
195
+ plan.discard(task)
196
+ end
197
+ else
198
+ task.mission = flag
199
+ end
200
+ nil
201
+ end
202
+
203
+ # Message received when the set of tasks +m_tasks+ has been
204
+ # discovered by the remote plan. +m_relations+ describes the
205
+ # internal relations between elements of +m_tasks+. It is in a
206
+ # format suitable for PeerServer#set_relations.
207
+ def plan_discover(plan, m_tasks, m_relations)
208
+ Distributed.update(plan = peer.local_object(plan)) do
209
+ tasks = peer.local_object(m_tasks).to_value_set
210
+ Distributed.update_all(tasks) do
211
+ plan.discover(tasks)
212
+ m_relations.each_slice(2) do |obj, rel|
213
+ set_relations(obj, rel)
214
+ end
215
+ end
216
+ end
217
+ nil
218
+ end
219
+
220
+ # Message received when +m_from+ has been replaced by +m_to+ in the
221
+ # plan
222
+ def plan_replace(plan, m_from, m_to)
223
+ Distributed.update(plan = peer.local_object(plan)) do
224
+ from, to = peer.local_object(m_from), peer.local_object(m_to)
225
+
226
+ Distributed.update_all([from, to]) { plan.replace(from, to) }
227
+
228
+ # Subscribe to the new task if the old task was subscribed
229
+ # +from+ will be unsubscribed when it is finalized
230
+ if peer.subscribed?(from) && !peer.subscribed?(to)
231
+ peer.subscribe(to)
232
+ nil
233
+ end
234
+ end
235
+ nil
236
+ end
237
+
238
+ # Message received when +object+ has been removed from +plan+
239
+ def plan_remove_object(plan, object)
240
+ if local = peer.local_object(object, false)
241
+ # Beware, transaction proxies have no 'plan' attribute
242
+ plan = peer.local_object(plan)
243
+ Distributed.update(plan) do
244
+ Distributed.update(local) do
245
+ plan.remove_object(local)
246
+ end
247
+ end
248
+ local.forget_peer(peer)
249
+ end
250
+
251
+ rescue ArgumentError => e
252
+ if e.message =~ /has not been included in this plan/
253
+ Roby::Distributed.warn "filtering the 'not included in this plan bug'"
254
+ else
255
+ raise
256
+ end
257
+ end
258
+
259
+ # Message received when a relation graph has been updated. +op+ is
260
+ # either +add_child_object+ or +remove_child_object+ and describes
261
+ # what relation modification should be done. The two plan objects
262
+ # +m_from+ and +m_to+ are respectively linked or unlinked in the
263
+ # relation +m_rel+, with the given information object in case of a
264
+ # new relation.
265
+ def update_relation(plan, m_from, op, m_to, m_rel, m_info = nil)
266
+ if plan
267
+ Roby::Distributed.update(peer.local_object(plan)) { update_relation(nil, m_from, op, m_to, m_rel, m_info) }
268
+ else
269
+ from, to =
270
+ peer.local_object(m_from, false),
271
+ peer.local_object(m_to, false)
272
+
273
+ if !from
274
+ return unless to && (to.self_owned? || to.subscribed?)
275
+ from = peer.local_object(m_from)
276
+ elsif !to
277
+ return unless from && (from.self_owned? || from.subscribed?)
278
+ to = peer.local_object(m_to)
279
+ end
280
+
281
+ rel = peer.local_object(m_rel)
282
+ Roby::Distributed.update_all([from.root_object, to.root_object]) do
283
+ if op == :add_child_object
284
+ from.add_child_object(to, rel, peer.local_object(m_info))
285
+ elsif op == :remove_child_object
286
+ from.remove_child_object(to, rel)
287
+ end
288
+ end
289
+ end
290
+ nil
291
+ end
292
+ end
293
+
294
+ # This module defines the hooks needed to notify our peers of relation
295
+ # modifications. It is included in plan objects.
296
+ module RelationModificationHooks
297
+ # Hook called when a new relation is added. It sends the
298
+ # PeerServer#update_relation message.
299
+ def added_child_object(child, relations, info)
300
+ super if defined? super
301
+
302
+ return if Distributed.updating?(plan)
303
+ return if Distributed.updating_all?([self.root_object, child.root_object])
304
+ return unless Distributed.state
305
+
306
+ # Remove all relations that should not be distributed, and if
307
+ # there is a relation remaining, notify our peer only of the
308
+ # first one: this is the child of all others
309
+ if notified_relation = relations.find { |rel| rel.distribute? }
310
+ Distributed.each_updated_peer(self.root_object, child.root_object) do |peer|
311
+ peer.transmit(:update_relation, plan, self, :add_child_object, child, notified_relation, info)
312
+ end
313
+ Distributed.trigger(self, child)
314
+ end
315
+ end
316
+
317
+ # Hook called when a relation is removed. It sends the
318
+ # PeerServer#update_relation message.
319
+ def removed_child_object(child, relations)
320
+ super if defined? super
321
+ return unless Distributed.state
322
+
323
+ # If our peer is pushing a distributed transaction, children
324
+ # can be removed Avoid sending unneeded updates by testing on
325
+ # plan update
326
+ return if Distributed.updating?(plan)
327
+ return if Distributed.updating_all?([self.root_object, child.root_object])
328
+
329
+ # Remove all relations that should not be distributed, and if
330
+ # there is a relation remaining, notify our peer only of the
331
+ # first one: this is the child of all others
332
+ if notified_relation = relations.find { |rel| rel.distribute? }
333
+ Distributed.each_updated_peer(self.root_object, child.root_object) do |peer|
334
+ peer.transmit(:update_relation, plan, self, :remove_child_object, child, notified_relation)
335
+ end
336
+ Distributed.trigger(self, child)
337
+ end
338
+ end
339
+ end
340
+ PlanObject.include RelationModificationHooks
341
+
342
+ # This module includes the hooks needed to notify our peers of event
343
+ # propagation (fired, forwarding and signalling)
344
+ module EventNotifications
345
+ # Hook called when an event has been emitted. It sends the
346
+ # PeerServer#event_fired message.
347
+ def fired(event)
348
+ super if defined? super
349
+ if self_owned? && !Distributed.updating?(root_object)
350
+ Distributed.each_updated_peer(root_object) do |peer|
351
+ peer.transmit(:event_fired, self, event.object_id, event.time, event.context)
352
+ end
353
+ end
354
+ end
355
+ # Hook called when an event is being forwarded. It sends the
356
+ # PeerServer#event_add_propagation message.
357
+ def forwarding(event, to)
358
+ super if defined? super
359
+ if self_owned? && !Distributed.updating?(root_object)
360
+ Distributed.each_updated_peer(root_object, to.root_object) do |peer|
361
+ peer.transmit(:event_add_propagation, true, self, to, event.object_id, event.time, event.context)
362
+ end
363
+ end
364
+ end
365
+ # Hook called when an event is being forwarded. It sends the
366
+ # PeerServer#event_add_propagation message.
367
+ def signalling(event, to)
368
+ super if defined? super
369
+ if self_owned? && !Distributed.updating?(root_object)
370
+ Distributed.each_updated_peer(root_object, to.root_object) do |peer|
371
+ peer.transmit(:event_add_propagation, false, self, to, event.object_id, event.time, event.context)
372
+ end
373
+ end
374
+ end
375
+
376
+ # This module define hooks on Roby::Plan to manage the event fired
377
+ # cache. It is required by the receiving side of the event
378
+ # propagation distribution.
379
+ #
380
+ # See PeerServer#pending_events
381
+ module PlanCacheCleanup
382
+ # Removes events generated by +generator+ from the Event object
383
+ # cache, PeerServer#pending_events. This cache is used by
384
+ # PeerServer#event_for on behalf of PeerServer#event_fired and
385
+ # PeerServer#event_add_propagation
386
+ def finalized_event(generator)
387
+ super if defined? super
388
+ Distributed.peers.each_value do |peer|
389
+ peer.local_server.pending_events.delete(generator)
390
+ end
391
+ end
392
+ end
393
+ Roby::Plan.include PlanCacheCleanup
394
+ end
395
+ Roby::EventGenerator.include EventNotifications
396
+
397
+ class PeerServer
398
+ # A set of events which have been received by #event_fired. This
399
+ # cache in cleaned up by PlanCacheCleanup#finalized_event when the
400
+ # associated generator is finalized.
401
+ #
402
+ # This cache is used to merge the events between the firing step
403
+ # (event_fired) and the propagation steps (add_event_propagation).
404
+ # Without it, different Event objects at the various method calls.
405
+ attribute(:pending_events) { Hash.new }
406
+
407
+ # Creates an Event object for +generator+, with the given argument
408
+ # as parameters, or returns an already existing one
409
+ def event_for(generator, event_id, time, context)
410
+ id, event = pending_events[generator]
411
+ if id && id == event_id
412
+ return event
413
+ end
414
+
415
+ event = generator.new(context)
416
+ event.send(:time=, time)
417
+ if generator.respond_to?(:task)
418
+ generator.task.update_task_status(event)
419
+ end
420
+ pending_events[generator] = [event_id, event]
421
+ event
422
+ end
423
+
424
+ # Message received when the +marshalled_from+ generator fired an
425
+ # event, with the given event id, time and context.
426
+ def event_fired(marshalled_from, event_id, time, context)
427
+ from_generator = peer.local_object(marshalled_from)
428
+ context = peer.local_object(context)
429
+
430
+ event = event_for(from_generator, event_id, time, context)
431
+
432
+ event.send(:propagation_id=, Propagation.propagation_id)
433
+ from_generator.instance_variable_set("@happened", true)
434
+ from_generator.fired(event)
435
+ from_generator.call_handlers(event)
436
+
437
+ nil
438
+ end
439
+
440
+ # Message received when the +marshalled_from+ generator has either
441
+ # been forwarded (only_forward = true) or signals (only_forward =
442
+ # false) the +marshalled_to+ generator. The remaining information
443
+ # describes the event itself.
444
+ def event_add_propagation(only_forward, marshalled_from, marshalled_to, event_id, time, context)
445
+ from_generator = peer.local_object(marshalled_from)
446
+ to_generator = peer.local_object(marshalled_to)
447
+ context = peer.local_object(context)
448
+
449
+ event = event_for(from_generator, event_id, time, context)
450
+
451
+ # Only add the signalling if we own +to+
452
+ if to_generator.self_owned?
453
+ Propagation.add_event_propagation(only_forward, [event], to_generator, event.context, nil)
454
+ else
455
+ # Call #signalling or #forwarding to make
456
+ # +from_generator+ look like as if the event was really
457
+ # fired locally ...
458
+ Distributed.update_all([from_generator.root_object, to_generator.root_object]) do
459
+ if only_forward then from_generator.forwarding(event, to_generator)
460
+ else from_generator.signalling(event, to_generator)
461
+ end
462
+ end
463
+ end
464
+
465
+ nil
466
+ end
467
+ end
468
+
469
+ # This module defines the hooks required by dRoby on Roby::Task
470
+ module TaskNotifications
471
+ # Hook called when the internal task data is modified. It sends
472
+ # PeerServer#updated_data
473
+ def updated_data
474
+ super if defined? super
475
+
476
+ unless Distributed.updating?(self)
477
+ Distributed.each_updated_peer(self) do |peer|
478
+ peer.transmit(:updated_data, self, data)
479
+ end
480
+ end
481
+ end
482
+ end
483
+ Roby::Task.include TaskNotifications
484
+
485
+ # This module defines the hooks required by dRoby on Roby::TaskArguments
486
+ module TaskArgumentsNotifications
487
+ # Hook called when the task argumensts are modified. It sends
488
+ # the PeerServer#updated_arguments message.
489
+ def updated
490
+ super if defined? super
491
+
492
+ unless Distributed.updating?(task)
493
+ Distributed.each_updated_peer(task) do |peer|
494
+ peer.transmit(:updated_arguments, task, task.arguments)
495
+ end
496
+ end
497
+ end
498
+ end
499
+ TaskArguments.include TaskArgumentsNotifications
500
+
501
+ class PeerServer
502
+ # Message received to announce that the internal data of +task+ is
503
+ # now +data+.
504
+ def updated_data(task, data)
505
+ proxy = peer.local_object(task)
506
+ proxy.instance_variable_set("@data", peer.proxy(data))
507
+ nil
508
+ end
509
+
510
+ # Message received to announce that the arguments of +task+ have
511
+ # been modified. +arguments+ is a hash containing only the new
512
+ # values.
513
+ def updated_arguments(task, arguments)
514
+ proxy = peer.local_object(task)
515
+ arguments = peer.proxy(arguments)
516
+ Distributed.update(proxy) do
517
+ proxy.arguments.merge!(arguments || {})
518
+ end
519
+ nil
520
+ end
521
+ end
522
+
523
+ class PeerServer
524
+ # Message received to update our view of the remote robot state.
525
+ def state_update(new_state)
526
+ peer.state = new_state
527
+ nil
528
+ end
529
+ end
530
+
531
+ Roby::Control.at_cycle_end do
532
+ peers.each_value do |peer|
533
+ if peer.connected?
534
+ peer.transmit(:state_update, Roby::State)
535
+ end
536
+ end
537
+ end
538
+ end
539
+ end