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,343 @@
|
|
|
1
|
+
require 'roby'
|
|
2
|
+
require 'roby/distributed/protocol'
|
|
3
|
+
module Roby
|
|
4
|
+
class BasicObject::DRoby
|
|
5
|
+
# The set of remote siblings for that object, as known by the peer who
|
|
6
|
+
# called #droby_dump. This is used to match object identity among plan
|
|
7
|
+
# managers.
|
|
8
|
+
attr_reader :remote_siblings
|
|
9
|
+
# The set of owners for that object.
|
|
10
|
+
attr_reader :owners
|
|
11
|
+
# Create a BasicObject::DRoby object with the given information
|
|
12
|
+
def initialize(remote_siblings, owners)
|
|
13
|
+
@remote_siblings, @owners = remote_siblings, owners
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def remote_siblings_to_s # :nodoc:
|
|
17
|
+
"{ " << remote_siblings.map { |peer, id| id.to_s(peer) }.join(", ") << " }"
|
|
18
|
+
end
|
|
19
|
+
def owners_to_s # :nodoc:
|
|
20
|
+
"[ " << owners.map { |peer| peer.name }.join(", ") << " ]"
|
|
21
|
+
end
|
|
22
|
+
def to_s # :nodoc:
|
|
23
|
+
"#<dRoby:BasicObject#{remote_siblings_to_s} owners=#{owners_to_s}>"
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
# If we know of a sibling on +peer+, return it. Otherwise, raises RemotePeerMismatch.
|
|
27
|
+
def sibling_on(peer)
|
|
28
|
+
remote_siblings.each do |m_peer, remote_id|
|
|
29
|
+
if m_peer.peer_id == peer.remote_id
|
|
30
|
+
return remote_id
|
|
31
|
+
end
|
|
32
|
+
end
|
|
33
|
+
raise RemotePeerMismatch, "#{self} has no known sibling on #{peer}"
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
# Update an existing proxy, using the information stored in this DRoby
|
|
37
|
+
# object.
|
|
38
|
+
def update(peer, proxy)
|
|
39
|
+
proxy.owners.clear
|
|
40
|
+
owners.each do |m_owner|
|
|
41
|
+
proxy.owners << peer.local_object(m_owner)
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
remote_siblings.each do |m_peer_sibling, remote_id|
|
|
45
|
+
peer_sibling = peer.local_object(m_peer_sibling)
|
|
46
|
+
|
|
47
|
+
if current = proxy.remote_siblings[peer_sibling]
|
|
48
|
+
if current != remote_id && peer_sibling != Roby::Distributed
|
|
49
|
+
raise "inconsistency for sibling on #{peer_sibling}: #{proxy} has #{current} while #{self} has #{remote_id}"
|
|
50
|
+
end
|
|
51
|
+
else
|
|
52
|
+
proxy.sibling_of(remote_id, peer_sibling)
|
|
53
|
+
end
|
|
54
|
+
end
|
|
55
|
+
end
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
# Base class for all marshalled plan objects.
|
|
59
|
+
class PlanObject::DRoby < BasicObject::DRoby
|
|
60
|
+
# The model for this plan object
|
|
61
|
+
attr_reader :model
|
|
62
|
+
# The plan of this object
|
|
63
|
+
attr_reader :plan
|
|
64
|
+
|
|
65
|
+
# Create a DRoby object with the given information. See also
|
|
66
|
+
# BasicObject::DRoby
|
|
67
|
+
def initialize(remote_siblings, owners, model, plan)
|
|
68
|
+
super(remote_siblings, owners)
|
|
69
|
+
@model, @plan = model, plan
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
def to_s # :nodoc:
|
|
73
|
+
"#<dRoby:#{model.ancestors.first.first}#{remote_siblings_to_s} plan=#{plan} owners=#{owners_to_s}>"
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
# Update an existing proxy, using the information stored in this DRoby
|
|
77
|
+
# object.
|
|
78
|
+
def update(peer, proxy)
|
|
79
|
+
super(peer, proxy)
|
|
80
|
+
|
|
81
|
+
if proxy.root_object?
|
|
82
|
+
if self.plan
|
|
83
|
+
plan = peer.local_object(self.plan)
|
|
84
|
+
return if proxy.plan == plan
|
|
85
|
+
Distributed.update_all([plan, proxy]) do
|
|
86
|
+
plan.discover(proxy)
|
|
87
|
+
end
|
|
88
|
+
end
|
|
89
|
+
end
|
|
90
|
+
end
|
|
91
|
+
end
|
|
92
|
+
|
|
93
|
+
class EventGenerator
|
|
94
|
+
def _dump(lvl) # :nodoc:
|
|
95
|
+
Marshal.dump(remote_id)
|
|
96
|
+
end
|
|
97
|
+
def self._load(str) # :nodoc:
|
|
98
|
+
Marshal.load(str)
|
|
99
|
+
end
|
|
100
|
+
|
|
101
|
+
# Returns an intermediate representation of +self+ suitable to be sent
|
|
102
|
+
# to the +dest+ peer.
|
|
103
|
+
def droby_dump(dest)
|
|
104
|
+
DRoby.new(remote_siblings.droby_dump(dest), owners.droby_dump(dest),
|
|
105
|
+
model.droby_dump(dest), plan.droby_dump(dest),
|
|
106
|
+
controlable?, happened?)
|
|
107
|
+
end
|
|
108
|
+
|
|
109
|
+
# An intermediate representation of EventGenerator objects suitable to
|
|
110
|
+
# be sent to our peers.
|
|
111
|
+
class DRoby < PlanObject::DRoby
|
|
112
|
+
# True if the generator is controlable
|
|
113
|
+
attr_reader :controlable
|
|
114
|
+
# True if the generator has already been emitted once at the time
|
|
115
|
+
# EventGenerator#droby_dump has been called.
|
|
116
|
+
attr_reader :happened
|
|
117
|
+
|
|
118
|
+
# Create a DRoby object with the given information. See also
|
|
119
|
+
# PlanObject::DRoby
|
|
120
|
+
def initialize(remote_siblings, owners, model, plan, controlable, happened)
|
|
121
|
+
super(remote_siblings, owners, model, plan)
|
|
122
|
+
@controlable, @happened = controlable, happened
|
|
123
|
+
end
|
|
124
|
+
|
|
125
|
+
# Create a new proxy which maps the object of +peer+ represented by
|
|
126
|
+
# this communication intermediate.
|
|
127
|
+
def proxy(peer)
|
|
128
|
+
local_object = peer.local_object(model).new
|
|
129
|
+
if controlable
|
|
130
|
+
local_object.command = lambda { }
|
|
131
|
+
end
|
|
132
|
+
local_object
|
|
133
|
+
end
|
|
134
|
+
|
|
135
|
+
# Updates an already existing proxy using the information contained
|
|
136
|
+
# in this object.
|
|
137
|
+
def update(peer, proxy)
|
|
138
|
+
super
|
|
139
|
+
if happened && !proxy.happened?
|
|
140
|
+
proxy.instance_eval { @happened = true }
|
|
141
|
+
end
|
|
142
|
+
end
|
|
143
|
+
end
|
|
144
|
+
end
|
|
145
|
+
|
|
146
|
+
class TaskEventGenerator
|
|
147
|
+
def _dump(lvl) # :nodoc:
|
|
148
|
+
Marshal.dump(remote_id)
|
|
149
|
+
end
|
|
150
|
+
def self._load(str) # :nodoc:
|
|
151
|
+
Marshal.load(str)
|
|
152
|
+
end
|
|
153
|
+
|
|
154
|
+
# Returns an intermediate representation of +self+ suitable to be sent
|
|
155
|
+
# to the +dest+ peer.
|
|
156
|
+
def droby_dump(dest)
|
|
157
|
+
DRoby.new(controlable?, happened?, Distributed.format(task, dest), symbol)
|
|
158
|
+
end
|
|
159
|
+
|
|
160
|
+
# An intermediate representation of TaskEventGenerator objects suitable
|
|
161
|
+
# to be sent to our peers.
|
|
162
|
+
class DRoby
|
|
163
|
+
# True if the generator is controlable
|
|
164
|
+
attr_reader :controlable
|
|
165
|
+
# True if the generator has already emitted once at the time
|
|
166
|
+
# TaskEventGenerator#droby_dump has been called.
|
|
167
|
+
attr_reader :happened
|
|
168
|
+
# An object representing the task of this generator on our remote
|
|
169
|
+
# peer.
|
|
170
|
+
attr_reader :task
|
|
171
|
+
# The event name
|
|
172
|
+
attr_reader :symbol
|
|
173
|
+
|
|
174
|
+
# Create a new DRoby object with the given information
|
|
175
|
+
def initialize(controlable, happened, task, symbol)
|
|
176
|
+
@controlable = controlable
|
|
177
|
+
@happened = happened
|
|
178
|
+
@task = task
|
|
179
|
+
@symbol = symbol
|
|
180
|
+
end
|
|
181
|
+
|
|
182
|
+
def to_s # :nodoc:
|
|
183
|
+
if task.respond_to?(:model)
|
|
184
|
+
"#<dRoby:#{task.model.ancestors.first.first}/#{symbol}#{task.remote_siblings_to_s} task_arguments=#{task.arguments} plan=#{task.plan} owners=#{task.owners_to_s}>"
|
|
185
|
+
else
|
|
186
|
+
"#<dRoby:#{task}/#{symbol}>"
|
|
187
|
+
end
|
|
188
|
+
end
|
|
189
|
+
|
|
190
|
+
# Create a new proxy which maps the object of +peer+ represented by
|
|
191
|
+
# this communication intermediate.
|
|
192
|
+
def proxy(peer)
|
|
193
|
+
task = peer.local_object(self.task)
|
|
194
|
+
unless task.has_event?(symbol)
|
|
195
|
+
Roby::Distributed.debug { "ignoring #{self}: #{symbol} is not known on #{task}" }
|
|
196
|
+
Roby::Distributed.ignore!
|
|
197
|
+
end
|
|
198
|
+
event = task.event(symbol)
|
|
199
|
+
|
|
200
|
+
if happened && !event.happened?
|
|
201
|
+
event.instance_eval { @happened = true }
|
|
202
|
+
end
|
|
203
|
+
event
|
|
204
|
+
end
|
|
205
|
+
end
|
|
206
|
+
end
|
|
207
|
+
|
|
208
|
+
class Task
|
|
209
|
+
def _dump(lvl) # :nodoc:
|
|
210
|
+
Marshal.dump(remote_id)
|
|
211
|
+
end
|
|
212
|
+
def self._load(str) # :nodoc:
|
|
213
|
+
Marshal.load(str)
|
|
214
|
+
end
|
|
215
|
+
|
|
216
|
+
# Returns an intermediate representation of +self+ suitable to be sent
|
|
217
|
+
# to the +dest+ peer.
|
|
218
|
+
def droby_dump(dest)
|
|
219
|
+
DRoby.new(remote_siblings.droby_dump(dest), owners.droby_dump(dest),
|
|
220
|
+
model.droby_dump(dest), plan.droby_dump(dest),
|
|
221
|
+
Distributed.format(arguments, dest), Distributed.format(data, dest),
|
|
222
|
+
:mission => mission?, :started => started?,
|
|
223
|
+
:finished => finished?, :success => success?)
|
|
224
|
+
end
|
|
225
|
+
|
|
226
|
+
# An intermediate representation of Task objects suitable
|
|
227
|
+
# to be sent to our peers.
|
|
228
|
+
class DRoby < PlanObject::DRoby
|
|
229
|
+
# The set of dRoby-formatted arguments
|
|
230
|
+
attr_reader :arguments
|
|
231
|
+
# The task's internal data
|
|
232
|
+
attr_reader :data
|
|
233
|
+
# A set of boolean flags which describe the task's status. It is a
|
|
234
|
+
# symbol => bool flag where the following parameters are save:
|
|
235
|
+
# started:: if the task has started
|
|
236
|
+
# finished:: if the task has finished
|
|
237
|
+
# success:: if the task has finished with success
|
|
238
|
+
# mission:: if the task is a mission in its plan
|
|
239
|
+
attr_reader :flags
|
|
240
|
+
|
|
241
|
+
# Create a new DRoby object with the given information
|
|
242
|
+
# See also PlanObject::DRoby.new
|
|
243
|
+
def initialize(remote_siblings, owners, model, plan, arguments, data, flags)
|
|
244
|
+
super(remote_siblings, owners, model, plan)
|
|
245
|
+
@arguments, @data, @flags = arguments, data, flags
|
|
246
|
+
end
|
|
247
|
+
|
|
248
|
+
def to_s # :nodoc:
|
|
249
|
+
"#<dRoby:#{model.ancestors.first.first}#{remote_siblings_to_s} plan=#{plan} owners=#{owners_to_s} arguments=#{arguments}>"
|
|
250
|
+
end
|
|
251
|
+
|
|
252
|
+
# Create a new proxy which maps the object of +peer+ represented by
|
|
253
|
+
# this communication intermediate.
|
|
254
|
+
def proxy(peer)
|
|
255
|
+
arguments = peer.local_object(self.arguments)
|
|
256
|
+
peer.local_object(model).new(arguments) do
|
|
257
|
+
Roby::Distributed.updated_objects << self
|
|
258
|
+
end
|
|
259
|
+
|
|
260
|
+
ensure
|
|
261
|
+
Roby::Distributed.updated_objects.delete(self)
|
|
262
|
+
end
|
|
263
|
+
|
|
264
|
+
# Updates an already existing proxy using the information contained
|
|
265
|
+
# in this object.
|
|
266
|
+
def update(peer, task)
|
|
267
|
+
super
|
|
268
|
+
|
|
269
|
+
task.started = flags[:started]
|
|
270
|
+
task.finished = flags[:finished]
|
|
271
|
+
task.success = flags[:success]
|
|
272
|
+
|
|
273
|
+
if task.mission? != flags[:mission]
|
|
274
|
+
plan = peer.local_object(self.plan) || Roby.plan
|
|
275
|
+
if plan.owns?(task)
|
|
276
|
+
if flags[:mission]
|
|
277
|
+
plan.insert(task)
|
|
278
|
+
else
|
|
279
|
+
plan.discard(task)
|
|
280
|
+
end
|
|
281
|
+
else
|
|
282
|
+
task.mission = flags[:mission]
|
|
283
|
+
end
|
|
284
|
+
end
|
|
285
|
+
|
|
286
|
+
task.arguments.merge!(peer.proxy(arguments))
|
|
287
|
+
task.instance_variable_set("@data", peer.proxy(data))
|
|
288
|
+
end
|
|
289
|
+
end
|
|
290
|
+
end
|
|
291
|
+
|
|
292
|
+
class Plan
|
|
293
|
+
# Returns an intermediate representation of +self+ suitable to be sent
|
|
294
|
+
# to the +dest+ peer.
|
|
295
|
+
def droby_dump(dest)
|
|
296
|
+
@__droby_marshalled__ ||= DRoby.new(Roby::Distributed.droby_dump(dest), remote_id)
|
|
297
|
+
end
|
|
298
|
+
|
|
299
|
+
# An intermediate representation of Plan objects suitable to be sent to
|
|
300
|
+
# our peers.
|
|
301
|
+
#
|
|
302
|
+
# FIXME: It assumes that the only Plan object sent to the peers is
|
|
303
|
+
# actually the main plan of the plan manager. We must fix that.
|
|
304
|
+
class DRoby
|
|
305
|
+
# The peer which manages this plan
|
|
306
|
+
attr_accessor :peer
|
|
307
|
+
# The plan remote_id
|
|
308
|
+
attr_accessor :id
|
|
309
|
+
# Create a DRoby representation of a plan object with the given
|
|
310
|
+
# parameters
|
|
311
|
+
def initialize(peer, id); @peer, @id = peer, id end
|
|
312
|
+
# Create a new proxy which maps the object of +peer+ represented by
|
|
313
|
+
# this communication intermediate.
|
|
314
|
+
def proxy(peer); peer.connection_space.plan end
|
|
315
|
+
def to_s # :nodoc:
|
|
316
|
+
"#<dRoby:Plan #{id.to_s(peer)}>"
|
|
317
|
+
end
|
|
318
|
+
# The set of remote siblings for that object. This is used to avoid
|
|
319
|
+
# creating proxies when not needed. See
|
|
320
|
+
# PlanObject::DRoby#remote_siblings.
|
|
321
|
+
def remote_siblings; @remote_siblings ||= Hash[peer, id] end
|
|
322
|
+
# If +peer+ is the plan's owner, returns #id. Otherwise, raises
|
|
323
|
+
# RemotePeerMismatch. This is used to avoid creating proxies when not
|
|
324
|
+
# needed. See BasicObject::DRoby#sibling_on.
|
|
325
|
+
def sibling_on(peer)
|
|
326
|
+
if peer.remote_id == self.peer.peer_id then id
|
|
327
|
+
else raise RemotePeerMismatch, "no known sibling for #{self} on #{peer}"
|
|
328
|
+
end
|
|
329
|
+
end
|
|
330
|
+
end
|
|
331
|
+
end
|
|
332
|
+
|
|
333
|
+
module Distributed
|
|
334
|
+
# Builds a remote proxy model for +object_model+. +object_model+ is
|
|
335
|
+
# either a string or a class. In the first case, it is interpreted
|
|
336
|
+
# as a constant name.
|
|
337
|
+
def self.RemoteProxyModel(object_model)
|
|
338
|
+
object_model
|
|
339
|
+
end
|
|
340
|
+
|
|
341
|
+
end
|
|
342
|
+
end
|
|
343
|
+
|
|
@@ -0,0 +1,311 @@
|
|
|
1
|
+
|
|
2
|
+
module Roby
|
|
3
|
+
module Distributed
|
|
4
|
+
class << self
|
|
5
|
+
# Yields the peers which are interested in at least one of the
|
|
6
|
+
# objects in +objects+.
|
|
7
|
+
def each_updated_peer(*objects)
|
|
8
|
+
for obj in objects
|
|
9
|
+
return if !obj.distribute?
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
for _, peer in Distributed.peers
|
|
13
|
+
next unless peer.connected?
|
|
14
|
+
for obj in objects
|
|
15
|
+
if obj.update_on?(peer)
|
|
16
|
+
yield(peer)
|
|
17
|
+
break
|
|
18
|
+
end
|
|
19
|
+
end
|
|
20
|
+
end
|
|
21
|
+
end
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
class PeerServer
|
|
25
|
+
# Called by the peer to subscribe on +object+. Returns an array which
|
|
26
|
+
# is to be fed to #demux to update the object relations on the remote
|
|
27
|
+
# host
|
|
28
|
+
#
|
|
29
|
+
# In case of distributed transaction, it is forbidden to subscribe to a
|
|
30
|
+
# proxy without having subscribed to the proxied object first. This
|
|
31
|
+
# method will thus subscribe to both at the same time. Peer#subscribe
|
|
32
|
+
# is supposed to do the same
|
|
33
|
+
def subscribe_plan_object(object)
|
|
34
|
+
if Transactions::Proxy === object && object.__getobj__.self_owned?
|
|
35
|
+
subscribe_plan_object(object.__getobj__)
|
|
36
|
+
end
|
|
37
|
+
set_relations_commands(object)
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
# The peer wants to subscribe to our main plan
|
|
41
|
+
def subscribe_plan(sibling)
|
|
42
|
+
added_sibling(Roby.plan.remote_id, sibling)
|
|
43
|
+
peer.transmit(:subscribed_plan, Roby.plan.remote_id)
|
|
44
|
+
subscribe(Roby.plan)
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
# Called by our peer because it has subscribed us to its main plan
|
|
48
|
+
def subscribed_plan(remote_plan_id)
|
|
49
|
+
peer.proxies[remote_plan_id] = Roby.plan
|
|
50
|
+
peer.remote_plan = remote_plan_id
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
# Subscribe the remote peer to changes on +object+. +object+ must be
|
|
54
|
+
# an object owned locally.
|
|
55
|
+
def subscribe(m_object)
|
|
56
|
+
if !(local_object = peer.local_object(m_object, false))
|
|
57
|
+
raise OwnershipError, "no object for #{m_object}"
|
|
58
|
+
elsif !local_object.self_owned?
|
|
59
|
+
raise OwnershipError, "not owner of #{local_object}"
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
# We put the subscription process outside the communication
|
|
63
|
+
# thread so that the remote peer can send back the siblings it
|
|
64
|
+
# has created
|
|
65
|
+
peer.queueing do
|
|
66
|
+
peer.transmit(:subscribed, [local_object])
|
|
67
|
+
|
|
68
|
+
case local_object
|
|
69
|
+
when PlanObject
|
|
70
|
+
if !local_object.root_object?
|
|
71
|
+
raise ArgumentError, "cannot subscribe to non-root objects"
|
|
72
|
+
end
|
|
73
|
+
subscribe_plan_object(local_object)
|
|
74
|
+
|
|
75
|
+
when Plan
|
|
76
|
+
tasks, events = local_object.known_tasks, local_object.free_events
|
|
77
|
+
tasks.delete_if { |t| !t.distribute? }
|
|
78
|
+
events.delete_if { |t| !t.distribute? }
|
|
79
|
+
|
|
80
|
+
peer.transmit(:discover_plan, local_object, tasks, events)
|
|
81
|
+
tasks.each { |obj| subscribe_plan_object(obj) }
|
|
82
|
+
events.each { |obj| subscribe_plan_object(obj) }
|
|
83
|
+
end
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
local_object.remote_id
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
# Called by the remote host because it has subscribed us to a plan
|
|
90
|
+
# (a set of tasks and events).
|
|
91
|
+
def discover_plan(marshalled_plan, m_tasks, m_events)
|
|
92
|
+
plan = peer.local_object(marshalled_plan)
|
|
93
|
+
Distributed.update(plan) do
|
|
94
|
+
peer.local_object(m_tasks)
|
|
95
|
+
peer.local_object(m_events)
|
|
96
|
+
end
|
|
97
|
+
nil
|
|
98
|
+
end
|
|
99
|
+
|
|
100
|
+
# Called by the remote peer to announce that it has created the
|
|
101
|
+
# given siblings. +siblings+ is a remote_drbobject => local_object
|
|
102
|
+
# hash
|
|
103
|
+
#
|
|
104
|
+
# It is also used by BasicObject#sibling_of to register a new
|
|
105
|
+
# sibling
|
|
106
|
+
def added_sibling(local_id, remote_id)
|
|
107
|
+
local_id.local_object.add_sibling_for(peer, remote_id)
|
|
108
|
+
nil
|
|
109
|
+
end
|
|
110
|
+
|
|
111
|
+
# Called by the remote peer to announce that it has removed the
|
|
112
|
+
# given siblings. +objects+ is the list of local objects.
|
|
113
|
+
#
|
|
114
|
+
# It is also used by BasicObject#forget_peer to remove references
|
|
115
|
+
# to an old sibling
|
|
116
|
+
def removed_sibling(local_id, remote_id)
|
|
117
|
+
local_object = local_id.local_object
|
|
118
|
+
sibling = local_object.remove_sibling_for(peer, remote_id)
|
|
119
|
+
|
|
120
|
+
# It is fine to remove a sibling twice: you nay for instance
|
|
121
|
+
# decide in both sides that the sibling should be removed (for
|
|
122
|
+
# instance during the disconnection process)
|
|
123
|
+
if sibling && sibling != remote_id
|
|
124
|
+
raise "removed sibling #{sibling} for #{local_id} on peer #{peer} does not match the provided remote id (#{remote_id})"
|
|
125
|
+
end
|
|
126
|
+
|
|
127
|
+
unless local_object.remotely_useful?
|
|
128
|
+
Distributed.removed_objects.delete(local_object)
|
|
129
|
+
end
|
|
130
|
+
end
|
|
131
|
+
|
|
132
|
+
# Called by the remote peer to announce that is has subscribed us to +objects+
|
|
133
|
+
def subscribed(objects)
|
|
134
|
+
# Register the subscription
|
|
135
|
+
objects.each do |object|
|
|
136
|
+
peer.subscriptions << peer.remote_object(object)
|
|
137
|
+
end
|
|
138
|
+
# Create the proxies
|
|
139
|
+
peer.local_object(objects)
|
|
140
|
+
nil
|
|
141
|
+
end
|
|
142
|
+
|
|
143
|
+
# Sends to the peer the set of relations needed to copy the state of +plan_object+
|
|
144
|
+
# on the remote peer.
|
|
145
|
+
def set_relations_commands(plan_object)
|
|
146
|
+
peer.transmit(:set_relations, plan_object, Distributed.relations_of(plan_object))
|
|
147
|
+
|
|
148
|
+
if plan_object.respond_to?(:each_plan_child)
|
|
149
|
+
plan_object.each_plan_child do |plan_child|
|
|
150
|
+
peer.transmit(:set_relations, plan_child, Distributed.relations_of(plan_child))
|
|
151
|
+
end
|
|
152
|
+
end
|
|
153
|
+
end
|
|
154
|
+
|
|
155
|
+
# Sets the relation of +objects+ according to the description in +relations+.
|
|
156
|
+
# See #relations_of for how +relations+ is formatted
|
|
157
|
+
#
|
|
158
|
+
# Note that any relation not listed in +relations+ will actually be
|
|
159
|
+
# *removed* from the plan. Therefore, if +relations+ is empty, then
|
|
160
|
+
# all relations of +object+ are removed.
|
|
161
|
+
def set_relations(object, relations)
|
|
162
|
+
object = peer.local_object(object)
|
|
163
|
+
relations = peer.local_object(relations)
|
|
164
|
+
|
|
165
|
+
Distributed.update(object.root_object) do
|
|
166
|
+
all_parents = Hash.new { |h, k| h[k] = ValueSet.new }
|
|
167
|
+
all_children = Hash.new { |h, k| h[k] = ValueSet.new }
|
|
168
|
+
|
|
169
|
+
# Add or update existing relations
|
|
170
|
+
relations.each_slice(3) do |graph, parents, children|
|
|
171
|
+
all_objects = parents.map { |p, _| p } + children.map { |c, _| c }
|
|
172
|
+
Distributed.update_all(all_objects) do
|
|
173
|
+
parents.each_slice(2) do |parent, info|
|
|
174
|
+
next unless parent
|
|
175
|
+
all_parents[graph] << parent
|
|
176
|
+
|
|
177
|
+
if graph.linked?(parent, object)
|
|
178
|
+
parent[object, graph] = info
|
|
179
|
+
else
|
|
180
|
+
Distributed.update(parent.root_object) do
|
|
181
|
+
parent.add_child_object(object, graph, info)
|
|
182
|
+
end
|
|
183
|
+
end
|
|
184
|
+
end
|
|
185
|
+
children.each_slice(2) do |child, info|
|
|
186
|
+
next unless child
|
|
187
|
+
all_children[graph] << child
|
|
188
|
+
|
|
189
|
+
if graph.linked?(object, child)
|
|
190
|
+
object[child, graph] = info
|
|
191
|
+
else
|
|
192
|
+
Distributed.update(child.root_object) do
|
|
193
|
+
object.add_child_object(child, graph, info)
|
|
194
|
+
end
|
|
195
|
+
end
|
|
196
|
+
end
|
|
197
|
+
end
|
|
198
|
+
end
|
|
199
|
+
|
|
200
|
+
Distributed.each_object_relation(object) do |rel|
|
|
201
|
+
# Remove relations that do not exist anymore
|
|
202
|
+
#
|
|
203
|
+
# If the other end of this relation cannot be seen by
|
|
204
|
+
# our remote peer, keep it: it means that the relation
|
|
205
|
+
# is a local-only annotation this pDB has added to the
|
|
206
|
+
# task
|
|
207
|
+
(object.parent_objects(rel).to_value_set - all_parents[rel]).each do |p|
|
|
208
|
+
# See comment above
|
|
209
|
+
next unless p.distribute?
|
|
210
|
+
Distributed.update_all([p.root_object, object.root_object]) do
|
|
211
|
+
p.remove_child_object(object, rel)
|
|
212
|
+
end
|
|
213
|
+
end
|
|
214
|
+
(object.child_objects(rel).to_value_set - all_children[rel]).each do |c|
|
|
215
|
+
# See comment above
|
|
216
|
+
next unless c.distribute?
|
|
217
|
+
Distributed.update_all([c.root_object, object.root_object]) do
|
|
218
|
+
object.remove_child_object(c, rel)
|
|
219
|
+
end
|
|
220
|
+
end
|
|
221
|
+
end
|
|
222
|
+
end
|
|
223
|
+
|
|
224
|
+
nil
|
|
225
|
+
end
|
|
226
|
+
|
|
227
|
+
end
|
|
228
|
+
|
|
229
|
+
class Peer
|
|
230
|
+
# The set of remote objects we *want* notifications on, as
|
|
231
|
+
# RemoteID objects. This does not include automatically susbcribed
|
|
232
|
+
# objects, but only those explicitely subscribed to by calling
|
|
233
|
+
# Peer#subscribe
|
|
234
|
+
#
|
|
235
|
+
# See also #subscribe, #subscribed? and #unsubscribe
|
|
236
|
+
#
|
|
237
|
+
#--
|
|
238
|
+
# DO NOT USE a ValueSet here. RemoteIDs must be compared using #==
|
|
239
|
+
#++
|
|
240
|
+
attribute(:subscriptions) { Set.new }
|
|
241
|
+
|
|
242
|
+
# Explicitely subscribe to #object
|
|
243
|
+
#
|
|
244
|
+
# See also #subscriptions, #subscribed? and #unsubscribe
|
|
245
|
+
def subscribe(object)
|
|
246
|
+
while object.respond_to?(:__getobj__)
|
|
247
|
+
object = object.__getobj__
|
|
248
|
+
end
|
|
249
|
+
|
|
250
|
+
if remote_object = (remote_object(object) rescue nil)
|
|
251
|
+
if !subscriptions.include?(remote_object)
|
|
252
|
+
remote_object = nil
|
|
253
|
+
end
|
|
254
|
+
end
|
|
255
|
+
|
|
256
|
+
unless remote_object
|
|
257
|
+
remote_sibling = object.sibling_on(self)
|
|
258
|
+
remote_object = call(:subscribe, remote_sibling)
|
|
259
|
+
synchro_point
|
|
260
|
+
end
|
|
261
|
+
local_object = local_object(remote_object)
|
|
262
|
+
end
|
|
263
|
+
|
|
264
|
+
# Make our peer subscribe to +object+
|
|
265
|
+
def push_subscription(object)
|
|
266
|
+
local_server.subscribe(object)
|
|
267
|
+
synchro_point
|
|
268
|
+
end
|
|
269
|
+
|
|
270
|
+
# The RemoteID for the peer main plan
|
|
271
|
+
attr_accessor :remote_plan
|
|
272
|
+
|
|
273
|
+
# Subscribe to the remote plan
|
|
274
|
+
def subscribe_plan
|
|
275
|
+
call(:subscribe_plan, connection_space.plan.remote_id)
|
|
276
|
+
synchro_point
|
|
277
|
+
end
|
|
278
|
+
|
|
279
|
+
# Unsubscribe from the remote plan
|
|
280
|
+
def unsubscribe_plan
|
|
281
|
+
proxies.delete(remote_plan)
|
|
282
|
+
subscriptions.delete(remote_plan)
|
|
283
|
+
if connected?
|
|
284
|
+
call(:removed_sibling, @remote_plan, connection_space.plan.remote_id)
|
|
285
|
+
end
|
|
286
|
+
end
|
|
287
|
+
|
|
288
|
+
def subscribed_plan?; remote_plan && subscriptions.include?(remote_plan) end
|
|
289
|
+
|
|
290
|
+
# True if we are explicitely subscribed to +object+. Automatically
|
|
291
|
+
# subscribed objects will not be included here, but
|
|
292
|
+
# BasicObject#updated? will return true for them
|
|
293
|
+
#
|
|
294
|
+
# See also #subscriptions, #subscribe and #unsubscribe
|
|
295
|
+
def subscribed?(object)
|
|
296
|
+
subscriptions.include?(remote_object(object))
|
|
297
|
+
rescue RemotePeerMismatch
|
|
298
|
+
false
|
|
299
|
+
end
|
|
300
|
+
|
|
301
|
+
# Remove an explicit subscription. See also #subscriptions,
|
|
302
|
+
# #subscribe and #subscribed?
|
|
303
|
+
#
|
|
304
|
+
# See also #subscriptions, #subscribe and #subscribed?
|
|
305
|
+
def unsubscribe(object)
|
|
306
|
+
subscriptions.delete(remote_object(object))
|
|
307
|
+
end
|
|
308
|
+
end
|
|
309
|
+
end
|
|
310
|
+
end
|
|
311
|
+
|