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,592 @@
1
+ require 'rinda/ring'
2
+ require 'rinda/tuplespace'
3
+ require 'utilrb/time/to_hms'
4
+ require 'utilrb/kernel/options'
5
+ require 'utilrb/socket/tcp_server'
6
+
7
+ require 'roby/distributed/drb'
8
+ require 'roby/distributed/peer'
9
+
10
+ module Roby
11
+ module Distributed
12
+ # A neighbour is a [name, remote_id] tuple, where +name+ is the name of
13
+ # the neighbour and +remote_id+ the RemoteID which describes the remote
14
+ # ConnectionSpace, used to connect to it.
15
+ class Neighbour
16
+ attr_reader :name, :remote_id
17
+ def initialize(name, remote_id)
18
+ @name, @remote_id = name, remote_id
19
+ end
20
+
21
+ def connect; Peer.initiate_connection(ConnectionSpace.state, peer) end
22
+ def ==(other)
23
+ other.kind_of?(Neighbour) &&
24
+ (remote_id == other.remote_id)
25
+ end
26
+ def to_s; "#<Neighbour:#{name} #{remote_id}>" end
27
+ def eql?(other); other == self end
28
+ end
29
+
30
+ # Returns the Peer object for the given ID. +id+ can be either the peer
31
+ # RemoteID or its name.
32
+ def self.peer(id)
33
+ if id.kind_of?(Distributed::RemoteID)
34
+ if id == remote_id
35
+ Distributed
36
+ else
37
+ peers[id]
38
+ end
39
+ elsif id.respond_to?(:to_str)
40
+ peers.each_value { |p| return p if p.remote_name == id.to_str }
41
+ nil
42
+ else
43
+ nil
44
+ end
45
+ end
46
+
47
+ # Returns a RemoteID object suitable to represent this plan manager on
48
+ # the network.
49
+ #
50
+ # This makes Roby::Distributed behave like a Peer object
51
+ def self.remote_id
52
+ if state then state.remote_id
53
+ else @__single_remote_id__ ||= RemoteID.new('local', 0)
54
+ end
55
+ end
56
+
57
+ # Returns a Peer::DRoby object which can be used in the dRoby
58
+ # connection to represent this plan manager.
59
+ #
60
+ # This makes Roby::Distributed behave like a Peer object
61
+ def self.droby_dump(dest = nil)
62
+ if state then state.droby_dump(dest)
63
+ else @__single_marshalled_peer__ ||= Peer::DRoby.new('single', remote_id)
64
+ end
65
+ end
66
+
67
+ # Execute the given message without blocking. If a block is given,
68
+ # yield the result to that block.
69
+ #
70
+ # This makes Roby::Distributed behave like a Peer object
71
+ def self.transmit(*args)
72
+ Roby::Control.once do
73
+ result = Distributed.state.send(*args)
74
+ yield(result) if block_given?
75
+ end
76
+ end
77
+
78
+ # Execute the given message and wait for its result to be available.
79
+ #
80
+ # This makes Roby::Distributed behave like a Peer object
81
+ def self.call(*args)
82
+ Roby.execute do
83
+ Distributed.state.send(*args)
84
+ end
85
+ end
86
+
87
+ # True if this plan manager is subscribed to +object+
88
+ #
89
+ # This makes Roby::Distributed behave like a Peer object
90
+ def self.subscribed?(object)
91
+ object.subscribed?
92
+ end
93
+
94
+ # This class manages the connections between this plan manager and the
95
+ # remote plan managers
96
+ #
97
+ # * there is only one reception thread, at which all peers send data
98
+ class ConnectionSpace
99
+ include DRbUndumped
100
+
101
+ # List of discovered neighbours
102
+ def neighbours; synchronize { @neighbours.dup } end
103
+ # A queue containing all new neighbours
104
+ attr_reader :new_neighbours
105
+ # A remote_id => Peer map of the connected peers
106
+ attr_reader :peers
107
+ # A remote_id => thread of the connection threads
108
+ #
109
+ # See Peer.connection_request and Peer.initiate_connection
110
+ attr_reader :pending_connections
111
+ # A remote_id => thread of the connection threads
112
+ #
113
+ # See Peer.connection_request, Peer.initiate_connection and Peer#reconnect
114
+ attr_reader :aborted_connections
115
+ # The set of peers for which we have lost the link
116
+ attr_reader :pending_reconnections
117
+ # The period at which we do discovery
118
+ attr_reader :discovery_period
119
+ # The discovery thread
120
+ attr_reader :discovery_thread
121
+
122
+ # If we are doing discovery based on Rinda::RingFinger
123
+ def ring_discovery?; @ring_discovery end
124
+ # The list of broadcasting addresses to search for plan databases
125
+ attr_reader :ring_broadcast
126
+ # If we are doing discovery based on a central tuplespace
127
+ def central_discovery?; !!@discovery_tuplespace end
128
+ # The central tuplespace where neighbours are announced
129
+ attr_reader :discovery_tuplespace
130
+ # Last time a discovery finished
131
+ attr_reader :last_discovery
132
+ # A condition variable which is signalled to start a new discovery
133
+ attr_reader :start_discovery
134
+ # A condition variable which is signalled when discovery finishes
135
+ attr_reader :finished_discovery
136
+
137
+ # The main mutex which is used for synchronization with the discovery
138
+ # thread
139
+ attr_reader :mutex
140
+ def synchronize; mutex.synchronize { yield } end
141
+ # The plan we are publishing, usually Roby.plan
142
+ attr_reader :plan
143
+
144
+ # Our name on the network
145
+ attr_reader :name
146
+ # The socket on which we listen for incoming connections
147
+ attr_reader :server_socket
148
+
149
+ # Create a new ConnectionSpace objects. The following options can be provided:
150
+ #
151
+ # name:: the name of this plan manager. Defaults to <hostname>-<PID>
152
+ # period:: the discovery period [default: nil]
153
+ # ring_discovery:: whether or not ring discovery should be attempted [default: true]
154
+ # ring_broadcast:: the broadcast address for ring discovery
155
+ # discovery_tuplespace:: the DRbObject referencing the remote tuplespace which holds references to plan managers [default: nil]
156
+ # plan:: the plan this ConnectionSpace acts on. [default: Roby.plan]
157
+ # listen_at:: the port at which we should listen for incoming connections [default: 0]
158
+ def initialize(options = {})
159
+ super()
160
+
161
+ options = validate_options options,
162
+ :name => "#{Socket.gethostname}-#{Process.pid}", # the name of this host
163
+ :period => nil, # the discovery period
164
+ :ring_discovery => true, # wether we should do discovery based on Rinda::RingFinger
165
+ :ring_broadcast => '', # the broadcast address for discovery
166
+ :discovery_tuplespace => nil, # a central tuplespace which lists hosts (including ourselves)
167
+ :plan => nil, # the plan we publish, uses Roby.plan if nil
168
+ :listen_at => 0 # the port at which we listen for incoming connections
169
+
170
+ if options[:ring_discovery] && !options[:period]
171
+ raise ArgumentError, "you must provide a discovery period when using ring discovery"
172
+ end
173
+
174
+ @name = options[:name]
175
+ @neighbours = Array.new
176
+ @peers = Hash.new
177
+ @plan = options[:plan] || Roby.plan
178
+ @discovery_period = options[:period]
179
+ @ring_discovery = options[:ring_discovery]
180
+ @ring_broadcast = options[:ring_broadcast]
181
+ @discovery_tuplespace = options[:discovery_tuplespace]
182
+ @port = options[:port]
183
+ @pending_sockets = Queue.new
184
+ @pending_connections = Hash.new
185
+ @aborted_connections = Hash.new
186
+ @pending_reconnections = Array.new
187
+ @quit_neighbour_thread = false
188
+
189
+ @mutex = Mutex.new
190
+ @start_discovery = ConditionVariable.new
191
+ @finished_discovery = ConditionVariable.new
192
+ @new_neighbours = Queue.new
193
+
194
+ @connection_listeners = Array.new
195
+
196
+ yield(self) if block_given?
197
+
198
+ listen(options[:listen_at])
199
+ @remote_id = RemoteID.new(Socket.gethostname, server_socket.port)
200
+
201
+ if central_discovery?
202
+ if (discovery_tuplespace.write([:droby, name, remote_id]) rescue nil)
203
+ if discovery_tuplespace.kind_of?(DRbObject)
204
+ Distributed.info "published #{name}(#{remote_id}) on #{discovery_tuplespace.__drburi}"
205
+ else
206
+ Distributed.info "published #{name}(#{remote_id}) on local tuplespace"
207
+ end
208
+ else
209
+ Distributed.warn "cannot connect to #{discovery_tuplespace.__drburi}, disabling centralized discovery"
210
+ discovery_tuplespace = nil
211
+ end
212
+ end
213
+
214
+ if ring_discovery?
215
+ Distributed.info "doing ring discovery on #{ring_broadcast}"
216
+ end
217
+
218
+ synchronize do
219
+ # Start the discovery thread and wait for it to be initialized
220
+ @discovery_thread = Thread.new(&method(:neighbour_discovery))
221
+ finished_discovery.wait(mutex)
222
+ end
223
+ start_neighbour_discovery(true)
224
+
225
+ receive
226
+
227
+ Roby::Control.finalizers << method(:quit)
228
+ end
229
+
230
+ # Sets up a separate thread which listens for connection
231
+ def listen(port)
232
+ @server_socket = TCPServer.new(nil, port)
233
+ server_socket.listen(10)
234
+ Thread.new do
235
+ begin
236
+ while new_connection = server_socket.accept
237
+ begin
238
+ Peer.connection_request(self, new_connection)
239
+ rescue Exception => e
240
+ Roby::Distributed.fatal "failed to handle connection request on #{new_connection}"
241
+ Roby::Distributed.fatal e.full_message
242
+ new_connection.close
243
+ end
244
+ end
245
+ rescue Exception
246
+ end
247
+ end
248
+ end
249
+
250
+ # The RemoteID object which allows to reference this ConnectionSpace on the network
251
+ attr_reader :remote_id
252
+
253
+ # The set of new sockets to wait for. If one of these is closed,
254
+ # Distributed.receive will check wether we are supposed to be
255
+ # connected to the peer. If it's not the case, the socket will be
256
+ # ignored.
257
+ attr_reader :pending_sockets
258
+
259
+ # Starts the reception thread
260
+ def receive # :nodoc:
261
+ sockets = Hash.new
262
+ Thread.new do
263
+ while true
264
+ begin
265
+ while !pending_sockets.empty?
266
+ socket, peer = pending_sockets.shift
267
+ sockets[socket] = peer
268
+ Roby::Distributed.info "listening to #{socket.peer_info} for #{peer}"
269
+ end
270
+
271
+ begin
272
+ sockets.delete_if { |s, p| s.closed? && p.disconnected? }
273
+ read, _, errors = select(sockets.keys, nil, nil, 0.1)
274
+ rescue IOError
275
+ end
276
+ next if !read
277
+
278
+ closed_sockets = []
279
+ for socket in read
280
+ if socket.closed?
281
+ closed_sockets << socket
282
+ next
283
+ end
284
+
285
+ header = socket.read(8)
286
+ unless header && header.size == 8
287
+ closed_sockets << socket
288
+ next
289
+ end
290
+
291
+ id, size = header.unpack("NN")
292
+ data = socket.read(size)
293
+
294
+ p = sockets[socket]
295
+ p.stats.rx += (size + 8)
296
+ Roby::Distributed.cycles_rx << [p, Marshal.load(data)]
297
+ end
298
+
299
+ for socket in closed_sockets
300
+ p = sockets[socket]
301
+ if p.connected?
302
+ Roby::Distributed.info "lost connection with #{p}"
303
+ p.reconnect
304
+ sockets.delete socket
305
+ elsif p.disconnecting?
306
+ Roby::Distributed.info "#{p} disconnected"
307
+ p.disconnected
308
+ end
309
+ end
310
+
311
+ rescue Exception
312
+ Roby::Distributed.fatal "error in ConnectionSpace#receive: #{$!.full_message}"
313
+ end
314
+ end
315
+ end
316
+ end
317
+
318
+ def discovering?
319
+ synchronize do
320
+ if @last_discovery != @discovery_start
321
+ yield if block_given?
322
+ true
323
+ end
324
+ end
325
+ end
326
+
327
+ def owns?(object); object.owners.include?(Roby::Distributed) end
328
+
329
+ # An array of procs called at the end of the neighbour discovery,
330
+ # after #neighbours have been updated
331
+ attr_reader :connection_listeners
332
+
333
+ def discovery_port
334
+ if Distributed.server
335
+ Distributed.server.port
336
+ else DISCOVERY_RING_PORT
337
+ end
338
+ end
339
+
340
+ # Loop which does neighbour_discovery
341
+ def neighbour_discovery
342
+ Thread.current.priority = 2
343
+
344
+ discovered = []
345
+
346
+ # Initialize so that @discovery_start == discovery_start
347
+ @discovery_start = nil
348
+ discovery_start = nil
349
+ finger = nil
350
+ loop do
351
+ return if @quit_neighbour_thread
352
+
353
+ Control.synchronize do
354
+ old_neighbours, @neighbours = @neighbours, []
355
+ for new in discovered
356
+ unless new.remote_id == remote_id || @neighbours.include?(new)
357
+ @neighbours << new
358
+ unless old_neighbours.include?(new)
359
+ new_neighbours << [self, new]
360
+ end
361
+ end
362
+ end
363
+ discovered.clear
364
+ end
365
+
366
+ connection_listeners.each { |listen| listen.call(self) }
367
+ synchronize do
368
+ @last_discovery = discovery_start
369
+ finished_discovery.broadcast
370
+
371
+ if @discovery_start == @last_discovery
372
+ start_discovery.wait(mutex)
373
+ end
374
+ return if @quit_neighbour_thread
375
+ discovery_start = @discovery_start
376
+
377
+ if ring_discovery? && (!finger || (finger.port != discovery_port))
378
+ finger = Rinda::RingFinger.new(ring_broadcast, discovery_port)
379
+ end
380
+ end
381
+
382
+ from = Time.now
383
+ if central_discovery?
384
+ discovery_tuplespace.read_all([:droby, nil, nil]).
385
+ each do |n|
386
+ next if n[2] == remote_id
387
+ n = Neighbour.new(n[1], n[2])
388
+ discovered << n
389
+ end
390
+ end
391
+
392
+ if discovery_period
393
+ remaining = (@discovery_start + discovery_period) - Time.now
394
+ end
395
+
396
+ if ring_discovery?
397
+ finger.lookup_ring(remaining) do |cs|
398
+ next if cs == self
399
+
400
+ discovered << Neighbour.new(cs.name, cs.remote_id)
401
+ end
402
+ end
403
+ end
404
+
405
+ rescue Interrupt
406
+ rescue Exception => e
407
+ Distributed.fatal "neighbour discovery died with\n#{e.full_message}"
408
+ Distributed.fatal "Peers are: #{Distributed.peers.map { |id, peer| "#{id.inspect} => #{peer}" }.join(", ")}"
409
+
410
+ ensure
411
+ Distributed.info "quit neighbour thread"
412
+ neighbours.clear
413
+ new_neighbours.clear
414
+
415
+ # Force disconnection in case something got wrong in the normal
416
+ # disconnection process
417
+ Distributed.peers.values.each do |peer|
418
+ peer.disconnected unless peer.disconnected?
419
+ end
420
+
421
+ synchronize do
422
+ @discovery_thread = nil
423
+ finished_discovery.broadcast
424
+ end
425
+ end
426
+
427
+ # Starts one neighbour discovery loop
428
+ def start_neighbour_discovery(block = false)
429
+ synchronize do
430
+ unless discovery_thread && discovery_thread.alive?
431
+ raise "no discovery thread"
432
+ end
433
+
434
+ @discovery_start = Time.now
435
+ start_discovery.signal
436
+ end
437
+ wait_discovery if block
438
+ end
439
+
440
+ def wait_discovery
441
+ discovering? do
442
+ finished_discovery.wait(mutex)
443
+ end
444
+ end
445
+ def wait_next_discovery
446
+ synchronize do
447
+ unless discovery_thread && discovery_thread.alive?
448
+ raise "no discovery thread"
449
+ end
450
+ finished_discovery.wait(mutex)
451
+ end
452
+ end
453
+
454
+ # Define #droby_dump for Peer-like behaviour
455
+ def droby_dump(dest = nil); @__droby_marshalled__ ||= Peer::DRoby.new(name, remote_id) end
456
+
457
+ # Make the ConnectionSpace quit
458
+ def quit
459
+ Distributed.debug "ConnectionSpace #{self} quitting"
460
+
461
+ # Remove us from the central tuplespace
462
+ if central_discovery?
463
+ begin
464
+ discovery_tuplespace.take [:droby, nil, remote_id], 0
465
+ rescue DRb::DRbConnError, Rinda::RequestExpiredError
466
+ end
467
+ end
468
+
469
+ # Make the neighbour discovery thread quit as well
470
+ thread = synchronize do
471
+ if thread = @discovery_thread
472
+ thread.raise Interrupt, "forcing discovery thread quit"
473
+ end
474
+ thread
475
+ end
476
+ if thread
477
+ thread.join
478
+ end
479
+
480
+ ensure
481
+ if server_socket
482
+ begin
483
+ server_socket.close
484
+ rescue IOError
485
+ end
486
+ end
487
+
488
+ Roby::Control.finalizers.delete(method(:quit))
489
+ if Distributed.state == self
490
+ Distributed.state = nil
491
+ end
492
+ end
493
+
494
+ # Disable the keeper thread, we will do cleanup ourselves
495
+ def start_keeper; end
496
+
497
+ # This makes ConnectionSpace act as a PeerServer object locally
498
+ def transaction_prepare_commit(trsc) # :nodoc:
499
+ !trsc.valid_transaction?
500
+ end
501
+ # This makes ConnectionSpace act as a PeerServer object locally
502
+ def transaction_abandon_commit(trsc, reason) # :nodoc:
503
+ trsc.abandoned_commit(reason)
504
+ end
505
+ # This makes ConnectionSpace act as a PeerServer object locally
506
+ def transaction_commit(trsc) # :nodoc:
507
+ trsc.commit_transaction(false)
508
+ end
509
+ # This makes ConnectionSpace act as a PeerServer object locally
510
+ def transaction_discard(trsc) # :nodoc:
511
+ trsc.discard_transaction(false)
512
+ end
513
+ end
514
+
515
+ class << self
516
+ # The RingServer object through which we publish this plan manager
517
+ # on the network
518
+ attr_reader :server
519
+
520
+ # True if we are published on the network.
521
+ #
522
+ # See #server, #publish and #unpublish
523
+ def published?; !!@server end
524
+
525
+ # Enable ring discovery on our part. A RingServer object is set up
526
+ # to listen to connections on the port given as a :port option (or
527
+ # DISCOVERY_RING_PORT if none is specified).
528
+ #
529
+ # Note that all plan managers must use the same discovery port.
530
+ def publish(options = {})
531
+ options[:port] ||= DISCOVERY_RING_PORT
532
+ @server = RingServer.new(state, options)
533
+ Distributed.info "listening for distributed discovery on #{options[:port]}"
534
+ end
535
+
536
+ # Disable the ring discovery on our part.
537
+ def unpublish
538
+ if server
539
+ server.close
540
+ @server = nil
541
+ Distributed.info "disabled distributed discovery"
542
+ end
543
+ end
544
+
545
+ # The list of known neighbours. See ConnectionSpace#neighbours
546
+ def neighbours
547
+ if state then state.neighbours
548
+ else []
549
+ end
550
+ end
551
+
552
+ # The list of neighbours that have been found since the last
553
+ # execution cycle
554
+ def new_neighbours
555
+ if state then state.new_neighbours
556
+ else []
557
+ end
558
+ end
559
+ end
560
+
561
+ @new_neighbours_observers = Array.new
562
+ class << self
563
+ # The set of proc objects which should be notified when new
564
+ # neighbours are detected.
565
+ attr_reader :new_neighbours_observers
566
+
567
+ # Called in the neighbour discovery thread to detect new
568
+ # neighbours. It fills the new_neighbours queue which is read by
569
+ # notify_new_neighbours to notify application code of new
570
+ # neighbours in the control thread
571
+ def notify_new_neighbours
572
+ return unless Distributed.state
573
+ while !new_neighbours.empty?
574
+ cs, neighbour = new_neighbours.pop(true)
575
+ new_neighbours_observers.each do |obs|
576
+ obs[cs, neighbour]
577
+ end
578
+ end
579
+ end
580
+
581
+ # Defines a block which should be called when a new neighbour is
582
+ # detected
583
+ def on_neighbour
584
+ current = neighbours.dup
585
+ Roby::Control.once { current.each { |n| yield(n) } }
586
+ new_neighbours_observers << lambda { |_, n| yield(n) }
587
+ end
588
+ end
589
+ Roby::Control.event_processing << method(:notify_new_neighbours)
590
+ end
591
+ end
592
+