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,550 @@
1
+ require 'set'
2
+ require 'utilrb/array/to_s'
3
+ require 'utilrb/socket/tcp_socket'
4
+
5
+ require 'roby'
6
+ require 'roby/state'
7
+ require 'roby/planning'
8
+ require 'roby/distributed/notifications'
9
+ require 'roby/distributed/proxy'
10
+ require 'roby/distributed/communication'
11
+
12
+ module Roby
13
+ class Control; include DRbUndumped end
14
+ end
15
+
16
+ module Roby::Distributed
17
+ class ConnectionSpace
18
+ def add_owner(object, peer)
19
+ object.add_owner(peer, false)
20
+ end
21
+ def remove_owner(object, peer)
22
+ object.remove_owner(peer, false)
23
+ end
24
+ def prepare_remove_owner(object, peer)
25
+ object.prepare_remove_owner(peer)
26
+ rescue Exception => e
27
+ e
28
+ end
29
+ end
30
+
31
+ class ConnectionTask < Roby::Task
32
+ local_only
33
+
34
+ argument :peer
35
+ def peer; arguments[:peer] end
36
+
37
+ event :ready
38
+ def ready?; event(:ready).happened? end
39
+
40
+ event :aborted, :terminal => true do |context|
41
+ peer.disconnected!
42
+ end
43
+ forward :aborted => :failed
44
+
45
+ event :failed, :terminal => true do |context|
46
+ peer.disconnect
47
+ end
48
+ interruptible
49
+ end
50
+
51
+ # Base class for all communication errors
52
+ class ConnectionError < RuntimeError; end
53
+ # Raised when a connection attempt has failed
54
+ class ConnectionFailedError < RuntimeError
55
+ def initialize(peer); @peer = peer end
56
+ end
57
+ # The peer is connected but connection is not alive
58
+ class NotAliveError < ConnectionError; end
59
+ # The peer is disconnected
60
+ class DisconnectedError < ConnectionError; end
61
+
62
+ class << self
63
+ # This method will call PeerServer#trigger on all peers, for the
64
+ # objects in +objects+ which are eligible for triggering.
65
+ #
66
+ # The same task cannot match the same trigger twice. To allow that,
67
+ # call #clean_triggered.
68
+ def trigger(*objects)
69
+ return unless Roby::Distributed.state
70
+ objects.delete_if do |o|
71
+ o.plan != Roby::Distributed.state.plan ||
72
+ !o.distribute? ||
73
+ !o.self_owned?
74
+ end
75
+ return if objects.empty?
76
+
77
+ # If +object+ is a trigger, send the :triggered event but do *not*
78
+ # act as if +object+ was subscribed
79
+ peers.each_value do |peer|
80
+ peer.local_server.trigger(*objects)
81
+ end
82
+ end
83
+ # Remove +objects+ from the sets of already-triggered objects. So, next
84
+ # time +object+ will be tested for triggers, it will re-match the
85
+ # triggers it has already matched.
86
+ def clean_triggered(object)
87
+ peers.each_value do |peer|
88
+ peer.local_server.triggers.each_value do |_, triggered|
89
+ triggered.delete object
90
+ end
91
+ end
92
+ end
93
+ end
94
+
95
+ # PeerServer objects are the objects which act as servers for the plan
96
+ # managers we are connected on, i.e. it will process the messages sent by
97
+ # those remote plan managers.
98
+ #
99
+ # The client part, that is the part which actually send the messages, is
100
+ # a Peer object accessible through the Peer#peer attribute.
101
+ class PeerServer
102
+ include DRbUndumped
103
+
104
+ # The Peer object we are associated to
105
+ attr_reader :peer
106
+
107
+ # The set of triggers our peer has added to our plan
108
+ attr_reader :triggers
109
+
110
+ # Create a PeerServer object for the given peer
111
+ def initialize(peer)
112
+ @peer = peer
113
+ @triggers = Hash.new
114
+ end
115
+
116
+ def to_s # :nodoc:
117
+ "PeerServer:#{remote_name}"
118
+ end
119
+
120
+ # Activate any trigger that may exist on +objects+
121
+ # It sends the PeerServer#triggered message for each objects that are
122
+ # actually matching a registered trigger.
123
+ def trigger(*objects)
124
+ triggers.each do |id, (matcher, triggered)|
125
+ objects.each do |object|
126
+ if !triggered.include?(object) && matcher === object
127
+ triggered << object
128
+ peer.transmit(:triggered, id, object)
129
+ end
130
+ end
131
+ end
132
+ end
133
+
134
+ # The name of the local ConnectionSpace object we are acting on
135
+ def local_name; peer.local_name end
136
+ # The name of the remote peer
137
+ def remote_name; peer.remote_name end
138
+
139
+ # The plan object which is used as a facade for our peer
140
+ def plan; peer.connection_space.plan end
141
+
142
+ # Applies +matcher+ on the local plan and sends back the result
143
+ def query_result_set(query)
144
+ plan.query_result_set(peer.local_object(query)).
145
+ delete_if { |obj| !obj.distribute? }
146
+ end
147
+
148
+ # The peers asks to be notified if a plan object which matches
149
+ # +matcher+ changes
150
+ def add_trigger(id, matcher)
151
+ triggers[id] = [matcher, (triggered = ValueSet.new)]
152
+ Roby.info "#{remote_name} wants notification on #{matcher} (#{id})"
153
+
154
+ peer.queueing do
155
+ matcher.each(plan) do |task|
156
+ if !triggered.include?(task)
157
+ triggered << task
158
+ peer.transmit(:triggered, id, task)
159
+ end
160
+ end
161
+ end
162
+ nil
163
+ end
164
+
165
+ # Remove the trigger +id+ defined by this peer
166
+ def remove_trigger(id)
167
+ Roby.info "#{remote_name} removed #{id} notification"
168
+ triggers.delete(id)
169
+ nil
170
+ end
171
+
172
+ # Message received when +task+ has matched the trigger referenced by +id+
173
+ def triggered(id, task)
174
+ peer.triggered(id, task)
175
+ nil
176
+ end
177
+
178
+ # Send the neighborhood of +distance+ hops around +object+ to the peer
179
+ def discover_neighborhood(object, distance)
180
+ object = peer.local_object(object)
181
+ edges = object.neighborhood(distance)
182
+ if object.respond_to?(:each_plan_child)
183
+ object.each_plan_child do |plan_child|
184
+ edges += plan_child.neighborhood(distance)
185
+ end
186
+ end
187
+
188
+ # Replace the relation graphs by their name
189
+ edges.delete_if do |rel, from, to, info|
190
+ !(rel.distribute? && from.distribute? && to.distribute?)
191
+ end
192
+ edges
193
+ end
194
+ end
195
+
196
+ # A Peer object is the client part of a connection with a remote plan
197
+ # manager. The server part, i.e. the object which actually receives
198
+ # requests from the remote plan manager, is the PeerServer object
199
+ # accessible through the Peer#local_server attribute.
200
+ #
201
+ # == Connection procedure
202
+ #
203
+ # Connections are initiated When the user calls Peer.initiate_connection.
204
+ # The following protocol is then followed:
205
+ # [local]
206
+ # if the neighbour is already connected to us, we do nothing and yield
207
+ # the already existing peer. End.
208
+ # [local]
209
+ # check if we are already connecting to the peer. If it is the case,
210
+ # wait for the end of the connection thread.
211
+ # [local]
212
+ # otherwise, open a new socket and send the connect() message in it
213
+ # The connection thread is registered in ConnectionSpace.pending_connections
214
+ # [remote]
215
+ # check if we are already connecting to the peer (check ConnectionSpace.pending_connections)
216
+ # * if it is the case, the lowest token wins
217
+ # * if 'remote' wins, return :already_connecting
218
+ # * if 'local' wins, return :connected with the relevant information
219
+ #
220
+ # == Communication
221
+ #
222
+ # Communication is done in two threads. The sending thread gets the calls
223
+ # from Peer#send_queue, formats them and sends them to the PeerServer#demux
224
+ # for processing. The reception thread is managed by dRb and its entry
225
+ # point is always #demux.
226
+ #
227
+ # Very often we need to have processing on both sides to finish an
228
+ # operation. For instance, the creation of two siblings need to register
229
+ # the siblings on both sides. To manage that, it is possible for PeerServer
230
+ # methods which are serving a remote request to queue callbacks. These
231
+ # callbacks will be processed by Peer#send_thread before the rest of the
232
+ # queue might be processed
233
+ class Peer
234
+ include DRbUndumped
235
+
236
+ # The local ConnectionSpace object we act on
237
+ attr_reader :connection_space
238
+ # The local PeerServer object for this peer
239
+ attr_reader :local_server
240
+ # The set of proxies for object from this remote peer
241
+ attr_reader :proxies
242
+ # The set of proxies we are currently removing. See BasicObject#forget_peer
243
+ attr_reader :removing_proxies
244
+ # The connection socket with our peer
245
+ attr_reader :socket
246
+
247
+ ComStats = Struct.new :rx, :tx
248
+ # A ComStats object which holds the communication statistics for this peer
249
+ # stats.tx is the count of bytes sent to the peer while stats.rx is the
250
+ # count of bytes received
251
+ attr_reader :stats
252
+
253
+ def to_s # :nodoc:
254
+ "Peer:#{remote_name}"
255
+ end
256
+ # This method is used by Distributed.format to determine the dumping
257
+ # policy for +object+. If the method returns true, then only the
258
+ # RemoteID object of +object+ will be sent to the peer. Otherwise,
259
+ # an intermediate object describing +object+ is sent.
260
+ def incremental_dump?(object)
261
+ object.respond_to?(:remote_siblings) && object.remote_siblings[self]
262
+ end
263
+
264
+ # The object which identifies this peer on the network
265
+ attr_reader :remote_id
266
+ # The name of the remote peer
267
+ attr_reader :remote_name
268
+ # The [host, port] pair at the peer end
269
+ attr_reader :peer_info
270
+
271
+ # The name of the local ConnectionSpace object we are acting on
272
+ def local_name; connection_space.name end
273
+
274
+ # The ID => block hash of all triggers we have defined on the remote plan
275
+ attr_reader :triggers
276
+ # The remote state
277
+ attr_accessor :state
278
+
279
+ # Creates a Peer object for the peer connected at +socket+. This peer
280
+ # is to be managed by +connection_space+ If a block is given, it is
281
+ # called in the control thread when the connection is finalized
282
+ def initialize(connection_space, socket, remote_name, remote_id, remote_state, &block)
283
+ # Initialize the remote name with the socket parameters. It will be set to
284
+ # the real name during the connection process
285
+ @remote_name = remote_name
286
+ @remote_id = remote_id
287
+ @peer_info = socket.peer_info
288
+
289
+ super() if defined? super
290
+
291
+ @connection_space = connection_space
292
+ @local_server = PeerServer.new(self)
293
+ @proxies = Hash.new
294
+ @removing_proxies = Hash.new { |h, k| h[k] = Array.new }
295
+ @mutex = Mutex.new
296
+ @triggers = Hash.new
297
+ @socket = socket
298
+ @stats = ComStats.new 0, 0
299
+ @dead = false
300
+ @disabled_rx = 0
301
+ @disabled_tx = 0
302
+ connection_space.pending_sockets << [socket, self]
303
+
304
+ @connection_state = :connected
305
+ @send_queue = Queue.new
306
+ @completion_queue = Queue.new
307
+ @current_cycle = Array.new
308
+
309
+ Roby::Distributed.peers[remote_id] = self
310
+ local_server.state_update remote_state
311
+
312
+ @task = ConnectionTask.new :peer => self
313
+ Roby::Control.once do
314
+ connection_space.plan.permanent(task)
315
+ task.start!
316
+ task.emit(:ready)
317
+ end
318
+
319
+ @send_thread = Thread.new(&method(:communication_loop))
320
+ end
321
+
322
+ # The peer name
323
+ attr_reader :name
324
+ # The ConnectionTask object for this peer
325
+ attr_reader :task
326
+
327
+ # Creates a query object on the remote plan.
328
+ #
329
+ # For thread-safe operation, always use #each on the resulting query:
330
+ # during the enumeration, the local plan GC will not remove those
331
+ # tasks.
332
+ def find_tasks
333
+ Roby::Query.new(self)
334
+ end
335
+
336
+ # Returns a set of remote tasks for +query+ applied on the remote plan
337
+ # This is not to be accessed directly. It is part of the Query
338
+ # interface.
339
+ #
340
+ # See #find_tasks.
341
+ def query_result_set(query)
342
+ result = ValueSet.new
343
+ call(:query_result_set, query) do |marshalled_set|
344
+ for task in marshalled_set
345
+ task = local_object(task)
346
+ Roby::Distributed.keep.ref(task)
347
+ result << task
348
+ end
349
+ end
350
+
351
+ result
352
+ end
353
+
354
+ # Yields the tasks saved in +result_set+ by #query_result_set. During
355
+ # the enumeration, the tasks are marked as permanent to avoid plan GC.
356
+ # The block can subscribe to the one that are interesting. After the
357
+ # block has returned, all non-subscribed tasks will be subject to plan
358
+ # GC.
359
+ def query_each(result_set) # :nodoc:
360
+ result_set.each do |task|
361
+ yield(task)
362
+ end
363
+
364
+ ensure
365
+ Roby::Control.synchronize do
366
+ if result_set
367
+ result_set.each do |task|
368
+ Roby::Distributed.keep.deref(task)
369
+ end
370
+ end
371
+ end
372
+ end
373
+
374
+ # call-seq:
375
+ # peer.on(matcher) { |task| ... } => ID
376
+ #
377
+ # Call the provided block in the control thread when a task matching
378
+ # +matcher+ has been found on the remote plan. +task+ is the local
379
+ # proxy for the matching remote task.
380
+ #
381
+ # The return value is an identifier which can be later used to remove
382
+ # the trigger with Peer#remove_trigger
383
+ #
384
+ # This sends the PeerServer#add_trigger message to the peer.
385
+ def on(matcher, &block)
386
+ triggers[matcher.object_id] = [matcher, block]
387
+ transmit(:add_trigger, matcher.object_id, matcher)
388
+ end
389
+
390
+ # Remove a trigger referenced by its ID. +id+ is the value returned by
391
+ # Peer#on
392
+ #
393
+ # This sends the PeerServer#remove_trigger message to the peer.
394
+ def remove_trigger(id)
395
+ transmit(:remove_trigger, id)
396
+ triggers.delete(id)
397
+ end
398
+
399
+ # Calls the block given to Peer#on in a separate thread when +task+ has
400
+ # matched the trigger
401
+ def triggered(id, task) # :nodoc:
402
+ task = local_object(task)
403
+ Roby::Distributed.keep.ref(task)
404
+ Thread.new do
405
+ begin
406
+ if trigger = triggers[id]
407
+ trigger.last.call(task)
408
+ end
409
+ rescue Exception
410
+ Roby.warn "trigger handler #{trigger.last} failed with #{$!.full_message}"
411
+ ensure
412
+ Roby::Distributed.keep.deref(task)
413
+ end
414
+ end
415
+ end
416
+
417
+ # Returns true if this peer owns +object+
418
+ def owns?(object); object.owners.include?(self) end
419
+
420
+ # Returns the remote object for +object+. +object+ can be either a
421
+ # DRbObject, a marshalled object or a local proxy. In the latter case,
422
+ # a RemotePeerMismatch exception is raised if the local proxy is not
423
+ # known to this peer.
424
+ def remote_object(object)
425
+ if object.kind_of?(RemoteID)
426
+ object
427
+ else object.sibling_on(self)
428
+ end
429
+ end
430
+
431
+ # Returns the remote_object, local_object pair for +object+. +object+
432
+ # can be either a marshalled object or a local proxy. Raises
433
+ # ArgumentError if it is none of the two. In the latter case, a
434
+ # RemotePeerMismatch exception is raised if the local proxy is not
435
+ # known to this peer.
436
+ def objects(object, create_local = true)
437
+ if object.kind_of?(RemoteID)
438
+ if local_proxy = proxies[object]
439
+ proxy_setup(local_proxy)
440
+ return [object, local_proxy]
441
+ end
442
+ raise ArgumentError, "got a RemoteID which has no proxy"
443
+ elsif object.respond_to?(:proxy)
444
+ [object.remote_object, proxy(object, create_local)]
445
+ else
446
+ [object.sibling_on(self), object]
447
+ end
448
+ end
449
+
450
+ def proxy_setup(local_object)
451
+ if local_object.respond_to?(:execution_agent) &&
452
+ local_object.owners.size == 1 &&
453
+ owns?(local_object) &&
454
+ !local_object.execution_agent &&
455
+ local_object.plan
456
+
457
+ remote_owner = local_object.owners.first
458
+ connection_task = local_object.plan[self.task]
459
+
460
+ Roby::Distributed.update_all([local_object, connection_task]) do
461
+ local_object.executed_by connection_task
462
+ end
463
+ end
464
+
465
+ local_object
466
+ end
467
+
468
+ # Returns the local object for +object+. +object+ can be either a
469
+ # marshalled object or a local proxy. Raises ArgumentError if it is
470
+ # none of the two. In the latter case, a RemotePeerMismatch exception
471
+ # is raised if the local proxy is not known to this peer.
472
+ def local_object(marshalled, create = true)
473
+ if marshalled.kind_of?(RemoteID)
474
+ return marshalled.to_local(self, create)
475
+ elsif !marshalled.respond_to?(:proxy)
476
+ return marshalled
477
+ elsif marshalled.respond_to?(:remote_siblings)
478
+ # 1/ try any local RemoteID reference registered in the marshalled object
479
+ local_id = marshalled.remote_siblings[Roby::Distributed.droby_dump]
480
+ if local_id
481
+ local_object = local_id.local_object rescue nil
482
+ local_object = nil if local_object.finalized?
483
+ end
484
+
485
+ # 2/ try the #proxies hash
486
+ if !local_object
487
+ remote_id = marshalled.remote_siblings[droby_dump]
488
+ unless local_object = proxies[remote_id]
489
+ return if !create
490
+
491
+ # remove any local ID since we are re-creating it
492
+ marshalled.remote_siblings.delete(Roby::Distributed.droby_dump)
493
+ local_object = marshalled.proxy(self)
494
+ end
495
+ end
496
+
497
+ if !local_object
498
+ raise "no remote siblings for #{remote_name} in #{marshalled} (#{marshalled.remote_siblings})"
499
+ end
500
+
501
+ if marshalled.respond_to?(:update)
502
+ Roby::Distributed.update(local_object) do
503
+ marshalled.update(self, local_object)
504
+ end
505
+ end
506
+ proxy_setup(local_object)
507
+ else
508
+ local_object = marshalled.proxy(self)
509
+ end
510
+
511
+ local_object
512
+ end
513
+ alias proxy local_object
514
+
515
+ # Discovers all objects at a distance +dist+ from +obj+. The object
516
+ # can be either a remote proxy or the remote object itself
517
+ def discover_neighborhood(object, distance)
518
+ objects = ValueSet.new
519
+ Roby.condition_variable(true) do |synchro, mutex|
520
+ mutex.synchronize do
521
+ transmit(:discover_neighborhood, object, distance) do |edges|
522
+ edges = local_object(edges)
523
+ edges.each do |rel, from, to, info|
524
+ objects << from.root_object << to.root_object
525
+ end
526
+ Roby::Distributed.update_all(objects) do
527
+ edges.each do |rel, from, to, info|
528
+ from.add_child_object(to, rel, info)
529
+ end
530
+ end
531
+
532
+ objects.each { |obj| Roby::Distributed.keep.ref(obj) }
533
+
534
+ synchro.broadcast
535
+ end
536
+ synchro.wait(mutex)
537
+ end
538
+ end
539
+
540
+ yield(local_object(remote_object(object)))
541
+
542
+ Roby::Control.synchronize do
543
+ objects.each { |obj| Roby::Distributed.keep.deref(obj) }
544
+ end
545
+ end
546
+ end
547
+ end
548
+
549
+ require 'roby/distributed/subscription'
550
+