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,498 @@
1
+ require 'roby'
2
+ require 'roby/distributed/distributed_object'
3
+ require 'roby/distributed/proxy'
4
+
5
+ module Roby
6
+ module Distributed
7
+ class << self
8
+ # The block which is called when a new transaction has been proposed to us.
9
+ attr_accessor :transaction_handler
10
+
11
+ # Sets up the transaction handler. The given block will be called
12
+ # in a separate thread whenever a remote peer proposes a new
13
+ # transaction
14
+ def on_transaction(&block)
15
+ Distributed.transaction_handler = block
16
+ end
17
+ end
18
+
19
+ # Raised when an operation needs the edition token, while the local
20
+ # plan manager does not have it.
21
+ class NotEditor < RuntimeError; end
22
+ # Raised when a commit is attempted while the transaction is not ready,
23
+ # i.e. the token should be passed once more in the edition ring.
24
+ class NotReady < RuntimeError; end
25
+
26
+ # An implementation of a transaction distributed over multiple plan
27
+ # managers. The transaction modification protocol is based on an
28
+ # edition token, which is passed through all the transaction owners by
29
+ # #edit and #release.
30
+ #
31
+ # Most operations on this distributed transaction must be done outside
32
+ # the control thread, as they are blocking.
33
+ #
34
+ # See DistributedObject for a list of operations valid on distributed objects.
35
+ class Transaction < Roby::Transaction
36
+ attr_reader :owners
37
+ attr_reader :token_lock, :token_lock_signal
38
+ include DistributedObject
39
+
40
+ # Create a new distributed transaction based on the given plan. The
41
+ # transaction sole owner is the local plan manager, which is also
42
+ # the owner of the edition token.
43
+ def initialize(plan, options = {})
44
+ @owners = [Distributed]
45
+ @editor = true
46
+
47
+ @token_lock = Mutex.new
48
+ @token_lock_signal = ConditionVariable.new
49
+
50
+ super
51
+ end
52
+
53
+ def do_wrap(base_object, create) # :nodoc:
54
+ # It is allowed to add objects in a transaction only if
55
+ # * the object is not distribuable. It means that we are
56
+ # annotating *locally* remote tasks (like it is done for
57
+ # ConnectionTask for instance).
58
+ # * the object is owned by the transaction owners
59
+ if create && (base_object.distribute? && !(base_object.owners - owners).empty?)
60
+ raise OwnershipError, "plan owners #{owners} do not own #{base_object}: #{base_object.owners}"
61
+ end
62
+
63
+ temporarily_subscribed = !base_object.updated?
64
+ if temporarily_subscribed
65
+ peer = base_object.owners.first
66
+ base_object = peer.subscribe(base_object)
67
+ end
68
+
69
+ if object = super
70
+ object.extend DistributedObject
71
+ if !Distributed.updating?(self) && object.root_object? && base_object.distribute?
72
+ # The new proxy has been sent to remote hosts since it
73
+ # has been discovered in the transaction. Nonetheless,
74
+ # we don't want to return from #wrap until we know its
75
+ # sibling. Add a synchro point to wait for that
76
+ updated_peers.each do |peer|
77
+ peer.synchro_point
78
+ end
79
+ end
80
+ end
81
+
82
+ object
83
+
84
+ ensure
85
+ if temporarily_subscribed
86
+ peer.unsubscribe(base_object)
87
+ end
88
+ end
89
+
90
+ def copy_object_relations(object, proxy) # :nodoc:
91
+ # If the transaction is being updated, it means that we are
92
+ # discovering the new transaction. In that case, no need to
93
+ # discover the plan relations since our peer will send us all
94
+ # transaction relations
95
+ unless Distributed.updating?(self)
96
+ super
97
+ end
98
+ end
99
+
100
+ # Checks that +peer+ can be removed from the list of owners
101
+ def prepare_remove_owner(peer)
102
+ known_tasks.each do |t|
103
+ t = t.__getobj__ if t.respond_to?(:__getobj__)
104
+ if peer.owns?(t) && t.distribute?
105
+ raise OwnershipError, "#{peer} still owns tasks in the transaction (#{t})"
106
+ end
107
+ end
108
+ nil
109
+ end
110
+
111
+ # Announces the transaction on +peer+ or, if +peer+ is nil, to all
112
+ # owners who don't know about it yet. This operation is
113
+ # asynchronous, so the block, if given, will be called for each
114
+ # remote peer which has processed the message.
115
+ #
116
+ # See Peer#transaction_propose
117
+ def propose(peer = nil, &block)
118
+ if !self_owned?
119
+ raise OwnershipError, "cannot propose a transaction we don't own"
120
+ end
121
+
122
+ if peer
123
+ peer.transaction_propose(self, &block)
124
+ else
125
+ (owners - remote_siblings.keys).each do |peer|
126
+ if peer != Roby::Distributed
127
+ Distributed.debug "proposing #{self} to #{peer}"
128
+ propose(peer) do
129
+ yield(peer)
130
+ end
131
+ end
132
+ end
133
+ end
134
+ end
135
+
136
+ def discover(objects) # :nodoc:
137
+ if objects
138
+ events, tasks = partition_event_task(objects)
139
+ for object in (events || []) + (tasks || [])
140
+ unless Distributed.updating?(object) ||
141
+ Distributed.owns?(object) ||
142
+ (object.owners - owners).empty?
143
+
144
+ raise OwnershipError, "#{object} is not owned by #{owners.to_a} (#{object.owners.to_a})"
145
+ end
146
+ end
147
+ super(events) if events
148
+ super(tasks) if tasks
149
+ else
150
+ super
151
+ end
152
+ end
153
+
154
+ # call-seq:
155
+ # commit_transaction => self
156
+ #
157
+ # Commits the transaction. This method can only be called by the
158
+ # first editor of the transaction, once all owners have requested
159
+ # no additional modifications.
160
+ #
161
+ # Distributed commits are done in two steps, to make sure that all
162
+ # owners agree to actually perform it. First, the
163
+ # PeerServer#transaction_prepare_commit message is sent, which can
164
+ # return either nil or an error object.
165
+ #
166
+ # If all peers return nil, the actual commit is performed by
167
+ # sending the PeerServer#transaction_commit message. Otherwise, the
168
+ # commit is abandonned by sending the
169
+ # PeerServer#transaction_abandon_commit message to the transaction
170
+ # owners.
171
+ def commit_transaction(synchro = true)
172
+ if !self_owned?
173
+ raise OwnershipError, "cannot commit a transaction which is not owned locally. #{self} is owned by #{owners.to_a}"
174
+ elsif synchro
175
+ if !editor?
176
+ raise NotEditor, "not editor of this transaction"
177
+ elsif !first_editor?
178
+ raise NotEditor, "transactions are committed by their first editor"
179
+ elsif edition_reloop
180
+ raise NotReady, "transaction still needs editing"
181
+ end
182
+ end
183
+
184
+ if synchro
185
+ result = call_owners(:transaction_prepare_commit, self)
186
+ error = result.find_all { |_, returned| returned }
187
+ if !error.empty?
188
+ call_owners(:transaction_abandon_commit, self, error)
189
+ return false
190
+ else
191
+ call_owners(:transaction_commit, self)
192
+ return true
193
+ end
194
+ else
195
+ all_objects = known_tasks.dup
196
+ proxy_objects.each_key { |o| all_objects << o }
197
+ Distributed.update(self) do
198
+ Distributed.update_all(all_objects) do
199
+ super()
200
+ end
201
+ end
202
+ end
203
+
204
+ self
205
+ end
206
+
207
+ # Hook called when the transaction commit has been abandoned
208
+ # because a owner refused it. +reason+ is the value returned by
209
+ # this peer.
210
+ def abandoned_commit(error)
211
+ Distributed.debug { "abandoned commit of #{self} because of #{error}" }
212
+ super if defined? super
213
+ end
214
+
215
+ # call-seq:
216
+ # discard_transaction => self
217
+ #
218
+ # Discards the transaction. Unlike #commit_transaction, this can be
219
+ # called by any of the owners.
220
+ def discard_transaction(synchro = true) # :nodoc:
221
+ unless Distributed.owns?(self)
222
+ raise OwnershipError, "cannot discard a transaction which is not owned locally. #{self} is owned by #{owners}"
223
+ end
224
+
225
+ if synchro
226
+ call_siblings(:transaction_discard, self)
227
+ else super()
228
+ end
229
+ self
230
+ end
231
+
232
+ # True if we currently have the edition token
233
+ attr_predicate :editor?
234
+ # True if one of the editors request that the token is passed to
235
+ # them once more. The transaction can be committed only when all
236
+ # peers did not request that.
237
+ #
238
+ # See #release
239
+ attr_reader :edition_reloop
240
+
241
+ # True if this plan manager is the first editor, i.e. the plan
242
+ # manager whose responsibility is to manage the edition protocol.
243
+ def first_editor?
244
+ owners.first == Distributed
245
+ end
246
+ # Returns the peer which is after this plan manager in the edition
247
+ # order. The edition token will be sent to this peer by #release
248
+ def next_editor
249
+ if owners.last == Distributed
250
+ return owners.first
251
+ end
252
+
253
+ owners.each_cons(2) do |first, second|
254
+ if first == Distributed
255
+ return second
256
+ end
257
+ end
258
+ end
259
+
260
+ def edit!(reloop)
261
+ token_lock.synchronize do
262
+ @editor = true
263
+ @edition_reloop = reloop
264
+ token_lock_signal.broadcast
265
+ end
266
+ end
267
+
268
+ # Waits for the edition token. If a block is given, it is called
269
+ # when the token is achieved, and releases the token when the
270
+ # blocks returns.
271
+ def edit(reloop = false)
272
+ if Thread.current[:control_mutex_locked]
273
+ raise "cannot call #edit with the control mutex taken !"
274
+ end
275
+
276
+ token_lock.synchronize do
277
+ if !editor? # not the current editor
278
+ token_lock_signal.wait(token_lock)
279
+ end
280
+ end
281
+
282
+ if block_given?
283
+ begin
284
+ yield
285
+ ensure
286
+ release(reloop)
287
+ end
288
+ end
289
+ end
290
+
291
+ # Releases the edition token, giving it to the next owner. If
292
+ # +give_back+ is true, the local plan manager announces that it
293
+ # expects the token to be given back to it once more. The commit is
294
+ # allowed only when all peers have released the edition token
295
+ # without requesting it once more.
296
+ #
297
+ # It sends the #transaction_give_token to the peer returned by
298
+ # #next_editor.
299
+ #
300
+ # Raised NotEditor if the local plan manager is not the current
301
+ # transaction editor.
302
+ def release(give_back = false)
303
+ token_lock.synchronize do
304
+ if !editor?
305
+ raise NotEditor, "not editor"
306
+ else
307
+ reloop = if first_editor?
308
+ give_back
309
+ else
310
+ edition_reloop || give_back
311
+ end
312
+
313
+ return if owners.size == 1
314
+ @editor = false
315
+ next_editor.transaction_give_token(self, reloop)
316
+ true
317
+ end
318
+ end
319
+ end
320
+
321
+ # Intermediate representation of a Roby::Distributed::Transaction
322
+ # object, suitable for representing that transaction in the dRoby
323
+ # protocol.
324
+ class DRoby < Roby::BasicObject::DRoby
325
+ attr_reader :plan, :options
326
+ def initialize(remote_siblings, owners, plan, options)
327
+ super(remote_siblings, owners)
328
+ @plan, @options = plan, options
329
+ end
330
+
331
+ # Returns the local representation of this transaction, or
332
+ # raises InvalidRemoteOperation if none exists.
333
+ def proxy(peer)
334
+ raise InvalidRemoteOperation, "the transaction #{self} does not exist on #{peer.connection_space.name}"
335
+ end
336
+
337
+ # Create a local representation for this transaction.
338
+ def sibling(peer)
339
+ plan = peer.local_object(self.plan)
340
+ trsc = Roby::Distributed::Transaction.new(plan, peer.local_object(options))
341
+ update(peer, trsc)
342
+ trsc.instance_eval do
343
+ @editor = false
344
+ end
345
+ trsc
346
+ end
347
+
348
+ # Called when a new sibling has been created locally for a
349
+ # distributed transaction present on +peer+. +trsc+ is the
350
+ # local representation of this transaction.
351
+ #
352
+ # In practice, it announces the new transaction by calling the
353
+ # block stored in Distributed.transaction_handler (if there is
354
+ # one).
355
+ #
356
+ # See PeerServer#created_sibling
357
+ def created_sibling(peer, trsc)
358
+ Thread.new do
359
+ Thread.current.priority = 0
360
+ begin
361
+ Distributed.transaction_handler[trsc] if Distributed.transaction_handler
362
+ rescue
363
+ Roby.warn "transaction handler for #{trsc} failed"
364
+ Roby.warn $!.full_message
365
+ trsc.invalidate("failed transaction handler")
366
+ end
367
+ end
368
+ end
369
+
370
+ def to_s # :nodoc:
371
+ "#<dRoby:Trsc#{remote_siblings_to_s} owners=#{owners_to_s} plan=#{plan}>"
372
+ end
373
+ end
374
+
375
+ # Returns a representation of +self+ which can be used to reference
376
+ # it in our communication with +dest+.
377
+ def droby_dump(dest) # :nodoc:
378
+ if remote_siblings.has_key?(dest)
379
+ remote_id
380
+ else
381
+ DRoby.new(remote_siblings.droby_dump(dest), owners.droby_dump(dest),
382
+ plan.droby_dump(dest),
383
+ options.droby_dump(dest))
384
+ end
385
+ end
386
+ end
387
+
388
+ module Roby::Transaction::Proxy
389
+ def droby_dump(dest) # :nodoc:
390
+ DRoby.new(remote_siblings.droby_dump(dest), owners.droby_dump(dest),
391
+ Distributed.format(@__getobj__, dest), Distributed.format(transaction, dest))
392
+ end
393
+
394
+ # A representation of a distributed transaction proxy suitable for
395
+ # communication with the remote plan managers.
396
+ class DRoby < Roby::BasicObject::DRoby
397
+ # The DRoby version of the underlying object
398
+ attr_reader :real_object
399
+ # The DRoby representation of the transaction
400
+ attr_reader :transaction
401
+ # Create a new dRoby representation for a transaction proxy.
402
+ # The proxy currently has the given set of remote siblings and
403
+ # owners, is a view on the given real object and is stored in
404
+ # the given transaction. All objects must already be formatted
405
+ # for marshalling using Distributed.format.
406
+ def initialize(remote_siblings, owners, real_object, transaction)
407
+ super(remote_siblings, owners)
408
+ @real_object, @transaction = real_object, transaction
409
+ end
410
+
411
+ # Returns the local object matching this dRoby-formatted
412
+ # representation of a remote transaction proxy present on
413
+ # +peer+.
414
+ def proxy(peer)
415
+ local_real = peer.local_object(real_object)
416
+ local_object = nil
417
+ local_transaction = peer.local_object(transaction)
418
+ Distributed.update(local_transaction) do
419
+ local_object = local_transaction[local_real]
420
+ end
421
+ local_object
422
+ end
423
+
424
+ def to_s # :nodoc:
425
+ "#<dRoby:mTrscProxy#{remote_siblings} transaction=#{transaction} real_object=#{real_object}>"
426
+ end
427
+ end
428
+ end
429
+
430
+ class Roby::Transactions::TaskEventGenerator
431
+ # A task event generator has no remote sibling. It is always
432
+ # referenced through its own task.
433
+ def has_sibling?(peer); false end
434
+ # Create an intermediate object which represent this task event
435
+ # generator in our communication with +dest+
436
+ def droby_dump(dest)
437
+ Roby::TaskEventGenerator::DRoby.new(controlable?, happened?, Distributed.format(task, dest), symbol)
438
+ end
439
+ end
440
+
441
+ class PeerServer
442
+ # Message received when the 'prepare' stage of the transaction
443
+ # commit is requested.
444
+ def transaction_prepare_commit(trsc)
445
+ trsc = peer.local_object(trsc)
446
+ peer.connection_space.transaction_prepare_commit(trsc)
447
+ trsc.freezed!
448
+ nil
449
+ end
450
+ # Message received when a transaction commit is requested.
451
+ def transaction_commit(trsc)
452
+ trsc = peer.local_object(trsc)
453
+ peer.connection_space.transaction_commit(trsc)
454
+ nil
455
+ end
456
+ # Message received when a transaction commit is to be abandonned.
457
+ def transaction_abandon_commit(trsc, error)
458
+ trsc = peer.local_object(trsc)
459
+ peer.connection_space.transaction_abandon_commit(trsc, error)
460
+ nil
461
+ end
462
+ # Message received when a transaction discard is requested.
463
+ def transaction_discard(trsc)
464
+ trsc = peer.local_object(trsc)
465
+ peer.connection_space.transaction_discard(trsc)
466
+ nil
467
+ end
468
+ # Message received when the transaction edition token is given to
469
+ # this plan manager.
470
+ def transaction_give_token(trsc, needs_edition)
471
+ trsc = peer.local_object(trsc)
472
+ trsc.edit!(needs_edition)
473
+ nil
474
+ end
475
+ end
476
+
477
+ class Peer
478
+ # Send the information related to the given transaction in the
479
+ # remote plan manager.
480
+ def transaction_propose(trsc)
481
+ synchro_point
482
+ create_sibling(trsc)
483
+ nil
484
+ end
485
+
486
+ # Give the edition token on +trsc+ to the given peer.
487
+ # +needs_edition+ is a flag which, if true, requests that the token
488
+ # is given back at least once to the local plan manager.
489
+ #
490
+ # Do not use this directly, it is part of the multi-robot
491
+ # communication protocol. Use the edition-related methods on
492
+ # Distributed::Transaction instead.
493
+ def transaction_give_token(trsc, needs_edition)
494
+ call(:transaction_give_token, trsc, needs_edition)
495
+ end
496
+ end
497
+ end
498
+ end