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,550 @@
|
|
|
1
|
+
require 'set'
|
|
2
|
+
require 'utilrb/array/to_s'
|
|
3
|
+
require 'utilrb/socket/tcp_socket'
|
|
4
|
+
|
|
5
|
+
require 'roby'
|
|
6
|
+
require 'roby/state'
|
|
7
|
+
require 'roby/planning'
|
|
8
|
+
require 'roby/distributed/notifications'
|
|
9
|
+
require 'roby/distributed/proxy'
|
|
10
|
+
require 'roby/distributed/communication'
|
|
11
|
+
|
|
12
|
+
module Roby
|
|
13
|
+
class Control; include DRbUndumped end
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
module Roby::Distributed
|
|
17
|
+
class ConnectionSpace
|
|
18
|
+
def add_owner(object, peer)
|
|
19
|
+
object.add_owner(peer, false)
|
|
20
|
+
end
|
|
21
|
+
def remove_owner(object, peer)
|
|
22
|
+
object.remove_owner(peer, false)
|
|
23
|
+
end
|
|
24
|
+
def prepare_remove_owner(object, peer)
|
|
25
|
+
object.prepare_remove_owner(peer)
|
|
26
|
+
rescue Exception => e
|
|
27
|
+
e
|
|
28
|
+
end
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
class ConnectionTask < Roby::Task
|
|
32
|
+
local_only
|
|
33
|
+
|
|
34
|
+
argument :peer
|
|
35
|
+
def peer; arguments[:peer] end
|
|
36
|
+
|
|
37
|
+
event :ready
|
|
38
|
+
def ready?; event(:ready).happened? end
|
|
39
|
+
|
|
40
|
+
event :aborted, :terminal => true do |context|
|
|
41
|
+
peer.disconnected!
|
|
42
|
+
end
|
|
43
|
+
forward :aborted => :failed
|
|
44
|
+
|
|
45
|
+
event :failed, :terminal => true do |context|
|
|
46
|
+
peer.disconnect
|
|
47
|
+
end
|
|
48
|
+
interruptible
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
# Base class for all communication errors
|
|
52
|
+
class ConnectionError < RuntimeError; end
|
|
53
|
+
# Raised when a connection attempt has failed
|
|
54
|
+
class ConnectionFailedError < RuntimeError
|
|
55
|
+
def initialize(peer); @peer = peer end
|
|
56
|
+
end
|
|
57
|
+
# The peer is connected but connection is not alive
|
|
58
|
+
class NotAliveError < ConnectionError; end
|
|
59
|
+
# The peer is disconnected
|
|
60
|
+
class DisconnectedError < ConnectionError; end
|
|
61
|
+
|
|
62
|
+
class << self
|
|
63
|
+
# This method will call PeerServer#trigger on all peers, for the
|
|
64
|
+
# objects in +objects+ which are eligible for triggering.
|
|
65
|
+
#
|
|
66
|
+
# The same task cannot match the same trigger twice. To allow that,
|
|
67
|
+
# call #clean_triggered.
|
|
68
|
+
def trigger(*objects)
|
|
69
|
+
return unless Roby::Distributed.state
|
|
70
|
+
objects.delete_if do |o|
|
|
71
|
+
o.plan != Roby::Distributed.state.plan ||
|
|
72
|
+
!o.distribute? ||
|
|
73
|
+
!o.self_owned?
|
|
74
|
+
end
|
|
75
|
+
return if objects.empty?
|
|
76
|
+
|
|
77
|
+
# If +object+ is a trigger, send the :triggered event but do *not*
|
|
78
|
+
# act as if +object+ was subscribed
|
|
79
|
+
peers.each_value do |peer|
|
|
80
|
+
peer.local_server.trigger(*objects)
|
|
81
|
+
end
|
|
82
|
+
end
|
|
83
|
+
# Remove +objects+ from the sets of already-triggered objects. So, next
|
|
84
|
+
# time +object+ will be tested for triggers, it will re-match the
|
|
85
|
+
# triggers it has already matched.
|
|
86
|
+
def clean_triggered(object)
|
|
87
|
+
peers.each_value do |peer|
|
|
88
|
+
peer.local_server.triggers.each_value do |_, triggered|
|
|
89
|
+
triggered.delete object
|
|
90
|
+
end
|
|
91
|
+
end
|
|
92
|
+
end
|
|
93
|
+
end
|
|
94
|
+
|
|
95
|
+
# PeerServer objects are the objects which act as servers for the plan
|
|
96
|
+
# managers we are connected on, i.e. it will process the messages sent by
|
|
97
|
+
# those remote plan managers.
|
|
98
|
+
#
|
|
99
|
+
# The client part, that is the part which actually send the messages, is
|
|
100
|
+
# a Peer object accessible through the Peer#peer attribute.
|
|
101
|
+
class PeerServer
|
|
102
|
+
include DRbUndumped
|
|
103
|
+
|
|
104
|
+
# The Peer object we are associated to
|
|
105
|
+
attr_reader :peer
|
|
106
|
+
|
|
107
|
+
# The set of triggers our peer has added to our plan
|
|
108
|
+
attr_reader :triggers
|
|
109
|
+
|
|
110
|
+
# Create a PeerServer object for the given peer
|
|
111
|
+
def initialize(peer)
|
|
112
|
+
@peer = peer
|
|
113
|
+
@triggers = Hash.new
|
|
114
|
+
end
|
|
115
|
+
|
|
116
|
+
def to_s # :nodoc:
|
|
117
|
+
"PeerServer:#{remote_name}"
|
|
118
|
+
end
|
|
119
|
+
|
|
120
|
+
# Activate any trigger that may exist on +objects+
|
|
121
|
+
# It sends the PeerServer#triggered message for each objects that are
|
|
122
|
+
# actually matching a registered trigger.
|
|
123
|
+
def trigger(*objects)
|
|
124
|
+
triggers.each do |id, (matcher, triggered)|
|
|
125
|
+
objects.each do |object|
|
|
126
|
+
if !triggered.include?(object) && matcher === object
|
|
127
|
+
triggered << object
|
|
128
|
+
peer.transmit(:triggered, id, object)
|
|
129
|
+
end
|
|
130
|
+
end
|
|
131
|
+
end
|
|
132
|
+
end
|
|
133
|
+
|
|
134
|
+
# The name of the local ConnectionSpace object we are acting on
|
|
135
|
+
def local_name; peer.local_name end
|
|
136
|
+
# The name of the remote peer
|
|
137
|
+
def remote_name; peer.remote_name end
|
|
138
|
+
|
|
139
|
+
# The plan object which is used as a facade for our peer
|
|
140
|
+
def plan; peer.connection_space.plan end
|
|
141
|
+
|
|
142
|
+
# Applies +matcher+ on the local plan and sends back the result
|
|
143
|
+
def query_result_set(query)
|
|
144
|
+
plan.query_result_set(peer.local_object(query)).
|
|
145
|
+
delete_if { |obj| !obj.distribute? }
|
|
146
|
+
end
|
|
147
|
+
|
|
148
|
+
# The peers asks to be notified if a plan object which matches
|
|
149
|
+
# +matcher+ changes
|
|
150
|
+
def add_trigger(id, matcher)
|
|
151
|
+
triggers[id] = [matcher, (triggered = ValueSet.new)]
|
|
152
|
+
Roby.info "#{remote_name} wants notification on #{matcher} (#{id})"
|
|
153
|
+
|
|
154
|
+
peer.queueing do
|
|
155
|
+
matcher.each(plan) do |task|
|
|
156
|
+
if !triggered.include?(task)
|
|
157
|
+
triggered << task
|
|
158
|
+
peer.transmit(:triggered, id, task)
|
|
159
|
+
end
|
|
160
|
+
end
|
|
161
|
+
end
|
|
162
|
+
nil
|
|
163
|
+
end
|
|
164
|
+
|
|
165
|
+
# Remove the trigger +id+ defined by this peer
|
|
166
|
+
def remove_trigger(id)
|
|
167
|
+
Roby.info "#{remote_name} removed #{id} notification"
|
|
168
|
+
triggers.delete(id)
|
|
169
|
+
nil
|
|
170
|
+
end
|
|
171
|
+
|
|
172
|
+
# Message received when +task+ has matched the trigger referenced by +id+
|
|
173
|
+
def triggered(id, task)
|
|
174
|
+
peer.triggered(id, task)
|
|
175
|
+
nil
|
|
176
|
+
end
|
|
177
|
+
|
|
178
|
+
# Send the neighborhood of +distance+ hops around +object+ to the peer
|
|
179
|
+
def discover_neighborhood(object, distance)
|
|
180
|
+
object = peer.local_object(object)
|
|
181
|
+
edges = object.neighborhood(distance)
|
|
182
|
+
if object.respond_to?(:each_plan_child)
|
|
183
|
+
object.each_plan_child do |plan_child|
|
|
184
|
+
edges += plan_child.neighborhood(distance)
|
|
185
|
+
end
|
|
186
|
+
end
|
|
187
|
+
|
|
188
|
+
# Replace the relation graphs by their name
|
|
189
|
+
edges.delete_if do |rel, from, to, info|
|
|
190
|
+
!(rel.distribute? && from.distribute? && to.distribute?)
|
|
191
|
+
end
|
|
192
|
+
edges
|
|
193
|
+
end
|
|
194
|
+
end
|
|
195
|
+
|
|
196
|
+
# A Peer object is the client part of a connection with a remote plan
|
|
197
|
+
# manager. The server part, i.e. the object which actually receives
|
|
198
|
+
# requests from the remote plan manager, is the PeerServer object
|
|
199
|
+
# accessible through the Peer#local_server attribute.
|
|
200
|
+
#
|
|
201
|
+
# == Connection procedure
|
|
202
|
+
#
|
|
203
|
+
# Connections are initiated When the user calls Peer.initiate_connection.
|
|
204
|
+
# The following protocol is then followed:
|
|
205
|
+
# [local]
|
|
206
|
+
# if the neighbour is already connected to us, we do nothing and yield
|
|
207
|
+
# the already existing peer. End.
|
|
208
|
+
# [local]
|
|
209
|
+
# check if we are already connecting to the peer. If it is the case,
|
|
210
|
+
# wait for the end of the connection thread.
|
|
211
|
+
# [local]
|
|
212
|
+
# otherwise, open a new socket and send the connect() message in it
|
|
213
|
+
# The connection thread is registered in ConnectionSpace.pending_connections
|
|
214
|
+
# [remote]
|
|
215
|
+
# check if we are already connecting to the peer (check ConnectionSpace.pending_connections)
|
|
216
|
+
# * if it is the case, the lowest token wins
|
|
217
|
+
# * if 'remote' wins, return :already_connecting
|
|
218
|
+
# * if 'local' wins, return :connected with the relevant information
|
|
219
|
+
#
|
|
220
|
+
# == Communication
|
|
221
|
+
#
|
|
222
|
+
# Communication is done in two threads. The sending thread gets the calls
|
|
223
|
+
# from Peer#send_queue, formats them and sends them to the PeerServer#demux
|
|
224
|
+
# for processing. The reception thread is managed by dRb and its entry
|
|
225
|
+
# point is always #demux.
|
|
226
|
+
#
|
|
227
|
+
# Very often we need to have processing on both sides to finish an
|
|
228
|
+
# operation. For instance, the creation of two siblings need to register
|
|
229
|
+
# the siblings on both sides. To manage that, it is possible for PeerServer
|
|
230
|
+
# methods which are serving a remote request to queue callbacks. These
|
|
231
|
+
# callbacks will be processed by Peer#send_thread before the rest of the
|
|
232
|
+
# queue might be processed
|
|
233
|
+
class Peer
|
|
234
|
+
include DRbUndumped
|
|
235
|
+
|
|
236
|
+
# The local ConnectionSpace object we act on
|
|
237
|
+
attr_reader :connection_space
|
|
238
|
+
# The local PeerServer object for this peer
|
|
239
|
+
attr_reader :local_server
|
|
240
|
+
# The set of proxies for object from this remote peer
|
|
241
|
+
attr_reader :proxies
|
|
242
|
+
# The set of proxies we are currently removing. See BasicObject#forget_peer
|
|
243
|
+
attr_reader :removing_proxies
|
|
244
|
+
# The connection socket with our peer
|
|
245
|
+
attr_reader :socket
|
|
246
|
+
|
|
247
|
+
ComStats = Struct.new :rx, :tx
|
|
248
|
+
# A ComStats object which holds the communication statistics for this peer
|
|
249
|
+
# stats.tx is the count of bytes sent to the peer while stats.rx is the
|
|
250
|
+
# count of bytes received
|
|
251
|
+
attr_reader :stats
|
|
252
|
+
|
|
253
|
+
def to_s # :nodoc:
|
|
254
|
+
"Peer:#{remote_name}"
|
|
255
|
+
end
|
|
256
|
+
# This method is used by Distributed.format to determine the dumping
|
|
257
|
+
# policy for +object+. If the method returns true, then only the
|
|
258
|
+
# RemoteID object of +object+ will be sent to the peer. Otherwise,
|
|
259
|
+
# an intermediate object describing +object+ is sent.
|
|
260
|
+
def incremental_dump?(object)
|
|
261
|
+
object.respond_to?(:remote_siblings) && object.remote_siblings[self]
|
|
262
|
+
end
|
|
263
|
+
|
|
264
|
+
# The object which identifies this peer on the network
|
|
265
|
+
attr_reader :remote_id
|
|
266
|
+
# The name of the remote peer
|
|
267
|
+
attr_reader :remote_name
|
|
268
|
+
# The [host, port] pair at the peer end
|
|
269
|
+
attr_reader :peer_info
|
|
270
|
+
|
|
271
|
+
# The name of the local ConnectionSpace object we are acting on
|
|
272
|
+
def local_name; connection_space.name end
|
|
273
|
+
|
|
274
|
+
# The ID => block hash of all triggers we have defined on the remote plan
|
|
275
|
+
attr_reader :triggers
|
|
276
|
+
# The remote state
|
|
277
|
+
attr_accessor :state
|
|
278
|
+
|
|
279
|
+
# Creates a Peer object for the peer connected at +socket+. This peer
|
|
280
|
+
# is to be managed by +connection_space+ If a block is given, it is
|
|
281
|
+
# called in the control thread when the connection is finalized
|
|
282
|
+
def initialize(connection_space, socket, remote_name, remote_id, remote_state, &block)
|
|
283
|
+
# Initialize the remote name with the socket parameters. It will be set to
|
|
284
|
+
# the real name during the connection process
|
|
285
|
+
@remote_name = remote_name
|
|
286
|
+
@remote_id = remote_id
|
|
287
|
+
@peer_info = socket.peer_info
|
|
288
|
+
|
|
289
|
+
super() if defined? super
|
|
290
|
+
|
|
291
|
+
@connection_space = connection_space
|
|
292
|
+
@local_server = PeerServer.new(self)
|
|
293
|
+
@proxies = Hash.new
|
|
294
|
+
@removing_proxies = Hash.new { |h, k| h[k] = Array.new }
|
|
295
|
+
@mutex = Mutex.new
|
|
296
|
+
@triggers = Hash.new
|
|
297
|
+
@socket = socket
|
|
298
|
+
@stats = ComStats.new 0, 0
|
|
299
|
+
@dead = false
|
|
300
|
+
@disabled_rx = 0
|
|
301
|
+
@disabled_tx = 0
|
|
302
|
+
connection_space.pending_sockets << [socket, self]
|
|
303
|
+
|
|
304
|
+
@connection_state = :connected
|
|
305
|
+
@send_queue = Queue.new
|
|
306
|
+
@completion_queue = Queue.new
|
|
307
|
+
@current_cycle = Array.new
|
|
308
|
+
|
|
309
|
+
Roby::Distributed.peers[remote_id] = self
|
|
310
|
+
local_server.state_update remote_state
|
|
311
|
+
|
|
312
|
+
@task = ConnectionTask.new :peer => self
|
|
313
|
+
Roby::Control.once do
|
|
314
|
+
connection_space.plan.permanent(task)
|
|
315
|
+
task.start!
|
|
316
|
+
task.emit(:ready)
|
|
317
|
+
end
|
|
318
|
+
|
|
319
|
+
@send_thread = Thread.new(&method(:communication_loop))
|
|
320
|
+
end
|
|
321
|
+
|
|
322
|
+
# The peer name
|
|
323
|
+
attr_reader :name
|
|
324
|
+
# The ConnectionTask object for this peer
|
|
325
|
+
attr_reader :task
|
|
326
|
+
|
|
327
|
+
# Creates a query object on the remote plan.
|
|
328
|
+
#
|
|
329
|
+
# For thread-safe operation, always use #each on the resulting query:
|
|
330
|
+
# during the enumeration, the local plan GC will not remove those
|
|
331
|
+
# tasks.
|
|
332
|
+
def find_tasks
|
|
333
|
+
Roby::Query.new(self)
|
|
334
|
+
end
|
|
335
|
+
|
|
336
|
+
# Returns a set of remote tasks for +query+ applied on the remote plan
|
|
337
|
+
# This is not to be accessed directly. It is part of the Query
|
|
338
|
+
# interface.
|
|
339
|
+
#
|
|
340
|
+
# See #find_tasks.
|
|
341
|
+
def query_result_set(query)
|
|
342
|
+
result = ValueSet.new
|
|
343
|
+
call(:query_result_set, query) do |marshalled_set|
|
|
344
|
+
for task in marshalled_set
|
|
345
|
+
task = local_object(task)
|
|
346
|
+
Roby::Distributed.keep.ref(task)
|
|
347
|
+
result << task
|
|
348
|
+
end
|
|
349
|
+
end
|
|
350
|
+
|
|
351
|
+
result
|
|
352
|
+
end
|
|
353
|
+
|
|
354
|
+
# Yields the tasks saved in +result_set+ by #query_result_set. During
|
|
355
|
+
# the enumeration, the tasks are marked as permanent to avoid plan GC.
|
|
356
|
+
# The block can subscribe to the one that are interesting. After the
|
|
357
|
+
# block has returned, all non-subscribed tasks will be subject to plan
|
|
358
|
+
# GC.
|
|
359
|
+
def query_each(result_set) # :nodoc:
|
|
360
|
+
result_set.each do |task|
|
|
361
|
+
yield(task)
|
|
362
|
+
end
|
|
363
|
+
|
|
364
|
+
ensure
|
|
365
|
+
Roby::Control.synchronize do
|
|
366
|
+
if result_set
|
|
367
|
+
result_set.each do |task|
|
|
368
|
+
Roby::Distributed.keep.deref(task)
|
|
369
|
+
end
|
|
370
|
+
end
|
|
371
|
+
end
|
|
372
|
+
end
|
|
373
|
+
|
|
374
|
+
# call-seq:
|
|
375
|
+
# peer.on(matcher) { |task| ... } => ID
|
|
376
|
+
#
|
|
377
|
+
# Call the provided block in the control thread when a task matching
|
|
378
|
+
# +matcher+ has been found on the remote plan. +task+ is the local
|
|
379
|
+
# proxy for the matching remote task.
|
|
380
|
+
#
|
|
381
|
+
# The return value is an identifier which can be later used to remove
|
|
382
|
+
# the trigger with Peer#remove_trigger
|
|
383
|
+
#
|
|
384
|
+
# This sends the PeerServer#add_trigger message to the peer.
|
|
385
|
+
def on(matcher, &block)
|
|
386
|
+
triggers[matcher.object_id] = [matcher, block]
|
|
387
|
+
transmit(:add_trigger, matcher.object_id, matcher)
|
|
388
|
+
end
|
|
389
|
+
|
|
390
|
+
# Remove a trigger referenced by its ID. +id+ is the value returned by
|
|
391
|
+
# Peer#on
|
|
392
|
+
#
|
|
393
|
+
# This sends the PeerServer#remove_trigger message to the peer.
|
|
394
|
+
def remove_trigger(id)
|
|
395
|
+
transmit(:remove_trigger, id)
|
|
396
|
+
triggers.delete(id)
|
|
397
|
+
end
|
|
398
|
+
|
|
399
|
+
# Calls the block given to Peer#on in a separate thread when +task+ has
|
|
400
|
+
# matched the trigger
|
|
401
|
+
def triggered(id, task) # :nodoc:
|
|
402
|
+
task = local_object(task)
|
|
403
|
+
Roby::Distributed.keep.ref(task)
|
|
404
|
+
Thread.new do
|
|
405
|
+
begin
|
|
406
|
+
if trigger = triggers[id]
|
|
407
|
+
trigger.last.call(task)
|
|
408
|
+
end
|
|
409
|
+
rescue Exception
|
|
410
|
+
Roby.warn "trigger handler #{trigger.last} failed with #{$!.full_message}"
|
|
411
|
+
ensure
|
|
412
|
+
Roby::Distributed.keep.deref(task)
|
|
413
|
+
end
|
|
414
|
+
end
|
|
415
|
+
end
|
|
416
|
+
|
|
417
|
+
# Returns true if this peer owns +object+
|
|
418
|
+
def owns?(object); object.owners.include?(self) end
|
|
419
|
+
|
|
420
|
+
# Returns the remote object for +object+. +object+ can be either a
|
|
421
|
+
# DRbObject, a marshalled object or a local proxy. In the latter case,
|
|
422
|
+
# a RemotePeerMismatch exception is raised if the local proxy is not
|
|
423
|
+
# known to this peer.
|
|
424
|
+
def remote_object(object)
|
|
425
|
+
if object.kind_of?(RemoteID)
|
|
426
|
+
object
|
|
427
|
+
else object.sibling_on(self)
|
|
428
|
+
end
|
|
429
|
+
end
|
|
430
|
+
|
|
431
|
+
# Returns the remote_object, local_object pair for +object+. +object+
|
|
432
|
+
# can be either a marshalled object or a local proxy. Raises
|
|
433
|
+
# ArgumentError if it is none of the two. In the latter case, a
|
|
434
|
+
# RemotePeerMismatch exception is raised if the local proxy is not
|
|
435
|
+
# known to this peer.
|
|
436
|
+
def objects(object, create_local = true)
|
|
437
|
+
if object.kind_of?(RemoteID)
|
|
438
|
+
if local_proxy = proxies[object]
|
|
439
|
+
proxy_setup(local_proxy)
|
|
440
|
+
return [object, local_proxy]
|
|
441
|
+
end
|
|
442
|
+
raise ArgumentError, "got a RemoteID which has no proxy"
|
|
443
|
+
elsif object.respond_to?(:proxy)
|
|
444
|
+
[object.remote_object, proxy(object, create_local)]
|
|
445
|
+
else
|
|
446
|
+
[object.sibling_on(self), object]
|
|
447
|
+
end
|
|
448
|
+
end
|
|
449
|
+
|
|
450
|
+
def proxy_setup(local_object)
|
|
451
|
+
if local_object.respond_to?(:execution_agent) &&
|
|
452
|
+
local_object.owners.size == 1 &&
|
|
453
|
+
owns?(local_object) &&
|
|
454
|
+
!local_object.execution_agent &&
|
|
455
|
+
local_object.plan
|
|
456
|
+
|
|
457
|
+
remote_owner = local_object.owners.first
|
|
458
|
+
connection_task = local_object.plan[self.task]
|
|
459
|
+
|
|
460
|
+
Roby::Distributed.update_all([local_object, connection_task]) do
|
|
461
|
+
local_object.executed_by connection_task
|
|
462
|
+
end
|
|
463
|
+
end
|
|
464
|
+
|
|
465
|
+
local_object
|
|
466
|
+
end
|
|
467
|
+
|
|
468
|
+
# Returns the local object for +object+. +object+ can be either a
|
|
469
|
+
# marshalled object or a local proxy. Raises ArgumentError if it is
|
|
470
|
+
# none of the two. In the latter case, a RemotePeerMismatch exception
|
|
471
|
+
# is raised if the local proxy is not known to this peer.
|
|
472
|
+
def local_object(marshalled, create = true)
|
|
473
|
+
if marshalled.kind_of?(RemoteID)
|
|
474
|
+
return marshalled.to_local(self, create)
|
|
475
|
+
elsif !marshalled.respond_to?(:proxy)
|
|
476
|
+
return marshalled
|
|
477
|
+
elsif marshalled.respond_to?(:remote_siblings)
|
|
478
|
+
# 1/ try any local RemoteID reference registered in the marshalled object
|
|
479
|
+
local_id = marshalled.remote_siblings[Roby::Distributed.droby_dump]
|
|
480
|
+
if local_id
|
|
481
|
+
local_object = local_id.local_object rescue nil
|
|
482
|
+
local_object = nil if local_object.finalized?
|
|
483
|
+
end
|
|
484
|
+
|
|
485
|
+
# 2/ try the #proxies hash
|
|
486
|
+
if !local_object
|
|
487
|
+
remote_id = marshalled.remote_siblings[droby_dump]
|
|
488
|
+
unless local_object = proxies[remote_id]
|
|
489
|
+
return if !create
|
|
490
|
+
|
|
491
|
+
# remove any local ID since we are re-creating it
|
|
492
|
+
marshalled.remote_siblings.delete(Roby::Distributed.droby_dump)
|
|
493
|
+
local_object = marshalled.proxy(self)
|
|
494
|
+
end
|
|
495
|
+
end
|
|
496
|
+
|
|
497
|
+
if !local_object
|
|
498
|
+
raise "no remote siblings for #{remote_name} in #{marshalled} (#{marshalled.remote_siblings})"
|
|
499
|
+
end
|
|
500
|
+
|
|
501
|
+
if marshalled.respond_to?(:update)
|
|
502
|
+
Roby::Distributed.update(local_object) do
|
|
503
|
+
marshalled.update(self, local_object)
|
|
504
|
+
end
|
|
505
|
+
end
|
|
506
|
+
proxy_setup(local_object)
|
|
507
|
+
else
|
|
508
|
+
local_object = marshalled.proxy(self)
|
|
509
|
+
end
|
|
510
|
+
|
|
511
|
+
local_object
|
|
512
|
+
end
|
|
513
|
+
alias proxy local_object
|
|
514
|
+
|
|
515
|
+
# Discovers all objects at a distance +dist+ from +obj+. The object
|
|
516
|
+
# can be either a remote proxy or the remote object itself
|
|
517
|
+
def discover_neighborhood(object, distance)
|
|
518
|
+
objects = ValueSet.new
|
|
519
|
+
Roby.condition_variable(true) do |synchro, mutex|
|
|
520
|
+
mutex.synchronize do
|
|
521
|
+
transmit(:discover_neighborhood, object, distance) do |edges|
|
|
522
|
+
edges = local_object(edges)
|
|
523
|
+
edges.each do |rel, from, to, info|
|
|
524
|
+
objects << from.root_object << to.root_object
|
|
525
|
+
end
|
|
526
|
+
Roby::Distributed.update_all(objects) do
|
|
527
|
+
edges.each do |rel, from, to, info|
|
|
528
|
+
from.add_child_object(to, rel, info)
|
|
529
|
+
end
|
|
530
|
+
end
|
|
531
|
+
|
|
532
|
+
objects.each { |obj| Roby::Distributed.keep.ref(obj) }
|
|
533
|
+
|
|
534
|
+
synchro.broadcast
|
|
535
|
+
end
|
|
536
|
+
synchro.wait(mutex)
|
|
537
|
+
end
|
|
538
|
+
end
|
|
539
|
+
|
|
540
|
+
yield(local_object(remote_object(object)))
|
|
541
|
+
|
|
542
|
+
Roby::Control.synchronize do
|
|
543
|
+
objects.each { |obj| Roby::Distributed.keep.deref(obj) }
|
|
544
|
+
end
|
|
545
|
+
end
|
|
546
|
+
end
|
|
547
|
+
end
|
|
548
|
+
|
|
549
|
+
require 'roby/distributed/subscription'
|
|
550
|
+
|