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,592 @@
|
|
|
1
|
+
require 'rinda/ring'
|
|
2
|
+
require 'rinda/tuplespace'
|
|
3
|
+
require 'utilrb/time/to_hms'
|
|
4
|
+
require 'utilrb/kernel/options'
|
|
5
|
+
require 'utilrb/socket/tcp_server'
|
|
6
|
+
|
|
7
|
+
require 'roby/distributed/drb'
|
|
8
|
+
require 'roby/distributed/peer'
|
|
9
|
+
|
|
10
|
+
module Roby
|
|
11
|
+
module Distributed
|
|
12
|
+
# A neighbour is a [name, remote_id] tuple, where +name+ is the name of
|
|
13
|
+
# the neighbour and +remote_id+ the RemoteID which describes the remote
|
|
14
|
+
# ConnectionSpace, used to connect to it.
|
|
15
|
+
class Neighbour
|
|
16
|
+
attr_reader :name, :remote_id
|
|
17
|
+
def initialize(name, remote_id)
|
|
18
|
+
@name, @remote_id = name, remote_id
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
def connect; Peer.initiate_connection(ConnectionSpace.state, peer) end
|
|
22
|
+
def ==(other)
|
|
23
|
+
other.kind_of?(Neighbour) &&
|
|
24
|
+
(remote_id == other.remote_id)
|
|
25
|
+
end
|
|
26
|
+
def to_s; "#<Neighbour:#{name} #{remote_id}>" end
|
|
27
|
+
def eql?(other); other == self end
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
# Returns the Peer object for the given ID. +id+ can be either the peer
|
|
31
|
+
# RemoteID or its name.
|
|
32
|
+
def self.peer(id)
|
|
33
|
+
if id.kind_of?(Distributed::RemoteID)
|
|
34
|
+
if id == remote_id
|
|
35
|
+
Distributed
|
|
36
|
+
else
|
|
37
|
+
peers[id]
|
|
38
|
+
end
|
|
39
|
+
elsif id.respond_to?(:to_str)
|
|
40
|
+
peers.each_value { |p| return p if p.remote_name == id.to_str }
|
|
41
|
+
nil
|
|
42
|
+
else
|
|
43
|
+
nil
|
|
44
|
+
end
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
# Returns a RemoteID object suitable to represent this plan manager on
|
|
48
|
+
# the network.
|
|
49
|
+
#
|
|
50
|
+
# This makes Roby::Distributed behave like a Peer object
|
|
51
|
+
def self.remote_id
|
|
52
|
+
if state then state.remote_id
|
|
53
|
+
else @__single_remote_id__ ||= RemoteID.new('local', 0)
|
|
54
|
+
end
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
# Returns a Peer::DRoby object which can be used in the dRoby
|
|
58
|
+
# connection to represent this plan manager.
|
|
59
|
+
#
|
|
60
|
+
# This makes Roby::Distributed behave like a Peer object
|
|
61
|
+
def self.droby_dump(dest = nil)
|
|
62
|
+
if state then state.droby_dump(dest)
|
|
63
|
+
else @__single_marshalled_peer__ ||= Peer::DRoby.new('single', remote_id)
|
|
64
|
+
end
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
# Execute the given message without blocking. If a block is given,
|
|
68
|
+
# yield the result to that block.
|
|
69
|
+
#
|
|
70
|
+
# This makes Roby::Distributed behave like a Peer object
|
|
71
|
+
def self.transmit(*args)
|
|
72
|
+
Roby::Control.once do
|
|
73
|
+
result = Distributed.state.send(*args)
|
|
74
|
+
yield(result) if block_given?
|
|
75
|
+
end
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
# Execute the given message and wait for its result to be available.
|
|
79
|
+
#
|
|
80
|
+
# This makes Roby::Distributed behave like a Peer object
|
|
81
|
+
def self.call(*args)
|
|
82
|
+
Roby.execute do
|
|
83
|
+
Distributed.state.send(*args)
|
|
84
|
+
end
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
# True if this plan manager is subscribed to +object+
|
|
88
|
+
#
|
|
89
|
+
# This makes Roby::Distributed behave like a Peer object
|
|
90
|
+
def self.subscribed?(object)
|
|
91
|
+
object.subscribed?
|
|
92
|
+
end
|
|
93
|
+
|
|
94
|
+
# This class manages the connections between this plan manager and the
|
|
95
|
+
# remote plan managers
|
|
96
|
+
#
|
|
97
|
+
# * there is only one reception thread, at which all peers send data
|
|
98
|
+
class ConnectionSpace
|
|
99
|
+
include DRbUndumped
|
|
100
|
+
|
|
101
|
+
# List of discovered neighbours
|
|
102
|
+
def neighbours; synchronize { @neighbours.dup } end
|
|
103
|
+
# A queue containing all new neighbours
|
|
104
|
+
attr_reader :new_neighbours
|
|
105
|
+
# A remote_id => Peer map of the connected peers
|
|
106
|
+
attr_reader :peers
|
|
107
|
+
# A remote_id => thread of the connection threads
|
|
108
|
+
#
|
|
109
|
+
# See Peer.connection_request and Peer.initiate_connection
|
|
110
|
+
attr_reader :pending_connections
|
|
111
|
+
# A remote_id => thread of the connection threads
|
|
112
|
+
#
|
|
113
|
+
# See Peer.connection_request, Peer.initiate_connection and Peer#reconnect
|
|
114
|
+
attr_reader :aborted_connections
|
|
115
|
+
# The set of peers for which we have lost the link
|
|
116
|
+
attr_reader :pending_reconnections
|
|
117
|
+
# The period at which we do discovery
|
|
118
|
+
attr_reader :discovery_period
|
|
119
|
+
# The discovery thread
|
|
120
|
+
attr_reader :discovery_thread
|
|
121
|
+
|
|
122
|
+
# If we are doing discovery based on Rinda::RingFinger
|
|
123
|
+
def ring_discovery?; @ring_discovery end
|
|
124
|
+
# The list of broadcasting addresses to search for plan databases
|
|
125
|
+
attr_reader :ring_broadcast
|
|
126
|
+
# If we are doing discovery based on a central tuplespace
|
|
127
|
+
def central_discovery?; !!@discovery_tuplespace end
|
|
128
|
+
# The central tuplespace where neighbours are announced
|
|
129
|
+
attr_reader :discovery_tuplespace
|
|
130
|
+
# Last time a discovery finished
|
|
131
|
+
attr_reader :last_discovery
|
|
132
|
+
# A condition variable which is signalled to start a new discovery
|
|
133
|
+
attr_reader :start_discovery
|
|
134
|
+
# A condition variable which is signalled when discovery finishes
|
|
135
|
+
attr_reader :finished_discovery
|
|
136
|
+
|
|
137
|
+
# The main mutex which is used for synchronization with the discovery
|
|
138
|
+
# thread
|
|
139
|
+
attr_reader :mutex
|
|
140
|
+
def synchronize; mutex.synchronize { yield } end
|
|
141
|
+
# The plan we are publishing, usually Roby.plan
|
|
142
|
+
attr_reader :plan
|
|
143
|
+
|
|
144
|
+
# Our name on the network
|
|
145
|
+
attr_reader :name
|
|
146
|
+
# The socket on which we listen for incoming connections
|
|
147
|
+
attr_reader :server_socket
|
|
148
|
+
|
|
149
|
+
# Create a new ConnectionSpace objects. The following options can be provided:
|
|
150
|
+
#
|
|
151
|
+
# name:: the name of this plan manager. Defaults to <hostname>-<PID>
|
|
152
|
+
# period:: the discovery period [default: nil]
|
|
153
|
+
# ring_discovery:: whether or not ring discovery should be attempted [default: true]
|
|
154
|
+
# ring_broadcast:: the broadcast address for ring discovery
|
|
155
|
+
# discovery_tuplespace:: the DRbObject referencing the remote tuplespace which holds references to plan managers [default: nil]
|
|
156
|
+
# plan:: the plan this ConnectionSpace acts on. [default: Roby.plan]
|
|
157
|
+
# listen_at:: the port at which we should listen for incoming connections [default: 0]
|
|
158
|
+
def initialize(options = {})
|
|
159
|
+
super()
|
|
160
|
+
|
|
161
|
+
options = validate_options options,
|
|
162
|
+
:name => "#{Socket.gethostname}-#{Process.pid}", # the name of this host
|
|
163
|
+
:period => nil, # the discovery period
|
|
164
|
+
:ring_discovery => true, # wether we should do discovery based on Rinda::RingFinger
|
|
165
|
+
:ring_broadcast => '', # the broadcast address for discovery
|
|
166
|
+
:discovery_tuplespace => nil, # a central tuplespace which lists hosts (including ourselves)
|
|
167
|
+
:plan => nil, # the plan we publish, uses Roby.plan if nil
|
|
168
|
+
:listen_at => 0 # the port at which we listen for incoming connections
|
|
169
|
+
|
|
170
|
+
if options[:ring_discovery] && !options[:period]
|
|
171
|
+
raise ArgumentError, "you must provide a discovery period when using ring discovery"
|
|
172
|
+
end
|
|
173
|
+
|
|
174
|
+
@name = options[:name]
|
|
175
|
+
@neighbours = Array.new
|
|
176
|
+
@peers = Hash.new
|
|
177
|
+
@plan = options[:plan] || Roby.plan
|
|
178
|
+
@discovery_period = options[:period]
|
|
179
|
+
@ring_discovery = options[:ring_discovery]
|
|
180
|
+
@ring_broadcast = options[:ring_broadcast]
|
|
181
|
+
@discovery_tuplespace = options[:discovery_tuplespace]
|
|
182
|
+
@port = options[:port]
|
|
183
|
+
@pending_sockets = Queue.new
|
|
184
|
+
@pending_connections = Hash.new
|
|
185
|
+
@aborted_connections = Hash.new
|
|
186
|
+
@pending_reconnections = Array.new
|
|
187
|
+
@quit_neighbour_thread = false
|
|
188
|
+
|
|
189
|
+
@mutex = Mutex.new
|
|
190
|
+
@start_discovery = ConditionVariable.new
|
|
191
|
+
@finished_discovery = ConditionVariable.new
|
|
192
|
+
@new_neighbours = Queue.new
|
|
193
|
+
|
|
194
|
+
@connection_listeners = Array.new
|
|
195
|
+
|
|
196
|
+
yield(self) if block_given?
|
|
197
|
+
|
|
198
|
+
listen(options[:listen_at])
|
|
199
|
+
@remote_id = RemoteID.new(Socket.gethostname, server_socket.port)
|
|
200
|
+
|
|
201
|
+
if central_discovery?
|
|
202
|
+
if (discovery_tuplespace.write([:droby, name, remote_id]) rescue nil)
|
|
203
|
+
if discovery_tuplespace.kind_of?(DRbObject)
|
|
204
|
+
Distributed.info "published #{name}(#{remote_id}) on #{discovery_tuplespace.__drburi}"
|
|
205
|
+
else
|
|
206
|
+
Distributed.info "published #{name}(#{remote_id}) on local tuplespace"
|
|
207
|
+
end
|
|
208
|
+
else
|
|
209
|
+
Distributed.warn "cannot connect to #{discovery_tuplespace.__drburi}, disabling centralized discovery"
|
|
210
|
+
discovery_tuplespace = nil
|
|
211
|
+
end
|
|
212
|
+
end
|
|
213
|
+
|
|
214
|
+
if ring_discovery?
|
|
215
|
+
Distributed.info "doing ring discovery on #{ring_broadcast}"
|
|
216
|
+
end
|
|
217
|
+
|
|
218
|
+
synchronize do
|
|
219
|
+
# Start the discovery thread and wait for it to be initialized
|
|
220
|
+
@discovery_thread = Thread.new(&method(:neighbour_discovery))
|
|
221
|
+
finished_discovery.wait(mutex)
|
|
222
|
+
end
|
|
223
|
+
start_neighbour_discovery(true)
|
|
224
|
+
|
|
225
|
+
receive
|
|
226
|
+
|
|
227
|
+
Roby::Control.finalizers << method(:quit)
|
|
228
|
+
end
|
|
229
|
+
|
|
230
|
+
# Sets up a separate thread which listens for connection
|
|
231
|
+
def listen(port)
|
|
232
|
+
@server_socket = TCPServer.new(nil, port)
|
|
233
|
+
server_socket.listen(10)
|
|
234
|
+
Thread.new do
|
|
235
|
+
begin
|
|
236
|
+
while new_connection = server_socket.accept
|
|
237
|
+
begin
|
|
238
|
+
Peer.connection_request(self, new_connection)
|
|
239
|
+
rescue Exception => e
|
|
240
|
+
Roby::Distributed.fatal "failed to handle connection request on #{new_connection}"
|
|
241
|
+
Roby::Distributed.fatal e.full_message
|
|
242
|
+
new_connection.close
|
|
243
|
+
end
|
|
244
|
+
end
|
|
245
|
+
rescue Exception
|
|
246
|
+
end
|
|
247
|
+
end
|
|
248
|
+
end
|
|
249
|
+
|
|
250
|
+
# The RemoteID object which allows to reference this ConnectionSpace on the network
|
|
251
|
+
attr_reader :remote_id
|
|
252
|
+
|
|
253
|
+
# The set of new sockets to wait for. If one of these is closed,
|
|
254
|
+
# Distributed.receive will check wether we are supposed to be
|
|
255
|
+
# connected to the peer. If it's not the case, the socket will be
|
|
256
|
+
# ignored.
|
|
257
|
+
attr_reader :pending_sockets
|
|
258
|
+
|
|
259
|
+
# Starts the reception thread
|
|
260
|
+
def receive # :nodoc:
|
|
261
|
+
sockets = Hash.new
|
|
262
|
+
Thread.new do
|
|
263
|
+
while true
|
|
264
|
+
begin
|
|
265
|
+
while !pending_sockets.empty?
|
|
266
|
+
socket, peer = pending_sockets.shift
|
|
267
|
+
sockets[socket] = peer
|
|
268
|
+
Roby::Distributed.info "listening to #{socket.peer_info} for #{peer}"
|
|
269
|
+
end
|
|
270
|
+
|
|
271
|
+
begin
|
|
272
|
+
sockets.delete_if { |s, p| s.closed? && p.disconnected? }
|
|
273
|
+
read, _, errors = select(sockets.keys, nil, nil, 0.1)
|
|
274
|
+
rescue IOError
|
|
275
|
+
end
|
|
276
|
+
next if !read
|
|
277
|
+
|
|
278
|
+
closed_sockets = []
|
|
279
|
+
for socket in read
|
|
280
|
+
if socket.closed?
|
|
281
|
+
closed_sockets << socket
|
|
282
|
+
next
|
|
283
|
+
end
|
|
284
|
+
|
|
285
|
+
header = socket.read(8)
|
|
286
|
+
unless header && header.size == 8
|
|
287
|
+
closed_sockets << socket
|
|
288
|
+
next
|
|
289
|
+
end
|
|
290
|
+
|
|
291
|
+
id, size = header.unpack("NN")
|
|
292
|
+
data = socket.read(size)
|
|
293
|
+
|
|
294
|
+
p = sockets[socket]
|
|
295
|
+
p.stats.rx += (size + 8)
|
|
296
|
+
Roby::Distributed.cycles_rx << [p, Marshal.load(data)]
|
|
297
|
+
end
|
|
298
|
+
|
|
299
|
+
for socket in closed_sockets
|
|
300
|
+
p = sockets[socket]
|
|
301
|
+
if p.connected?
|
|
302
|
+
Roby::Distributed.info "lost connection with #{p}"
|
|
303
|
+
p.reconnect
|
|
304
|
+
sockets.delete socket
|
|
305
|
+
elsif p.disconnecting?
|
|
306
|
+
Roby::Distributed.info "#{p} disconnected"
|
|
307
|
+
p.disconnected
|
|
308
|
+
end
|
|
309
|
+
end
|
|
310
|
+
|
|
311
|
+
rescue Exception
|
|
312
|
+
Roby::Distributed.fatal "error in ConnectionSpace#receive: #{$!.full_message}"
|
|
313
|
+
end
|
|
314
|
+
end
|
|
315
|
+
end
|
|
316
|
+
end
|
|
317
|
+
|
|
318
|
+
def discovering?
|
|
319
|
+
synchronize do
|
|
320
|
+
if @last_discovery != @discovery_start
|
|
321
|
+
yield if block_given?
|
|
322
|
+
true
|
|
323
|
+
end
|
|
324
|
+
end
|
|
325
|
+
end
|
|
326
|
+
|
|
327
|
+
def owns?(object); object.owners.include?(Roby::Distributed) end
|
|
328
|
+
|
|
329
|
+
# An array of procs called at the end of the neighbour discovery,
|
|
330
|
+
# after #neighbours have been updated
|
|
331
|
+
attr_reader :connection_listeners
|
|
332
|
+
|
|
333
|
+
def discovery_port
|
|
334
|
+
if Distributed.server
|
|
335
|
+
Distributed.server.port
|
|
336
|
+
else DISCOVERY_RING_PORT
|
|
337
|
+
end
|
|
338
|
+
end
|
|
339
|
+
|
|
340
|
+
# Loop which does neighbour_discovery
|
|
341
|
+
def neighbour_discovery
|
|
342
|
+
Thread.current.priority = 2
|
|
343
|
+
|
|
344
|
+
discovered = []
|
|
345
|
+
|
|
346
|
+
# Initialize so that @discovery_start == discovery_start
|
|
347
|
+
@discovery_start = nil
|
|
348
|
+
discovery_start = nil
|
|
349
|
+
finger = nil
|
|
350
|
+
loop do
|
|
351
|
+
return if @quit_neighbour_thread
|
|
352
|
+
|
|
353
|
+
Control.synchronize do
|
|
354
|
+
old_neighbours, @neighbours = @neighbours, []
|
|
355
|
+
for new in discovered
|
|
356
|
+
unless new.remote_id == remote_id || @neighbours.include?(new)
|
|
357
|
+
@neighbours << new
|
|
358
|
+
unless old_neighbours.include?(new)
|
|
359
|
+
new_neighbours << [self, new]
|
|
360
|
+
end
|
|
361
|
+
end
|
|
362
|
+
end
|
|
363
|
+
discovered.clear
|
|
364
|
+
end
|
|
365
|
+
|
|
366
|
+
connection_listeners.each { |listen| listen.call(self) }
|
|
367
|
+
synchronize do
|
|
368
|
+
@last_discovery = discovery_start
|
|
369
|
+
finished_discovery.broadcast
|
|
370
|
+
|
|
371
|
+
if @discovery_start == @last_discovery
|
|
372
|
+
start_discovery.wait(mutex)
|
|
373
|
+
end
|
|
374
|
+
return if @quit_neighbour_thread
|
|
375
|
+
discovery_start = @discovery_start
|
|
376
|
+
|
|
377
|
+
if ring_discovery? && (!finger || (finger.port != discovery_port))
|
|
378
|
+
finger = Rinda::RingFinger.new(ring_broadcast, discovery_port)
|
|
379
|
+
end
|
|
380
|
+
end
|
|
381
|
+
|
|
382
|
+
from = Time.now
|
|
383
|
+
if central_discovery?
|
|
384
|
+
discovery_tuplespace.read_all([:droby, nil, nil]).
|
|
385
|
+
each do |n|
|
|
386
|
+
next if n[2] == remote_id
|
|
387
|
+
n = Neighbour.new(n[1], n[2])
|
|
388
|
+
discovered << n
|
|
389
|
+
end
|
|
390
|
+
end
|
|
391
|
+
|
|
392
|
+
if discovery_period
|
|
393
|
+
remaining = (@discovery_start + discovery_period) - Time.now
|
|
394
|
+
end
|
|
395
|
+
|
|
396
|
+
if ring_discovery?
|
|
397
|
+
finger.lookup_ring(remaining) do |cs|
|
|
398
|
+
next if cs == self
|
|
399
|
+
|
|
400
|
+
discovered << Neighbour.new(cs.name, cs.remote_id)
|
|
401
|
+
end
|
|
402
|
+
end
|
|
403
|
+
end
|
|
404
|
+
|
|
405
|
+
rescue Interrupt
|
|
406
|
+
rescue Exception => e
|
|
407
|
+
Distributed.fatal "neighbour discovery died with\n#{e.full_message}"
|
|
408
|
+
Distributed.fatal "Peers are: #{Distributed.peers.map { |id, peer| "#{id.inspect} => #{peer}" }.join(", ")}"
|
|
409
|
+
|
|
410
|
+
ensure
|
|
411
|
+
Distributed.info "quit neighbour thread"
|
|
412
|
+
neighbours.clear
|
|
413
|
+
new_neighbours.clear
|
|
414
|
+
|
|
415
|
+
# Force disconnection in case something got wrong in the normal
|
|
416
|
+
# disconnection process
|
|
417
|
+
Distributed.peers.values.each do |peer|
|
|
418
|
+
peer.disconnected unless peer.disconnected?
|
|
419
|
+
end
|
|
420
|
+
|
|
421
|
+
synchronize do
|
|
422
|
+
@discovery_thread = nil
|
|
423
|
+
finished_discovery.broadcast
|
|
424
|
+
end
|
|
425
|
+
end
|
|
426
|
+
|
|
427
|
+
# Starts one neighbour discovery loop
|
|
428
|
+
def start_neighbour_discovery(block = false)
|
|
429
|
+
synchronize do
|
|
430
|
+
unless discovery_thread && discovery_thread.alive?
|
|
431
|
+
raise "no discovery thread"
|
|
432
|
+
end
|
|
433
|
+
|
|
434
|
+
@discovery_start = Time.now
|
|
435
|
+
start_discovery.signal
|
|
436
|
+
end
|
|
437
|
+
wait_discovery if block
|
|
438
|
+
end
|
|
439
|
+
|
|
440
|
+
def wait_discovery
|
|
441
|
+
discovering? do
|
|
442
|
+
finished_discovery.wait(mutex)
|
|
443
|
+
end
|
|
444
|
+
end
|
|
445
|
+
def wait_next_discovery
|
|
446
|
+
synchronize do
|
|
447
|
+
unless discovery_thread && discovery_thread.alive?
|
|
448
|
+
raise "no discovery thread"
|
|
449
|
+
end
|
|
450
|
+
finished_discovery.wait(mutex)
|
|
451
|
+
end
|
|
452
|
+
end
|
|
453
|
+
|
|
454
|
+
# Define #droby_dump for Peer-like behaviour
|
|
455
|
+
def droby_dump(dest = nil); @__droby_marshalled__ ||= Peer::DRoby.new(name, remote_id) end
|
|
456
|
+
|
|
457
|
+
# Make the ConnectionSpace quit
|
|
458
|
+
def quit
|
|
459
|
+
Distributed.debug "ConnectionSpace #{self} quitting"
|
|
460
|
+
|
|
461
|
+
# Remove us from the central tuplespace
|
|
462
|
+
if central_discovery?
|
|
463
|
+
begin
|
|
464
|
+
discovery_tuplespace.take [:droby, nil, remote_id], 0
|
|
465
|
+
rescue DRb::DRbConnError, Rinda::RequestExpiredError
|
|
466
|
+
end
|
|
467
|
+
end
|
|
468
|
+
|
|
469
|
+
# Make the neighbour discovery thread quit as well
|
|
470
|
+
thread = synchronize do
|
|
471
|
+
if thread = @discovery_thread
|
|
472
|
+
thread.raise Interrupt, "forcing discovery thread quit"
|
|
473
|
+
end
|
|
474
|
+
thread
|
|
475
|
+
end
|
|
476
|
+
if thread
|
|
477
|
+
thread.join
|
|
478
|
+
end
|
|
479
|
+
|
|
480
|
+
ensure
|
|
481
|
+
if server_socket
|
|
482
|
+
begin
|
|
483
|
+
server_socket.close
|
|
484
|
+
rescue IOError
|
|
485
|
+
end
|
|
486
|
+
end
|
|
487
|
+
|
|
488
|
+
Roby::Control.finalizers.delete(method(:quit))
|
|
489
|
+
if Distributed.state == self
|
|
490
|
+
Distributed.state = nil
|
|
491
|
+
end
|
|
492
|
+
end
|
|
493
|
+
|
|
494
|
+
# Disable the keeper thread, we will do cleanup ourselves
|
|
495
|
+
def start_keeper; end
|
|
496
|
+
|
|
497
|
+
# This makes ConnectionSpace act as a PeerServer object locally
|
|
498
|
+
def transaction_prepare_commit(trsc) # :nodoc:
|
|
499
|
+
!trsc.valid_transaction?
|
|
500
|
+
end
|
|
501
|
+
# This makes ConnectionSpace act as a PeerServer object locally
|
|
502
|
+
def transaction_abandon_commit(trsc, reason) # :nodoc:
|
|
503
|
+
trsc.abandoned_commit(reason)
|
|
504
|
+
end
|
|
505
|
+
# This makes ConnectionSpace act as a PeerServer object locally
|
|
506
|
+
def transaction_commit(trsc) # :nodoc:
|
|
507
|
+
trsc.commit_transaction(false)
|
|
508
|
+
end
|
|
509
|
+
# This makes ConnectionSpace act as a PeerServer object locally
|
|
510
|
+
def transaction_discard(trsc) # :nodoc:
|
|
511
|
+
trsc.discard_transaction(false)
|
|
512
|
+
end
|
|
513
|
+
end
|
|
514
|
+
|
|
515
|
+
class << self
|
|
516
|
+
# The RingServer object through which we publish this plan manager
|
|
517
|
+
# on the network
|
|
518
|
+
attr_reader :server
|
|
519
|
+
|
|
520
|
+
# True if we are published on the network.
|
|
521
|
+
#
|
|
522
|
+
# See #server, #publish and #unpublish
|
|
523
|
+
def published?; !!@server end
|
|
524
|
+
|
|
525
|
+
# Enable ring discovery on our part. A RingServer object is set up
|
|
526
|
+
# to listen to connections on the port given as a :port option (or
|
|
527
|
+
# DISCOVERY_RING_PORT if none is specified).
|
|
528
|
+
#
|
|
529
|
+
# Note that all plan managers must use the same discovery port.
|
|
530
|
+
def publish(options = {})
|
|
531
|
+
options[:port] ||= DISCOVERY_RING_PORT
|
|
532
|
+
@server = RingServer.new(state, options)
|
|
533
|
+
Distributed.info "listening for distributed discovery on #{options[:port]}"
|
|
534
|
+
end
|
|
535
|
+
|
|
536
|
+
# Disable the ring discovery on our part.
|
|
537
|
+
def unpublish
|
|
538
|
+
if server
|
|
539
|
+
server.close
|
|
540
|
+
@server = nil
|
|
541
|
+
Distributed.info "disabled distributed discovery"
|
|
542
|
+
end
|
|
543
|
+
end
|
|
544
|
+
|
|
545
|
+
# The list of known neighbours. See ConnectionSpace#neighbours
|
|
546
|
+
def neighbours
|
|
547
|
+
if state then state.neighbours
|
|
548
|
+
else []
|
|
549
|
+
end
|
|
550
|
+
end
|
|
551
|
+
|
|
552
|
+
# The list of neighbours that have been found since the last
|
|
553
|
+
# execution cycle
|
|
554
|
+
def new_neighbours
|
|
555
|
+
if state then state.new_neighbours
|
|
556
|
+
else []
|
|
557
|
+
end
|
|
558
|
+
end
|
|
559
|
+
end
|
|
560
|
+
|
|
561
|
+
@new_neighbours_observers = Array.new
|
|
562
|
+
class << self
|
|
563
|
+
# The set of proc objects which should be notified when new
|
|
564
|
+
# neighbours are detected.
|
|
565
|
+
attr_reader :new_neighbours_observers
|
|
566
|
+
|
|
567
|
+
# Called in the neighbour discovery thread to detect new
|
|
568
|
+
# neighbours. It fills the new_neighbours queue which is read by
|
|
569
|
+
# notify_new_neighbours to notify application code of new
|
|
570
|
+
# neighbours in the control thread
|
|
571
|
+
def notify_new_neighbours
|
|
572
|
+
return unless Distributed.state
|
|
573
|
+
while !new_neighbours.empty?
|
|
574
|
+
cs, neighbour = new_neighbours.pop(true)
|
|
575
|
+
new_neighbours_observers.each do |obs|
|
|
576
|
+
obs[cs, neighbour]
|
|
577
|
+
end
|
|
578
|
+
end
|
|
579
|
+
end
|
|
580
|
+
|
|
581
|
+
# Defines a block which should be called when a new neighbour is
|
|
582
|
+
# detected
|
|
583
|
+
def on_neighbour
|
|
584
|
+
current = neighbours.dup
|
|
585
|
+
Roby::Control.once { current.each { |n| yield(n) } }
|
|
586
|
+
new_neighbours_observers << lambda { |_, n| yield(n) }
|
|
587
|
+
end
|
|
588
|
+
end
|
|
589
|
+
Roby::Control.event_processing << method(:notify_new_neighbours)
|
|
590
|
+
end
|
|
591
|
+
end
|
|
592
|
+
|