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,151 @@
|
|
|
1
|
+
module Roby
|
|
2
|
+
class BasicObject
|
|
3
|
+
include DRbUndumped
|
|
4
|
+
|
|
5
|
+
def initialize_copy(old) # :nodoc:
|
|
6
|
+
super
|
|
7
|
+
|
|
8
|
+
@remote_siblings = Hash[Distributed, Roby::Distributed::RemoteID.from_object(self)]
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
# The set of Peer objects which own this object
|
|
12
|
+
attribute(:owners) { [Distributed] }
|
|
13
|
+
# True if we own this object
|
|
14
|
+
def self_owned?; owners.include?(Distributed) end
|
|
15
|
+
|
|
16
|
+
# Attribute which overrides the #distribute attribute on object classes
|
|
17
|
+
attr_writer :distribute
|
|
18
|
+
# True if this object can be seen by remote hosts
|
|
19
|
+
def distribute?
|
|
20
|
+
@distribute || (@distribute.nil? && self.class.distribute?)
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
# True if instances of this class should be seen by remote hosts
|
|
24
|
+
def self.distribute?; !(@distribute == false) end
|
|
25
|
+
# Call to make the object of this class never seen by remote hosts
|
|
26
|
+
def self.local_only; @distribute = false end
|
|
27
|
+
|
|
28
|
+
def finalized?; !remote_siblings[Distributed] end
|
|
29
|
+
|
|
30
|
+
# The peer => remote_object hash of known siblings for this peer: if
|
|
31
|
+
# there is a representation of this object on a peer, then
|
|
32
|
+
# +remote_siblings+ includes it
|
|
33
|
+
attribute(:remote_siblings) { Hash[Distributed, remote_id] }
|
|
34
|
+
|
|
35
|
+
# True if we know about a sibling on +peer+
|
|
36
|
+
def has_sibling_on?(peer)
|
|
37
|
+
peer == Roby::Distributed || remote_siblings.include?(peer)
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
# Returns the object representation of +self+ on +peer+. The returned
|
|
41
|
+
# value is either a remote sibling (the DRbObject of the representation
|
|
42
|
+
# of +self+ on +peer+), or self if peer is Roby::Distributed
|
|
43
|
+
def sibling_on(peer)
|
|
44
|
+
if sibling = remote_siblings[peer] then sibling
|
|
45
|
+
elsif Roby::Distributed == peer then self
|
|
46
|
+
else
|
|
47
|
+
raise RemotePeerMismatch, "#{self} has no known sibling on #{peer}"
|
|
48
|
+
end
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
# Sets +remote_object+ as the remote siblings for +self+ on +peer+, and
|
|
52
|
+
# notifies peer that +self+ is the remote siblings for +remote_object+
|
|
53
|
+
def sibling_of(remote_object, peer)
|
|
54
|
+
if !distribute?
|
|
55
|
+
raise ArgumentError, "#{self} is local only"
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
add_sibling_for(peer, remote_object)
|
|
59
|
+
peer.transmit(:added_sibling, remote_object, remote_id)
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
# Called when all links to +peer+ should be removed.
|
|
63
|
+
def forget_peer(peer)
|
|
64
|
+
if remote_object = remove_sibling_for(peer)
|
|
65
|
+
peer.removing_proxies[remote_object] << droby_dump(nil)
|
|
66
|
+
|
|
67
|
+
if peer.connected?
|
|
68
|
+
peer.transmit(:removed_sibling, remote_object, self.remote_id) do
|
|
69
|
+
set = peer.removing_proxies[remote_object]
|
|
70
|
+
set.shift
|
|
71
|
+
if set.empty?
|
|
72
|
+
peer.removing_proxies.delete(remote_object)
|
|
73
|
+
end
|
|
74
|
+
yield if block_given?
|
|
75
|
+
end
|
|
76
|
+
else
|
|
77
|
+
peer.removing_proxies.delete(remote_object)
|
|
78
|
+
end
|
|
79
|
+
end
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
# Registers +remote_object+ as the sibling of +self+ on +peer+. Unlike
|
|
83
|
+
# #sibling_of, do not notify the peer about it.
|
|
84
|
+
def add_sibling_for(peer, remote_object)
|
|
85
|
+
if old_sibling = remote_siblings[peer]
|
|
86
|
+
if old_sibling != remote_object
|
|
87
|
+
raise ArgumentError, "#{self} has already a sibling for #{peer} (#{old_sibling}) #{remote_siblings}"
|
|
88
|
+
else
|
|
89
|
+
# This is OK. The same sibling information can come from
|
|
90
|
+
# different sources. We only check for inconsistencies
|
|
91
|
+
return
|
|
92
|
+
end
|
|
93
|
+
end
|
|
94
|
+
|
|
95
|
+
remote_siblings[peer] = remote_object
|
|
96
|
+
peer.proxies[remote_object] = self
|
|
97
|
+
Roby::Distributed.debug "added sibling #{remote_object.inspect} for #{self} on #{peer} (#{remote_siblings})"
|
|
98
|
+
end
|
|
99
|
+
|
|
100
|
+
# Remove references about the sibling registered for +peer+ and returns it
|
|
101
|
+
def remove_sibling_for(peer, id = nil)
|
|
102
|
+
if id && remote_siblings[peer] != id
|
|
103
|
+
return
|
|
104
|
+
end
|
|
105
|
+
|
|
106
|
+
if remote_object = remote_siblings.delete(peer)
|
|
107
|
+
peer.proxies.delete(remote_object)
|
|
108
|
+
peer.subscriptions.delete(remote_object)
|
|
109
|
+
Roby::Distributed.debug "removed sibling #{remote_object.inspect} for #{self} on #{peer}"
|
|
110
|
+
remote_object
|
|
111
|
+
end
|
|
112
|
+
end
|
|
113
|
+
|
|
114
|
+
# True if we explicitely want this object to be updated by our peers
|
|
115
|
+
def subscribed?; owners.any? { |peer| peer.subscribed?(self) if peer != Distributed } end
|
|
116
|
+
|
|
117
|
+
# Subscribe to this object on all the peers which own it.
|
|
118
|
+
#
|
|
119
|
+
# This is a blocking operation, and cannot be used in the control
|
|
120
|
+
# thread.
|
|
121
|
+
def subscribe
|
|
122
|
+
if !self_owned? && !subscribed?
|
|
123
|
+
owners.each do |peer|
|
|
124
|
+
peer.subscribe(self) unless peer.subcribed?(self)
|
|
125
|
+
end
|
|
126
|
+
end
|
|
127
|
+
end
|
|
128
|
+
|
|
129
|
+
# True if this object is maintained up-to-date
|
|
130
|
+
def updated?; self_owned? || owners.any?(&remote_siblings.method(:[])) end
|
|
131
|
+
# True if +peer+ will send us updates about this object
|
|
132
|
+
def updated_by?(peer); self_owned? || (remote_siblings[peer] && peer.owns?(self)) end
|
|
133
|
+
# True if we shall send updates for this object on +peer+
|
|
134
|
+
def update_on?(peer); (self_owned? || peer.owns?(self)) && remote_siblings[peer] end
|
|
135
|
+
# The set of peers that will get updates of this object
|
|
136
|
+
def updated_peers
|
|
137
|
+
peers = remote_siblings.keys
|
|
138
|
+
peers.delete(Distributed)
|
|
139
|
+
peers
|
|
140
|
+
end
|
|
141
|
+
|
|
142
|
+
# True if this object is useful for our peers
|
|
143
|
+
def remotely_useful?; self_owned? && remote_siblings.size > 1 end
|
|
144
|
+
|
|
145
|
+
# True if this object can be modified in the current context
|
|
146
|
+
def read_write?
|
|
147
|
+
owners.include?(Distributed) || Distributed.updating?(self)
|
|
148
|
+
end
|
|
149
|
+
end
|
|
150
|
+
end
|
|
151
|
+
|
data/lib/roby/config.rb
ADDED
data/lib/roby/control.rb
ADDED
|
@@ -0,0 +1,747 @@
|
|
|
1
|
+
require 'roby'
|
|
2
|
+
require 'utilrb/exception/full_message'
|
|
3
|
+
|
|
4
|
+
require 'drb'
|
|
5
|
+
require 'set'
|
|
6
|
+
|
|
7
|
+
module Roby
|
|
8
|
+
class Pool < Queue
|
|
9
|
+
def initialize(klass)
|
|
10
|
+
@klass = klass
|
|
11
|
+
super()
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
def pop
|
|
15
|
+
value = super(true) rescue nil
|
|
16
|
+
value || @klass.new
|
|
17
|
+
end
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
@mutexes = Pool.new(Mutex)
|
|
21
|
+
@condition_variables = Pool.new(ConditionVariable)
|
|
22
|
+
class << self
|
|
23
|
+
# Returns the only one Control object
|
|
24
|
+
attr_reader :control
|
|
25
|
+
# Returns the executed plan. This is equivalent to
|
|
26
|
+
# Roby.control.plan
|
|
27
|
+
attr_reader :plan
|
|
28
|
+
|
|
29
|
+
def every(duration, &block)
|
|
30
|
+
Control.every(duration, &block)
|
|
31
|
+
end
|
|
32
|
+
def each_cycle(&block)
|
|
33
|
+
Control.each_cycle(&block)
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
# Returns the control thread or, if control is not in a separate
|
|
37
|
+
# thread, Thread.main
|
|
38
|
+
def control_thread
|
|
39
|
+
Control.instance.thread || Thread.main
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
# True if the current thread is the control thread
|
|
43
|
+
#
|
|
44
|
+
# See #outside_control? for a discussion of the use of #inside_control?
|
|
45
|
+
# and #outside_control? when testing the threading context
|
|
46
|
+
def inside_control?
|
|
47
|
+
t = Control.instance.thread
|
|
48
|
+
!t || t == Thread.current
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
# True if the current thread is not control thread, or if
|
|
52
|
+
# there is not control thread. When you check the current
|
|
53
|
+
# thread context, always use a negated form. Do not do
|
|
54
|
+
#
|
|
55
|
+
# if Roby.inside_control?
|
|
56
|
+
# ERROR
|
|
57
|
+
# end
|
|
58
|
+
#
|
|
59
|
+
# Do instead
|
|
60
|
+
#
|
|
61
|
+
# if !Roby.outside_control?
|
|
62
|
+
# ERROR
|
|
63
|
+
# end
|
|
64
|
+
#
|
|
65
|
+
# Since the first form will fail if there is no control thread, while
|
|
66
|
+
# the second form will work. Use the first form only if you require
|
|
67
|
+
# that there actually IS a control thread.
|
|
68
|
+
def outside_control?
|
|
69
|
+
t = Control.instance.thread
|
|
70
|
+
!t || t != Thread.current
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
# A pool of mutexes (as a Queue)
|
|
74
|
+
attr_reader :mutexes
|
|
75
|
+
# A pool of condition variables (as a Queue)
|
|
76
|
+
attr_reader :condition_variables
|
|
77
|
+
|
|
78
|
+
# call-seq:
|
|
79
|
+
# condition_variable => cv
|
|
80
|
+
# condition_variable(true) => cv, mutex
|
|
81
|
+
# condition_variable { |cv| ... } => value returned by the block
|
|
82
|
+
# condition_variable(true) { |cv, mutex| ... } => value returned by the block
|
|
83
|
+
#
|
|
84
|
+
# Get a condition variable object from the Roby.condition_variables
|
|
85
|
+
# pool and, if mutex is not true, a Mutex object
|
|
86
|
+
#
|
|
87
|
+
# If a block is given, the two objects are yield and returned into the
|
|
88
|
+
# pool after the block has returned. In that case, the method returns
|
|
89
|
+
# the value returned by the block
|
|
90
|
+
def condition_variable(mutex = false)
|
|
91
|
+
cv = condition_variables.pop
|
|
92
|
+
|
|
93
|
+
if block_given?
|
|
94
|
+
begin
|
|
95
|
+
if mutex
|
|
96
|
+
mt = mutexes.pop
|
|
97
|
+
yield(cv, mt)
|
|
98
|
+
else
|
|
99
|
+
yield(cv)
|
|
100
|
+
end
|
|
101
|
+
|
|
102
|
+
ensure
|
|
103
|
+
return_condition_variable(cv, mt)
|
|
104
|
+
end
|
|
105
|
+
else
|
|
106
|
+
if mutex
|
|
107
|
+
return cv, mutexes.pop
|
|
108
|
+
else
|
|
109
|
+
return cv
|
|
110
|
+
end
|
|
111
|
+
end
|
|
112
|
+
end
|
|
113
|
+
|
|
114
|
+
# Execute the given block inside the control thread, and returns when
|
|
115
|
+
# it has finished. The return value is the value returned by the block
|
|
116
|
+
def execute
|
|
117
|
+
if Roby.inside_control?
|
|
118
|
+
return yield
|
|
119
|
+
end
|
|
120
|
+
|
|
121
|
+
cv = condition_variable
|
|
122
|
+
|
|
123
|
+
return_value = nil
|
|
124
|
+
Roby::Control.synchronize do
|
|
125
|
+
if !Roby.control.running?
|
|
126
|
+
raise "control thread not running"
|
|
127
|
+
end
|
|
128
|
+
|
|
129
|
+
caller_thread = Thread.current
|
|
130
|
+
Control.waiting_threads << caller_thread
|
|
131
|
+
|
|
132
|
+
Roby::Control.once do
|
|
133
|
+
begin
|
|
134
|
+
return_value = yield
|
|
135
|
+
cv.broadcast
|
|
136
|
+
rescue Exception => e
|
|
137
|
+
caller_thread.raise e, e.message, e.backtrace
|
|
138
|
+
end
|
|
139
|
+
Control.waiting_threads.delete(caller_thread)
|
|
140
|
+
end
|
|
141
|
+
cv.wait(Roby::Control.mutex)
|
|
142
|
+
end
|
|
143
|
+
return_value
|
|
144
|
+
|
|
145
|
+
ensure
|
|
146
|
+
return_condition_variable(cv)
|
|
147
|
+
end
|
|
148
|
+
|
|
149
|
+
# Execute the given block in the control thread, but don't wait for its
|
|
150
|
+
# completion like Roby.execute does
|
|
151
|
+
def once
|
|
152
|
+
Roby::Control.once { yield }
|
|
153
|
+
end
|
|
154
|
+
def wait_one_cycle
|
|
155
|
+
Roby.control.wait_one_cycle
|
|
156
|
+
end
|
|
157
|
+
|
|
158
|
+
# Stops the current thread until the given even is emitted
|
|
159
|
+
def wait_until(ev)
|
|
160
|
+
if Roby.inside_control?
|
|
161
|
+
raise ThreadMismatch, "cannot use #wait_until in control thread"
|
|
162
|
+
end
|
|
163
|
+
|
|
164
|
+
condition_variable(true) do |cv, mt|
|
|
165
|
+
caller_thread = Thread.current
|
|
166
|
+
|
|
167
|
+
mt.synchronize do
|
|
168
|
+
Roby::Control.once do
|
|
169
|
+
ev.if_unreachable(true) do |reason|
|
|
170
|
+
caller_thread.raise UnreachableEvent.new(ev, reason)
|
|
171
|
+
end
|
|
172
|
+
ev.on do
|
|
173
|
+
mt.synchronize { cv.broadcast }
|
|
174
|
+
end
|
|
175
|
+
yield
|
|
176
|
+
end
|
|
177
|
+
cv.wait(mt)
|
|
178
|
+
end
|
|
179
|
+
end
|
|
180
|
+
end
|
|
181
|
+
|
|
182
|
+
# Returns a ConditionVariable and optionally a Mutex into the
|
|
183
|
+
# Roby.condition_variables and Roby.mutexes pools
|
|
184
|
+
def return_condition_variable(cv, mutex = nil)
|
|
185
|
+
condition_variables.push cv
|
|
186
|
+
if mutex
|
|
187
|
+
mutexes.push mutex
|
|
188
|
+
end
|
|
189
|
+
nil
|
|
190
|
+
end
|
|
191
|
+
end
|
|
192
|
+
|
|
193
|
+
# This singleton class is the central object: it handles the event loop,
|
|
194
|
+
# event propagation and exception propagation.
|
|
195
|
+
class Control
|
|
196
|
+
include Singleton
|
|
197
|
+
|
|
198
|
+
@mutex = Mutex.new
|
|
199
|
+
class << self
|
|
200
|
+
attr_reader :mutex
|
|
201
|
+
|
|
202
|
+
def taken_mutex?; Thread.current[:control_mutex_locked] end
|
|
203
|
+
|
|
204
|
+
# Implements a recursive behaviour on Control.mutex
|
|
205
|
+
def synchronize
|
|
206
|
+
if Thread.current[:control_mutex_locked]
|
|
207
|
+
yield
|
|
208
|
+
else
|
|
209
|
+
mutex.lock
|
|
210
|
+
begin
|
|
211
|
+
Thread.current[:control_mutex_locked] = true
|
|
212
|
+
yield
|
|
213
|
+
ensure
|
|
214
|
+
Thread.current[:control_mutex_locked] = false
|
|
215
|
+
mutex.unlock
|
|
216
|
+
end
|
|
217
|
+
end
|
|
218
|
+
end
|
|
219
|
+
end
|
|
220
|
+
|
|
221
|
+
# Do not sleep or call Thread#pass if there is less that
|
|
222
|
+
# this much time left in the cycle
|
|
223
|
+
SLEEP_MIN_TIME = 0.01
|
|
224
|
+
|
|
225
|
+
# The priority of the control thread
|
|
226
|
+
THREAD_PRIORITY = 10
|
|
227
|
+
|
|
228
|
+
# If true, abort if an unhandled exception is found
|
|
229
|
+
attr_accessor :abort_on_exception
|
|
230
|
+
# If true, abort if an application exception is found
|
|
231
|
+
attr_accessor :abort_on_application_exception
|
|
232
|
+
# If true, abort if a framework exception is found
|
|
233
|
+
attr_accessor :abort_on_framework_exception
|
|
234
|
+
|
|
235
|
+
@event_processing = []
|
|
236
|
+
@structure_checks = []
|
|
237
|
+
class << self
|
|
238
|
+
# List of procs which are called at each event cycle
|
|
239
|
+
attr_reader :event_processing
|
|
240
|
+
|
|
241
|
+
# List of procs to be called for task structure checking
|
|
242
|
+
#
|
|
243
|
+
# The blocks return a set of exceptions or nil. The exception
|
|
244
|
+
# *must* respond to #task or #generator to know from which task the
|
|
245
|
+
# problem comes.
|
|
246
|
+
attr_reader :structure_checks
|
|
247
|
+
end
|
|
248
|
+
|
|
249
|
+
# The plan being executed
|
|
250
|
+
attr_reader :plan
|
|
251
|
+
# A set of planners declared in this application
|
|
252
|
+
attr_reader :planners
|
|
253
|
+
|
|
254
|
+
def initialize
|
|
255
|
+
super
|
|
256
|
+
@quit = 0
|
|
257
|
+
@thread = nil
|
|
258
|
+
@cycle_index = 0
|
|
259
|
+
@cycle_start = Time.now
|
|
260
|
+
@cycle_length = 0
|
|
261
|
+
@planners = []
|
|
262
|
+
@last_stop_count = 0
|
|
263
|
+
@plan = Plan.new
|
|
264
|
+
Roby.instance_variable_set(:@plan, @plan)
|
|
265
|
+
plan.extend Roby::Propagation::ExecutablePlanChanged
|
|
266
|
+
end
|
|
267
|
+
|
|
268
|
+
# Perform the structure checking step by calling the procs registered
|
|
269
|
+
# in Control::structure_checks. These procs are supposed to return a
|
|
270
|
+
# collection of exception objects, or nil if no error has been found
|
|
271
|
+
def structure_checking
|
|
272
|
+
# Do structure checking and gather the raised exceptions
|
|
273
|
+
exceptions = {}
|
|
274
|
+
for prc in Control.structure_checks
|
|
275
|
+
begin
|
|
276
|
+
new_exceptions = prc.call(plan)
|
|
277
|
+
rescue Exception => e
|
|
278
|
+
Propagation.add_framework_error(e, 'structure checking')
|
|
279
|
+
end
|
|
280
|
+
next unless new_exceptions
|
|
281
|
+
|
|
282
|
+
[*new_exceptions].each do |e, tasks|
|
|
283
|
+
e = Propagation.to_execution_exception(e)
|
|
284
|
+
exceptions[e] = tasks
|
|
285
|
+
end
|
|
286
|
+
end
|
|
287
|
+
exceptions
|
|
288
|
+
end
|
|
289
|
+
|
|
290
|
+
# Abort the control loop because of +exceptions+
|
|
291
|
+
def reraise(exceptions)
|
|
292
|
+
if exceptions.size == 1
|
|
293
|
+
e = exceptions.first
|
|
294
|
+
if e.kind_of?(ExecutionException)
|
|
295
|
+
e = e.exception
|
|
296
|
+
end
|
|
297
|
+
raise e, e.message, e.backtrace
|
|
298
|
+
else
|
|
299
|
+
raise Aborting.new(exceptions)
|
|
300
|
+
end
|
|
301
|
+
end
|
|
302
|
+
|
|
303
|
+
# Process the pending events. The time at each event loop step
|
|
304
|
+
# is saved into +stats+.
|
|
305
|
+
def process_events(stats = {})
|
|
306
|
+
Thread.current[:application_exceptions] = []
|
|
307
|
+
|
|
308
|
+
add_timepoint(stats, :real_start)
|
|
309
|
+
|
|
310
|
+
# Gather new events and propagate them
|
|
311
|
+
events_errors = Propagation.propagate_events(Control.event_processing)
|
|
312
|
+
add_timepoint(stats, :events)
|
|
313
|
+
|
|
314
|
+
# HACK: events_errors is sometime nil here. It shouldn't
|
|
315
|
+
events_errors ||= []
|
|
316
|
+
|
|
317
|
+
# Generate exceptions from task structure
|
|
318
|
+
structure_errors = structure_checking
|
|
319
|
+
add_timepoint(stats, :structure_check)
|
|
320
|
+
|
|
321
|
+
# Propagate the errors. Note that the plan repairs are taken into
|
|
322
|
+
# account in Propagation.propagate_exceptions drectly. We keep
|
|
323
|
+
# event and structure errors separate since in the first case there
|
|
324
|
+
# is not two-stage handling (all errors that have not been handled
|
|
325
|
+
# are fatal), and in the second case we call #structure_checking
|
|
326
|
+
# again to get the remaining errors
|
|
327
|
+
events_errors = Propagation.propagate_exceptions(events_errors)
|
|
328
|
+
Propagation.propagate_exceptions(structure_errors)
|
|
329
|
+
add_timepoint(stats, :exception_propagation)
|
|
330
|
+
|
|
331
|
+
# Get the remaining problems in the plan structure, and act on it
|
|
332
|
+
fatal_structure_errors = Propagation.remove_inhibited_exceptions(structure_checking)
|
|
333
|
+
fatal_errors = fatal_structure_errors.to_a + events_errors
|
|
334
|
+
kill_tasks = fatal_errors.inject(ValueSet.new) do |kill_tasks, (error, tasks)|
|
|
335
|
+
tasks ||= [*error.task]
|
|
336
|
+
for parent in [*tasks]
|
|
337
|
+
new_tasks = parent.reverse_generated_subgraph(TaskStructure::Hierarchy) - plan.force_gc
|
|
338
|
+
if !new_tasks.empty?
|
|
339
|
+
Control.fatal_exception(error, new_tasks)
|
|
340
|
+
end
|
|
341
|
+
kill_tasks.merge(new_tasks)
|
|
342
|
+
end
|
|
343
|
+
kill_tasks
|
|
344
|
+
end
|
|
345
|
+
add_timepoint(stats, :exceptions_fatal)
|
|
346
|
+
|
|
347
|
+
plan.garbage_collect(kill_tasks)
|
|
348
|
+
add_timepoint(stats, :garbage_collect)
|
|
349
|
+
|
|
350
|
+
application_errors = Thread.current[:application_exceptions]
|
|
351
|
+
Thread.current[:application_exceptions] = nil
|
|
352
|
+
for error, origin in application_errors
|
|
353
|
+
Propagation.add_framework_error(error, origin)
|
|
354
|
+
end
|
|
355
|
+
add_timepoint(stats, :application_errors)
|
|
356
|
+
|
|
357
|
+
if abort_on_exception && !quitting? && !fatal_errors.empty?
|
|
358
|
+
reraise(fatal_errors.map { |e, _| e })
|
|
359
|
+
end
|
|
360
|
+
|
|
361
|
+
ensure
|
|
362
|
+
Thread.current[:application_exceptions] = nil
|
|
363
|
+
end
|
|
364
|
+
|
|
365
|
+
# Blocks until at least once execution cycle has been done
|
|
366
|
+
def wait_one_cycle
|
|
367
|
+
current_cycle = Roby.execute { Roby.control.cycle_index }
|
|
368
|
+
while current_cycle == Roby.execute { Roby.control.cycle_index }
|
|
369
|
+
raise ControlQuitError if !Roby.control.running?
|
|
370
|
+
sleep(Roby.control.cycle_length)
|
|
371
|
+
end
|
|
372
|
+
end
|
|
373
|
+
|
|
374
|
+
@process_once = Queue.new
|
|
375
|
+
@at_cycle_end_handlers = Array.new
|
|
376
|
+
@process_every = Array.new
|
|
377
|
+
@waiting_threads = Array.new
|
|
378
|
+
class << self
|
|
379
|
+
# A list of threads which are currently waitiing for the control thread
|
|
380
|
+
# (see for instance Roby.execute)
|
|
381
|
+
#
|
|
382
|
+
# Control#run will raise ControlQuitError on this threads if they
|
|
383
|
+
# are still waiting while the control is quitting
|
|
384
|
+
attr_reader :waiting_threads
|
|
385
|
+
# A list of blocks to be called at the beginning of the next event loop
|
|
386
|
+
attr_reader :process_once
|
|
387
|
+
# Calls all pending procs in +process_once+
|
|
388
|
+
def call_once # :nodoc:
|
|
389
|
+
while !process_once.empty?
|
|
390
|
+
p = process_once.pop
|
|
391
|
+
begin
|
|
392
|
+
p.call
|
|
393
|
+
rescue Exception => e
|
|
394
|
+
Propagation.add_framework_error(e, "call once in #{p}")
|
|
395
|
+
end
|
|
396
|
+
end
|
|
397
|
+
end
|
|
398
|
+
Control.event_processing << Control.method(:call_once)
|
|
399
|
+
|
|
400
|
+
# Call block once before event processing
|
|
401
|
+
def once(&block); process_once.push block end
|
|
402
|
+
# Call +block+ at each cycle
|
|
403
|
+
def each_cycle(&block); Control.event_processing << block end
|
|
404
|
+
|
|
405
|
+
# A set of blocks that are called at each cycle end
|
|
406
|
+
attr_reader :at_cycle_end_handlers
|
|
407
|
+
|
|
408
|
+
# Call +block+ at the end of the execution cycle
|
|
409
|
+
def at_cycle_end(&block)
|
|
410
|
+
Control.at_cycle_end_handlers << block
|
|
411
|
+
end
|
|
412
|
+
|
|
413
|
+
# A set of blocks which are called every cycle
|
|
414
|
+
attr_reader :process_every
|
|
415
|
+
|
|
416
|
+
# Call +block+ every +duration+ seconds. Note that +duration+ is
|
|
417
|
+
# round up to the cycle size (time between calls is *at least* duration)
|
|
418
|
+
def every(duration, &block)
|
|
419
|
+
Control.once do
|
|
420
|
+
block.call
|
|
421
|
+
process_every << [block, Roby.control.cycle_start, duration]
|
|
422
|
+
end
|
|
423
|
+
block.object_id
|
|
424
|
+
end
|
|
425
|
+
|
|
426
|
+
def remove_periodic_handler(id)
|
|
427
|
+
Roby.execute do
|
|
428
|
+
process_every.delete_if { |spec| spec[0].object_id == id }
|
|
429
|
+
end
|
|
430
|
+
end
|
|
431
|
+
|
|
432
|
+
def call_every # :nodoc:
|
|
433
|
+
now = Roby.control.cycle_start
|
|
434
|
+
length = Roby.control.cycle_length
|
|
435
|
+
process_every.map! do |block, last_call, duration|
|
|
436
|
+
begin
|
|
437
|
+
# Check if the nearest timepoint is the beginning of
|
|
438
|
+
# this cycle or of the next cycle
|
|
439
|
+
if !last_call || (duration - (now - last_call)) < length / 2
|
|
440
|
+
block.call
|
|
441
|
+
last_call = now
|
|
442
|
+
end
|
|
443
|
+
rescue Exception => e
|
|
444
|
+
Propagation.add_framework_error(e, "#call_every, in #{block}")
|
|
445
|
+
end
|
|
446
|
+
[block, last_call, duration]
|
|
447
|
+
end
|
|
448
|
+
end
|
|
449
|
+
Control.event_processing << Control.method(:call_every)
|
|
450
|
+
end
|
|
451
|
+
|
|
452
|
+
|
|
453
|
+
attr_accessor :thread
|
|
454
|
+
def running?; !!@thread end
|
|
455
|
+
|
|
456
|
+
# The cycle length in seconds
|
|
457
|
+
attr_reader :cycle_length
|
|
458
|
+
|
|
459
|
+
# The starting point of this cycle
|
|
460
|
+
attr_reader :cycle_start
|
|
461
|
+
|
|
462
|
+
# The number of this cycle since the beginning
|
|
463
|
+
attr_reader :cycle_index
|
|
464
|
+
|
|
465
|
+
# Main event loop. Valid options are
|
|
466
|
+
# cycle:: the cycle duration in seconds (default: 0.1)
|
|
467
|
+
# drb:: address of the DRuby server if one should be started (default: nil)
|
|
468
|
+
# detach:: if true, start in its own thread (default: false)
|
|
469
|
+
def run(options = {})
|
|
470
|
+
if running?
|
|
471
|
+
raise "there is already a control running in thread #{@thread}"
|
|
472
|
+
end
|
|
473
|
+
|
|
474
|
+
options = validate_options options,
|
|
475
|
+
:cycle => 0.1, :detach => false
|
|
476
|
+
|
|
477
|
+
@quit = 0
|
|
478
|
+
if !options[:detach]
|
|
479
|
+
@thread = Thread.current
|
|
480
|
+
@thread.priority = THREAD_PRIORITY
|
|
481
|
+
end
|
|
482
|
+
|
|
483
|
+
if options[:detach]
|
|
484
|
+
# Start the control thread and wait for @thread to be set
|
|
485
|
+
Roby.condition_variable(true) do |cv, mt|
|
|
486
|
+
mt.synchronize do
|
|
487
|
+
Thread.new do
|
|
488
|
+
run(options.merge(:detach => false)) do
|
|
489
|
+
mt.synchronize { cv.signal }
|
|
490
|
+
end
|
|
491
|
+
end
|
|
492
|
+
cv.wait(mt)
|
|
493
|
+
end
|
|
494
|
+
end
|
|
495
|
+
raise unless @thread
|
|
496
|
+
return
|
|
497
|
+
end
|
|
498
|
+
|
|
499
|
+
yield if block_given?
|
|
500
|
+
|
|
501
|
+
@cycle_length = options[:cycle]
|
|
502
|
+
event_loop
|
|
503
|
+
|
|
504
|
+
ensure
|
|
505
|
+
if Thread.current == self.thread
|
|
506
|
+
Roby::Control.synchronize do
|
|
507
|
+
# reset the options only if we are in the control thread
|
|
508
|
+
@thread = nil
|
|
509
|
+
Control.waiting_threads.each do |th|
|
|
510
|
+
th.raise ControlQuitError
|
|
511
|
+
end
|
|
512
|
+
Control.finalizers.each { |blk| blk.call rescue nil }
|
|
513
|
+
@quit = 0
|
|
514
|
+
end
|
|
515
|
+
end
|
|
516
|
+
end
|
|
517
|
+
|
|
518
|
+
attr_reader :last_stop_count
|
|
519
|
+
def clear
|
|
520
|
+
Control.synchronize do
|
|
521
|
+
plan.missions.dup.each { |t| plan.discard(t) }
|
|
522
|
+
plan.keepalive.dup.each { |t| plan.auto(t) }
|
|
523
|
+
plan.force_gc.merge( plan.known_tasks )
|
|
524
|
+
|
|
525
|
+
quaranteened_subplan = plan.useful_task_component(nil, ValueSet.new, plan.gc_quarantine.dup)
|
|
526
|
+
remaining = plan.known_tasks - quaranteened_subplan
|
|
527
|
+
|
|
528
|
+
if remaining.empty?
|
|
529
|
+
# Have to call #garbage_collect one more to make
|
|
530
|
+
# sure that unneeded events are removed as well
|
|
531
|
+
plan.garbage_collect
|
|
532
|
+
# Done cleaning the tasks, clear the remains
|
|
533
|
+
plan.transactions.each do |trsc|
|
|
534
|
+
trsc.discard_transaction if trsc.self_owned?
|
|
535
|
+
end
|
|
536
|
+
plan.clear
|
|
537
|
+
return
|
|
538
|
+
end
|
|
539
|
+
|
|
540
|
+
if last_stop_count != remaining.size
|
|
541
|
+
if last_stop_count == 0
|
|
542
|
+
Roby.info "control quitting. Waiting for #{remaining.size} tasks to finish (#{plan.size} tasks still in plan)"
|
|
543
|
+
Roby.debug " " + remaining.to_a.join("\n ")
|
|
544
|
+
else
|
|
545
|
+
Roby.info "waiting for #{remaining.size} tasks to finish (#{plan.size} tasks still in plan)"
|
|
546
|
+
Roby.debug " #{remaining.to_a.join("\n ")}"
|
|
547
|
+
end
|
|
548
|
+
if plan.gc_quarantine.size != 0
|
|
549
|
+
Roby.info "#{plan.gc_quarantine.size} tasks in quarantine"
|
|
550
|
+
end
|
|
551
|
+
@last_stop_count = remaining.size
|
|
552
|
+
end
|
|
553
|
+
remaining
|
|
554
|
+
end
|
|
555
|
+
end
|
|
556
|
+
|
|
557
|
+
attr_reader :remaining_cycle_time
|
|
558
|
+
def add_timepoint(stats, name)
|
|
559
|
+
stats[:end] = stats[name] = Time.now - cycle_start
|
|
560
|
+
@remaining_cycle_time = cycle_length - stats[:end]
|
|
561
|
+
end
|
|
562
|
+
def add_expected_duration(stats, name, duration)
|
|
563
|
+
stats[name] = Time.now + duration - cycle_start
|
|
564
|
+
end
|
|
565
|
+
|
|
566
|
+
def event_loop
|
|
567
|
+
@last_stop_count = 0
|
|
568
|
+
@cycle_start = Time.now
|
|
569
|
+
@cycle_index = 0
|
|
570
|
+
|
|
571
|
+
gc_enable_has_argument = begin
|
|
572
|
+
GC.enable(true)
|
|
573
|
+
true
|
|
574
|
+
rescue; false
|
|
575
|
+
end
|
|
576
|
+
stats = Hash.new
|
|
577
|
+
if ObjectSpace.respond_to?(:live_objects)
|
|
578
|
+
stats[:live_objects] = ObjectSpace.live_objects
|
|
579
|
+
end
|
|
580
|
+
|
|
581
|
+
GC.start
|
|
582
|
+
if gc_enable_has_argument
|
|
583
|
+
already_disabled_gc = GC.disable
|
|
584
|
+
end
|
|
585
|
+
loop do
|
|
586
|
+
begin
|
|
587
|
+
if quitting?
|
|
588
|
+
thread.priority = 0
|
|
589
|
+
begin
|
|
590
|
+
return if forced_exit? || !clear
|
|
591
|
+
rescue Exception => e
|
|
592
|
+
Roby.warn "Control failed to clean up"
|
|
593
|
+
Roby.format_exception(e).each do |line|
|
|
594
|
+
Roby.warn line
|
|
595
|
+
end
|
|
596
|
+
return
|
|
597
|
+
end
|
|
598
|
+
end
|
|
599
|
+
|
|
600
|
+
while Time.now > cycle_start + cycle_length
|
|
601
|
+
@cycle_start += cycle_length
|
|
602
|
+
@cycle_index += 1
|
|
603
|
+
end
|
|
604
|
+
stats[:start] = [cycle_start.tv_sec, cycle_start.tv_usec]
|
|
605
|
+
stats[:cycle_index] = cycle_index
|
|
606
|
+
Control.synchronize { process_events(stats) }
|
|
607
|
+
|
|
608
|
+
# Record the statistics about object allocation *before* running the Ruby
|
|
609
|
+
# GC. It is also updated at
|
|
610
|
+
if ObjectSpace.respond_to?(:live_objects)
|
|
611
|
+
live_objects_before_gc = ObjectSpace.live_objects
|
|
612
|
+
end
|
|
613
|
+
|
|
614
|
+
# If the ruby interpreter we run on offers a true/false argument to
|
|
615
|
+
# GC.enable, we disabled the GC and just run GC.enable(true) to make
|
|
616
|
+
# it run immediately if needed. Then, we re-disable it just after.
|
|
617
|
+
if gc_enable_has_argument && remaining_cycle_time > SLEEP_MIN_TIME
|
|
618
|
+
GC.enable(true)
|
|
619
|
+
GC.disable
|
|
620
|
+
end
|
|
621
|
+
add_timepoint(stats, :ruby_gc)
|
|
622
|
+
|
|
623
|
+
if ObjectSpace.respond_to?(:live_objects)
|
|
624
|
+
live_objects_after_gc = ObjectSpace.live_objects
|
|
625
|
+
end
|
|
626
|
+
|
|
627
|
+
# Sleep if there is enough time for it
|
|
628
|
+
if remaining_cycle_time > SLEEP_MIN_TIME
|
|
629
|
+
add_expected_duration(stats, :sleep, remaining_cycle_time)
|
|
630
|
+
sleep(remaining_cycle_time)
|
|
631
|
+
end
|
|
632
|
+
add_timepoint(stats, :sleep)
|
|
633
|
+
|
|
634
|
+
# Add some statistics and call cycle_end
|
|
635
|
+
if defined? Roby::Log
|
|
636
|
+
stats[:log_queue_size] = Roby::Log.logged_events.size
|
|
637
|
+
end
|
|
638
|
+
stats[:plan_task_count] = plan.known_tasks.size
|
|
639
|
+
stats[:plan_event_count] = plan.free_events.size
|
|
640
|
+
process_time = Process.times
|
|
641
|
+
stats[:cpu_time] = (process_time.utime + process_time.stime) * 1000
|
|
642
|
+
|
|
643
|
+
if ObjectSpace.respond_to?(:live_objects)
|
|
644
|
+
live_objects = ObjectSpace.live_objects
|
|
645
|
+
stats[:object_allocation] = live_objects - stats[:live_objects] - (live_objects_after_gc - live_objects_before_gc)
|
|
646
|
+
stats[:live_objects] = live_objects
|
|
647
|
+
end
|
|
648
|
+
|
|
649
|
+
cycle_end(stats)
|
|
650
|
+
|
|
651
|
+
stats = Hash.new
|
|
652
|
+
stats[:live_objects] = live_objects
|
|
653
|
+
@cycle_start += cycle_length
|
|
654
|
+
@cycle_index += 1
|
|
655
|
+
|
|
656
|
+
rescue Exception => e
|
|
657
|
+
Roby.warn "Control quitting because of unhandled exception"
|
|
658
|
+
Roby.format_exception(e).each do |line|
|
|
659
|
+
Roby.warn line
|
|
660
|
+
end
|
|
661
|
+
quit
|
|
662
|
+
end
|
|
663
|
+
end
|
|
664
|
+
|
|
665
|
+
ensure
|
|
666
|
+
GC.enable if !already_disabled_gc
|
|
667
|
+
|
|
668
|
+
if !plan.known_tasks.empty?
|
|
669
|
+
Roby.warn "the following tasks are still present in the plan:"
|
|
670
|
+
plan.known_tasks.each do |t|
|
|
671
|
+
Roby.warn " #{t}"
|
|
672
|
+
end
|
|
673
|
+
end
|
|
674
|
+
end
|
|
675
|
+
|
|
676
|
+
@finalizers = []
|
|
677
|
+
def self.finalizers; @finalizers end
|
|
678
|
+
|
|
679
|
+
# True if the control thread is currently quitting
|
|
680
|
+
def quitting?; @quit > 0 end
|
|
681
|
+
# True if the control thread is currently quitting
|
|
682
|
+
def forced_exit?; @quit > 1 end
|
|
683
|
+
# Make control quit
|
|
684
|
+
def quit; @quit += 1 end
|
|
685
|
+
|
|
686
|
+
# Called at each cycle end
|
|
687
|
+
def cycle_end(stats)
|
|
688
|
+
super if defined? super
|
|
689
|
+
|
|
690
|
+
Control.at_cycle_end_handlers.each do |handler|
|
|
691
|
+
begin
|
|
692
|
+
handler.call
|
|
693
|
+
rescue Exception => e
|
|
694
|
+
Propagation.add_framework_error(e, "during cycle end handler #{handler}")
|
|
695
|
+
end
|
|
696
|
+
end
|
|
697
|
+
end
|
|
698
|
+
|
|
699
|
+
# If the event thread has been started in its own thread,
|
|
700
|
+
# wait for it to terminate
|
|
701
|
+
def join
|
|
702
|
+
thread.join if thread
|
|
703
|
+
|
|
704
|
+
rescue Interrupt
|
|
705
|
+
Roby::Control.synchronize do
|
|
706
|
+
return unless thread
|
|
707
|
+
|
|
708
|
+
Roby.logger.level = Logger::INFO
|
|
709
|
+
Roby.warn "received interruption request"
|
|
710
|
+
quit
|
|
711
|
+
if @quit > 2
|
|
712
|
+
thread.raise Interrupt, "interrupting control thread at user request"
|
|
713
|
+
end
|
|
714
|
+
end
|
|
715
|
+
|
|
716
|
+
retry
|
|
717
|
+
end
|
|
718
|
+
|
|
719
|
+
attr_reader :cycle_index
|
|
720
|
+
|
|
721
|
+
# Hook called when a set of tasks is being killed because of an exception
|
|
722
|
+
def self.fatal_exception(error, tasks)
|
|
723
|
+
super if defined? super
|
|
724
|
+
Roby.format_exception(error.exception).each do |line|
|
|
725
|
+
Roby.warn line
|
|
726
|
+
end
|
|
727
|
+
end
|
|
728
|
+
# Hook called when an exception +e+ has been handled by +task+
|
|
729
|
+
def self.handled_exception(e, task); super if defined? super end
|
|
730
|
+
end
|
|
731
|
+
|
|
732
|
+
# Get all missions that have failed
|
|
733
|
+
def self.check_failed_missions(plan)
|
|
734
|
+
result = []
|
|
735
|
+
for task in plan.missions
|
|
736
|
+
result << MissionFailedError.new(task) if task.failed?
|
|
737
|
+
end
|
|
738
|
+
result
|
|
739
|
+
end
|
|
740
|
+
Control.structure_checks << method(:check_failed_missions)
|
|
741
|
+
end
|
|
742
|
+
|
|
743
|
+
require 'roby/propagation'
|
|
744
|
+
|
|
745
|
+
module Roby
|
|
746
|
+
@control = Control.instance
|
|
747
|
+
end
|