roby 0.7

Sign up to get free protection for your applications and to get access to all the features.
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
+