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.
- data/.gitignore +29 -0
- data/History.txt +4 -0
- data/License-fr.txt +519 -0
- data/License.txt +515 -0
- data/Manifest.txt +245 -0
- data/NOTES +4 -0
- data/README.txt +163 -0
- data/Rakefile +161 -0
- data/TODO.txt +146 -0
- data/app/README.txt +24 -0
- data/app/Rakefile +8 -0
- data/app/config/ROBOT.rb +5 -0
- data/app/config/app.yml +91 -0
- data/app/config/init.rb +7 -0
- data/app/config/roby.yml +3 -0
- data/app/controllers/.gitattributes +0 -0
- data/app/controllers/ROBOT.rb +2 -0
- data/app/data/.gitattributes +0 -0
- data/app/planners/ROBOT/main.rb +6 -0
- data/app/planners/main.rb +5 -0
- data/app/scripts/distributed +3 -0
- data/app/scripts/generate/bookmarks +3 -0
- data/app/scripts/replay +3 -0
- data/app/scripts/results +3 -0
- data/app/scripts/run +3 -0
- data/app/scripts/server +3 -0
- data/app/scripts/shell +3 -0
- data/app/scripts/test +3 -0
- data/app/tasks/.gitattributes +0 -0
- data/app/tasks/ROBOT/.gitattributes +0 -0
- data/bin/roby +210 -0
- data/bin/roby-log +168 -0
- data/bin/roby-shell +25 -0
- data/doc/images/event_generalization.png +0 -0
- data/doc/images/exception_propagation_1.png +0 -0
- data/doc/images/exception_propagation_2.png +0 -0
- data/doc/images/exception_propagation_3.png +0 -0
- data/doc/images/exception_propagation_4.png +0 -0
- data/doc/images/exception_propagation_5.png +0 -0
- data/doc/images/replay_handler_error.png +0 -0
- data/doc/images/replay_handler_error_0.png +0 -0
- data/doc/images/replay_handler_error_1.png +0 -0
- data/doc/images/roby_cycle_overview.png +0 -0
- data/doc/images/roby_replay_02.png +0 -0
- data/doc/images/roby_replay_03.png +0 -0
- data/doc/images/roby_replay_04.png +0 -0
- data/doc/images/roby_replay_event_representation.png +0 -0
- data/doc/images/roby_replay_first_state.png +0 -0
- data/doc/images/roby_replay_relations.png +0 -0
- data/doc/images/roby_replay_startup.png +0 -0
- data/doc/images/task_event_generalization.png +0 -0
- data/doc/papers.rdoc +11 -0
- data/doc/styles/allison.css +314 -0
- data/doc/styles/allison.js +316 -0
- data/doc/styles/allison.rb +276 -0
- data/doc/styles/jamis.rb +593 -0
- data/doc/tutorials/01-GettingStarted.rdoc +86 -0
- data/doc/tutorials/02-GoForward.rdoc +220 -0
- data/doc/tutorials/03-PlannedPath.rdoc +268 -0
- data/doc/tutorials/04-EventPropagation.rdoc +236 -0
- data/doc/tutorials/05-ErrorHandling.rdoc +319 -0
- data/doc/tutorials/06-Overview.rdoc +40 -0
- data/doc/videos.rdoc +69 -0
- data/ext/droby/dump.cc +175 -0
- data/ext/droby/extconf.rb +3 -0
- data/ext/graph/algorithm.cc +746 -0
- data/ext/graph/extconf.rb +7 -0
- data/ext/graph/graph.cc +529 -0
- data/ext/graph/graph.hh +183 -0
- data/ext/graph/iterator_sequence.hh +102 -0
- data/ext/graph/undirected_dfs.hh +226 -0
- data/ext/graph/undirected_graph.hh +421 -0
- data/lib/roby.rb +41 -0
- data/lib/roby/app.rb +870 -0
- data/lib/roby/app/rake.rb +56 -0
- data/lib/roby/app/run.rb +14 -0
- data/lib/roby/app/scripts/distributed.rb +13 -0
- data/lib/roby/app/scripts/generate/bookmarks.rb +162 -0
- data/lib/roby/app/scripts/replay.rb +31 -0
- data/lib/roby/app/scripts/results.rb +15 -0
- data/lib/roby/app/scripts/run.rb +26 -0
- data/lib/roby/app/scripts/server.rb +18 -0
- data/lib/roby/app/scripts/shell.rb +88 -0
- data/lib/roby/app/scripts/test.rb +40 -0
- data/lib/roby/basic_object.rb +151 -0
- data/lib/roby/config.rb +5 -0
- data/lib/roby/control.rb +747 -0
- data/lib/roby/decision_control.rb +17 -0
- data/lib/roby/distributed.rb +32 -0
- data/lib/roby/distributed/base.rb +440 -0
- data/lib/roby/distributed/communication.rb +871 -0
- data/lib/roby/distributed/connection_space.rb +592 -0
- data/lib/roby/distributed/distributed_object.rb +206 -0
- data/lib/roby/distributed/drb.rb +62 -0
- data/lib/roby/distributed/notifications.rb +539 -0
- data/lib/roby/distributed/peer.rb +550 -0
- data/lib/roby/distributed/protocol.rb +529 -0
- data/lib/roby/distributed/proxy.rb +343 -0
- data/lib/roby/distributed/subscription.rb +311 -0
- data/lib/roby/distributed/transaction.rb +498 -0
- data/lib/roby/event.rb +897 -0
- data/lib/roby/exceptions.rb +234 -0
- data/lib/roby/executives/simple.rb +30 -0
- data/lib/roby/graph.rb +166 -0
- data/lib/roby/interface.rb +390 -0
- data/lib/roby/log.rb +3 -0
- data/lib/roby/log/chronicle.rb +303 -0
- data/lib/roby/log/console.rb +72 -0
- data/lib/roby/log/data_stream.rb +197 -0
- data/lib/roby/log/dot.rb +279 -0
- data/lib/roby/log/event_stream.rb +151 -0
- data/lib/roby/log/file.rb +340 -0
- data/lib/roby/log/gui/basic_display.ui +83 -0
- data/lib/roby/log/gui/chronicle.rb +26 -0
- data/lib/roby/log/gui/chronicle_view.rb +40 -0
- data/lib/roby/log/gui/chronicle_view.ui +70 -0
- data/lib/roby/log/gui/data_displays.rb +172 -0
- data/lib/roby/log/gui/data_displays.ui +155 -0
- data/lib/roby/log/gui/notifications.rb +26 -0
- data/lib/roby/log/gui/relations.rb +248 -0
- data/lib/roby/log/gui/relations.ui +123 -0
- data/lib/roby/log/gui/relations_view.rb +185 -0
- data/lib/roby/log/gui/relations_view.ui +149 -0
- data/lib/roby/log/gui/replay.rb +327 -0
- data/lib/roby/log/gui/replay_controls.rb +200 -0
- data/lib/roby/log/gui/replay_controls.ui +259 -0
- data/lib/roby/log/gui/runtime.rb +130 -0
- data/lib/roby/log/hooks.rb +185 -0
- data/lib/roby/log/logger.rb +202 -0
- data/lib/roby/log/notifications.rb +244 -0
- data/lib/roby/log/plan_rebuilder.rb +470 -0
- data/lib/roby/log/relations.rb +1056 -0
- data/lib/roby/log/server.rb +550 -0
- data/lib/roby/log/sqlite.rb +47 -0
- data/lib/roby/log/timings.rb +164 -0
- data/lib/roby/plan-object.rb +247 -0
- data/lib/roby/plan.rb +762 -0
- data/lib/roby/planning.rb +13 -0
- data/lib/roby/planning/loops.rb +302 -0
- data/lib/roby/planning/model.rb +906 -0
- data/lib/roby/planning/task.rb +151 -0
- data/lib/roby/propagation.rb +562 -0
- data/lib/roby/query.rb +619 -0
- data/lib/roby/relations.rb +583 -0
- data/lib/roby/relations/conflicts.rb +70 -0
- data/lib/roby/relations/ensured.rb +20 -0
- data/lib/roby/relations/error_handling.rb +23 -0
- data/lib/roby/relations/events.rb +9 -0
- data/lib/roby/relations/executed_by.rb +193 -0
- data/lib/roby/relations/hierarchy.rb +239 -0
- data/lib/roby/relations/influence.rb +10 -0
- data/lib/roby/relations/planned_by.rb +63 -0
- data/lib/roby/robot.rb +7 -0
- data/lib/roby/standard_errors.rb +218 -0
- data/lib/roby/state.rb +5 -0
- data/lib/roby/state/events.rb +221 -0
- data/lib/roby/state/information.rb +55 -0
- data/lib/roby/state/pos.rb +110 -0
- data/lib/roby/state/shapes.rb +32 -0
- data/lib/roby/state/state.rb +353 -0
- data/lib/roby/support.rb +92 -0
- data/lib/roby/task-operations.rb +182 -0
- data/lib/roby/task.rb +1618 -0
- data/lib/roby/test/common.rb +399 -0
- data/lib/roby/test/distributed.rb +214 -0
- data/lib/roby/test/tasks/empty_task.rb +9 -0
- data/lib/roby/test/tasks/goto.rb +36 -0
- data/lib/roby/test/tasks/simple_task.rb +23 -0
- data/lib/roby/test/testcase.rb +519 -0
- data/lib/roby/test/tools.rb +160 -0
- data/lib/roby/thread_task.rb +87 -0
- data/lib/roby/transactions.rb +462 -0
- data/lib/roby/transactions/proxy.rb +292 -0
- data/lib/roby/transactions/updates.rb +139 -0
- data/plugins/fault_injection/History.txt +4 -0
- data/plugins/fault_injection/README.txt +37 -0
- data/plugins/fault_injection/Rakefile +18 -0
- data/plugins/fault_injection/TODO.txt +0 -0
- data/plugins/fault_injection/app.rb +52 -0
- data/plugins/fault_injection/fault_injection.rb +89 -0
- data/plugins/fault_injection/test/test_fault_injection.rb +84 -0
- data/plugins/subsystems/README.txt +40 -0
- data/plugins/subsystems/Rakefile +18 -0
- data/plugins/subsystems/app.rb +171 -0
- data/plugins/subsystems/test/app/README +24 -0
- data/plugins/subsystems/test/app/Rakefile +8 -0
- data/plugins/subsystems/test/app/config/app.yml +71 -0
- data/plugins/subsystems/test/app/config/init.rb +9 -0
- data/plugins/subsystems/test/app/config/roby.yml +3 -0
- data/plugins/subsystems/test/app/planners/main.rb +20 -0
- data/plugins/subsystems/test/app/scripts/distributed +3 -0
- data/plugins/subsystems/test/app/scripts/replay +3 -0
- data/plugins/subsystems/test/app/scripts/results +3 -0
- data/plugins/subsystems/test/app/scripts/run +3 -0
- data/plugins/subsystems/test/app/scripts/server +3 -0
- data/plugins/subsystems/test/app/scripts/shell +3 -0
- data/plugins/subsystems/test/app/scripts/test +3 -0
- data/plugins/subsystems/test/app/tasks/services.rb +15 -0
- data/plugins/subsystems/test/test_subsystems.rb +71 -0
- data/test/distributed/test_communication.rb +178 -0
- data/test/distributed/test_connection.rb +282 -0
- data/test/distributed/test_execution.rb +373 -0
- data/test/distributed/test_mixed_plan.rb +341 -0
- data/test/distributed/test_plan_notifications.rb +238 -0
- data/test/distributed/test_protocol.rb +516 -0
- data/test/distributed/test_query.rb +102 -0
- data/test/distributed/test_remote_plan.rb +491 -0
- data/test/distributed/test_transaction.rb +463 -0
- data/test/mockups/tasks.rb +27 -0
- data/test/planning/test_loops.rb +380 -0
- data/test/planning/test_model.rb +427 -0
- data/test/planning/test_task.rb +106 -0
- data/test/relations/test_conflicts.rb +42 -0
- data/test/relations/test_ensured.rb +38 -0
- data/test/relations/test_executed_by.rb +149 -0
- data/test/relations/test_hierarchy.rb +158 -0
- data/test/relations/test_planned_by.rb +54 -0
- data/test/suite_core.rb +24 -0
- data/test/suite_distributed.rb +9 -0
- data/test/suite_planning.rb +3 -0
- data/test/suite_relations.rb +8 -0
- data/test/test_bgl.rb +508 -0
- data/test/test_control.rb +399 -0
- data/test/test_event.rb +894 -0
- data/test/test_exceptions.rb +592 -0
- data/test/test_interface.rb +37 -0
- data/test/test_log.rb +114 -0
- data/test/test_log_server.rb +132 -0
- data/test/test_plan.rb +584 -0
- data/test/test_propagation.rb +210 -0
- data/test/test_query.rb +266 -0
- data/test/test_relations.rb +180 -0
- data/test/test_state.rb +414 -0
- data/test/test_support.rb +16 -0
- data/test/test_task.rb +938 -0
- data/test/test_testcase.rb +122 -0
- data/test/test_thread_task.rb +73 -0
- data/test/test_transactions.rb +569 -0
- data/test/test_transactions_proxy.rb +198 -0
- 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
|