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,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