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
|
+
require 'roby/task'
|
|
2
|
+
require 'roby/relations/planned_by'
|
|
3
|
+
require 'roby/control'
|
|
4
|
+
require 'roby/transactions'
|
|
5
|
+
|
|
6
|
+
module Roby
|
|
7
|
+
# An asynchronous planning task using Ruby threads
|
|
8
|
+
class PlanningTask < Roby::Task
|
|
9
|
+
attr_reader :planner, :transaction
|
|
10
|
+
|
|
11
|
+
arguments :planner_model, :method_name,
|
|
12
|
+
:method_options, :planned_model,
|
|
13
|
+
:planning_owners
|
|
14
|
+
|
|
15
|
+
def self.filter_options(options)
|
|
16
|
+
task_options, method_options = Kernel.filter_options options,
|
|
17
|
+
:planner_model => nil,
|
|
18
|
+
:method_name => nil,
|
|
19
|
+
:method_options => {},
|
|
20
|
+
:planned_model => nil,
|
|
21
|
+
:planning_owners => nil
|
|
22
|
+
|
|
23
|
+
if !task_options[:planner_model]
|
|
24
|
+
raise ArgumentError, "missing required argument 'planner_model'"
|
|
25
|
+
elsif !task_options[:method_name]
|
|
26
|
+
raise ArgumentError, "missing required argument 'method_name'"
|
|
27
|
+
end
|
|
28
|
+
task_options[:planned_model] ||= nil
|
|
29
|
+
task_options[:method_options] ||= Hash.new
|
|
30
|
+
task_options[:method_options].merge! method_options
|
|
31
|
+
task_options[:planning_owners] ||= nil
|
|
32
|
+
task_options
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
def initialize(options)
|
|
36
|
+
task_options = PlanningTask.filter_options(options)
|
|
37
|
+
super(task_options)
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
def planned_model
|
|
41
|
+
arguments[:planned_model] ||= planner_model.model_of(method_name, method_options).returns || Roby::Task
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
def to_s
|
|
46
|
+
"#{super}[#{method_name}:#{method_options}] -> #{@planned_task || "nil"}"
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
def planned_task
|
|
50
|
+
task = planned_tasks.find { true }
|
|
51
|
+
if !task && pending?
|
|
52
|
+
task = planned_model.new
|
|
53
|
+
task.planned_by self
|
|
54
|
+
task.executable = false
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
task
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
# The thread that is running the planner
|
|
61
|
+
attr_reader :thread
|
|
62
|
+
# The transaction in which we build the new plan. It gets committed on
|
|
63
|
+
# success.
|
|
64
|
+
attr_reader :transaction
|
|
65
|
+
# The planner result. It is either an exception or a task object
|
|
66
|
+
attr_reader :result
|
|
67
|
+
|
|
68
|
+
# Starts planning
|
|
69
|
+
event :start do |context|
|
|
70
|
+
emit :start
|
|
71
|
+
|
|
72
|
+
if planning_owners
|
|
73
|
+
@transaction = Distributed::Transaction.new(plan)
|
|
74
|
+
planning_owners.each do |peer|
|
|
75
|
+
transaction.add_owner peer
|
|
76
|
+
end
|
|
77
|
+
else
|
|
78
|
+
@transaction = Transaction.new(plan)
|
|
79
|
+
end
|
|
80
|
+
@planner = planner_model.new(transaction)
|
|
81
|
+
|
|
82
|
+
@thread = Thread.new do
|
|
83
|
+
Thread.current.priority = 0
|
|
84
|
+
planning_thread(context)
|
|
85
|
+
end
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
def planning_thread(context)
|
|
89
|
+
result_task = planner.send(method_name, method_options.merge(:context => context))
|
|
90
|
+
|
|
91
|
+
# Don't replace the planning task with ourselves if the
|
|
92
|
+
# transaction specifies another planning task
|
|
93
|
+
if !result_task.planning_task
|
|
94
|
+
result_task.planned_by transaction[self]
|
|
95
|
+
end
|
|
96
|
+
|
|
97
|
+
if placeholder = planned_task
|
|
98
|
+
placeholder = transaction[placeholder]
|
|
99
|
+
transaction.replace(placeholder, result_task)
|
|
100
|
+
placeholder.remove_planning_task transaction[self]
|
|
101
|
+
end
|
|
102
|
+
|
|
103
|
+
# If the transaction is distributed, and is not proposed to all
|
|
104
|
+
# owners, do it
|
|
105
|
+
transaction.propose
|
|
106
|
+
transaction.commit_transaction
|
|
107
|
+
|
|
108
|
+
@result = result_task
|
|
109
|
+
end
|
|
110
|
+
|
|
111
|
+
# Polls for the planning thread end
|
|
112
|
+
poll do
|
|
113
|
+
if thread.alive?
|
|
114
|
+
return
|
|
115
|
+
end
|
|
116
|
+
|
|
117
|
+
# Check if the transaction has been committed. If it is not the
|
|
118
|
+
# case, assume that the thread failed
|
|
119
|
+
if transaction.freezed?
|
|
120
|
+
emit :success
|
|
121
|
+
else
|
|
122
|
+
error = begin
|
|
123
|
+
thread.value
|
|
124
|
+
rescue Exception => e
|
|
125
|
+
@result = e
|
|
126
|
+
end
|
|
127
|
+
|
|
128
|
+
transaction.discard_transaction
|
|
129
|
+
emit :failed, error
|
|
130
|
+
end
|
|
131
|
+
end
|
|
132
|
+
|
|
133
|
+
# Stops the planning thread
|
|
134
|
+
event :stop do |context|
|
|
135
|
+
planner.stop
|
|
136
|
+
end
|
|
137
|
+
on :stop do
|
|
138
|
+
@transaction = nil
|
|
139
|
+
@planner = nil
|
|
140
|
+
@thread = nil
|
|
141
|
+
end
|
|
142
|
+
|
|
143
|
+
class TransactionProxy < Roby::Transactions::Task
|
|
144
|
+
proxy_for PlanningTask
|
|
145
|
+
def_delegator :@__getobj__, :planner
|
|
146
|
+
def_delegator :@__getobj__, :method_name
|
|
147
|
+
def_delegator :@__getobj__, :method_options
|
|
148
|
+
end
|
|
149
|
+
end
|
|
150
|
+
end
|
|
151
|
+
|
|
@@ -0,0 +1,562 @@
|
|
|
1
|
+
require 'roby/support'
|
|
2
|
+
require 'roby/exceptions'
|
|
3
|
+
require 'utilrb/exception/full_message'
|
|
4
|
+
require 'utilrb/unbound_method'
|
|
5
|
+
require 'roby/relations/error_handling'
|
|
6
|
+
|
|
7
|
+
# This module contains all code necessary for the propagation steps during
|
|
8
|
+
# execution. This includes event and exception propagation
|
|
9
|
+
#
|
|
10
|
+
# == Event propagation
|
|
11
|
+
# Event propagation is based on three event relations:
|
|
12
|
+
#
|
|
13
|
+
# * Signal describes the commands that must be called when an event occurs. The
|
|
14
|
+
# signalled event command is called when the signalling events are emitted. If
|
|
15
|
+
# more than one event are signalling the same event in the same execution
|
|
16
|
+
# cycle, the command will be called only once
|
|
17
|
+
# * Forwarding describes the events that must be emitted whenever a source
|
|
18
|
+
# event is. It is to be used as a way to define event aliases (for instance
|
|
19
|
+
# 'stop' is an alias for 'success'), because a task is stopped when it has
|
|
20
|
+
# finished with success. Unlike with signals, if more than one event is
|
|
21
|
+
# forwarded to the same event in the same cycle, the target event will be
|
|
22
|
+
# emitted as many times as the incoming events.
|
|
23
|
+
# * Precedence is a graph which constrains the order in which propagation is
|
|
24
|
+
# done. If there is a a => b edge in Precedence, and if both events are either
|
|
25
|
+
# called and/or forwarded in the same cycle, then 'a' will be propagated before
|
|
26
|
+
# 'b'. All edges in Signal and Forwarding are present in Precedence
|
|
27
|
+
#
|
|
28
|
+
# == Exception propagation
|
|
29
|
+
#
|
|
30
|
+
module Roby::Propagation
|
|
31
|
+
extend Logger::Hierarchy
|
|
32
|
+
extend Logger::Forward
|
|
33
|
+
|
|
34
|
+
@@propagation_id = 0
|
|
35
|
+
def self.propagation_id; Thread.current[:propagation_id] end
|
|
36
|
+
|
|
37
|
+
# If we are currently in the propagation stage
|
|
38
|
+
def self.gathering?; !!Thread.current[:propagation] end
|
|
39
|
+
# The set of source events for the current propagation action. This is a
|
|
40
|
+
# mix of EventGenerator and Event objects.
|
|
41
|
+
def self.sources; Thread.current[:propagation_sources] end
|
|
42
|
+
# The set of events extracted from PropagationException.sources
|
|
43
|
+
def self.source_events
|
|
44
|
+
result = ValueSet.new
|
|
45
|
+
for ev in Thread.current[:propagation_sources]
|
|
46
|
+
if ev.respond_to?(:generator)
|
|
47
|
+
result << ev
|
|
48
|
+
end
|
|
49
|
+
end
|
|
50
|
+
result
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
# The set of generators extracted from Propagation.sources
|
|
54
|
+
def self.source_generators
|
|
55
|
+
result = ValueSet.new
|
|
56
|
+
for ev in Thread.current[:propagation_sources]
|
|
57
|
+
result << if ev.respond_to?(:generator)
|
|
58
|
+
ev.generator
|
|
59
|
+
else
|
|
60
|
+
ev
|
|
61
|
+
end
|
|
62
|
+
end
|
|
63
|
+
result
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
@@delayed_events = []
|
|
67
|
+
def self.delayed_events; @@delayed_events end
|
|
68
|
+
def self.add_event_delay(time, forward, source, signalled, context)
|
|
69
|
+
delayed_events << [time, forward, source, signalled, context]
|
|
70
|
+
end
|
|
71
|
+
def self.execute_delayed_events
|
|
72
|
+
reftime = Time.now
|
|
73
|
+
delayed_events.delete_if do |time, forward, source, signalled, context|
|
|
74
|
+
if time < reftime
|
|
75
|
+
add_event_propagation(forward, [source], signalled, context, nil)
|
|
76
|
+
true
|
|
77
|
+
end
|
|
78
|
+
end
|
|
79
|
+
end
|
|
80
|
+
module RemoveDelayedOnFinalized
|
|
81
|
+
def finalized_event(event)
|
|
82
|
+
super if defined? super
|
|
83
|
+
Roby::Propagation.delayed_events.delete_if { |_, _, _, signalled, _| signalled == event }
|
|
84
|
+
end
|
|
85
|
+
end
|
|
86
|
+
Roby::Plan.include RemoveDelayedOnFinalized
|
|
87
|
+
Roby::Control.event_processing << Roby::Propagation.method(:execute_delayed_events)
|
|
88
|
+
|
|
89
|
+
# Begin an event propagation stage
|
|
90
|
+
def self.gather_propagation(initial_set = Hash.new)
|
|
91
|
+
raise InternalError, "nested call to #gather_propagation" if gathering?
|
|
92
|
+
Thread.current[:propagation] = initial_set
|
|
93
|
+
|
|
94
|
+
propagation_context(nil) { yield }
|
|
95
|
+
|
|
96
|
+
return Thread.current[:propagation]
|
|
97
|
+
ensure
|
|
98
|
+
Thread.current[:propagation] = nil
|
|
99
|
+
end
|
|
100
|
+
|
|
101
|
+
def self.to_execution_exception(error)
|
|
102
|
+
Roby::ExecutionException.new(error)
|
|
103
|
+
end
|
|
104
|
+
|
|
105
|
+
def self.add_error(e)
|
|
106
|
+
if Thread.current[:propagation_exceptions]
|
|
107
|
+
plan_exception = to_execution_exception(e)
|
|
108
|
+
Thread.current[:propagation_exceptions] << plan_exception
|
|
109
|
+
else
|
|
110
|
+
if e.respond_to?(:error) && e.error
|
|
111
|
+
add_framework_error(e.error, "error outside error handling")
|
|
112
|
+
else
|
|
113
|
+
add_framework_error(e, "error outside error handling")
|
|
114
|
+
end
|
|
115
|
+
end
|
|
116
|
+
end
|
|
117
|
+
|
|
118
|
+
def self.gather_framework_errors(source)
|
|
119
|
+
yield
|
|
120
|
+
rescue Exception => e
|
|
121
|
+
add_framework_error(e, source)
|
|
122
|
+
end
|
|
123
|
+
|
|
124
|
+
def self.add_framework_error(error, source)
|
|
125
|
+
if Thread.current[:application_exceptions]
|
|
126
|
+
Thread.current[:application_exceptions] << [error, source]
|
|
127
|
+
elsif Roby.control.abort_on_application_exception || error.kind_of?(SignalException)
|
|
128
|
+
raise error, "in #{source}: #{error.message}", error.backtrace
|
|
129
|
+
else
|
|
130
|
+
Roby.error "Application error in #{source}: #{error.full_message}"
|
|
131
|
+
end
|
|
132
|
+
end
|
|
133
|
+
|
|
134
|
+
# Sets the source_event and source_generator variables according
|
|
135
|
+
# to +source+. +source+ is the +from+ argument of #add_event_propagation
|
|
136
|
+
def self.propagation_context(sources)
|
|
137
|
+
raise InternalError, "not in a gathering context in #fire" unless gathering?
|
|
138
|
+
|
|
139
|
+
if sources
|
|
140
|
+
current_sources = sources
|
|
141
|
+
Thread.current[:propagation_sources] = sources
|
|
142
|
+
else
|
|
143
|
+
Thread.current[:propagation_sources] = []
|
|
144
|
+
end
|
|
145
|
+
|
|
146
|
+
yield Thread.current[:propagation]
|
|
147
|
+
|
|
148
|
+
ensure
|
|
149
|
+
Thread.current[:propagation_sources] = sources
|
|
150
|
+
end
|
|
151
|
+
|
|
152
|
+
# Adds a propagation to the next propagation step. More specifically, it
|
|
153
|
+
# adds either forwarding or signalling the set of Event objects +from+ to
|
|
154
|
+
# the +signalled+ event generator, with the context +context+
|
|
155
|
+
def self.add_event_propagation(forward, from, signalled, context, timespec)
|
|
156
|
+
if signalled.plan != Roby.plan
|
|
157
|
+
raise Roby::EventNotExecutable.new(signalled), "#{signalled} not in main plan"
|
|
158
|
+
end
|
|
159
|
+
|
|
160
|
+
step = (Thread.current[:propagation][signalled] ||= [nil, nil])
|
|
161
|
+
from = [nil] unless from && !from.empty?
|
|
162
|
+
|
|
163
|
+
step = if forward then (step[0] ||= [])
|
|
164
|
+
else (step[1] ||= [])
|
|
165
|
+
end
|
|
166
|
+
|
|
167
|
+
from.each do |ev|
|
|
168
|
+
step << ev << context << timespec
|
|
169
|
+
end
|
|
170
|
+
end
|
|
171
|
+
|
|
172
|
+
# Calls its block in a #gather_propagation context and propagate events
|
|
173
|
+
# that have been called and/or emitted by the block
|
|
174
|
+
#
|
|
175
|
+
# If a block is given, it is called with the initial set of events: the
|
|
176
|
+
# events we should consider as already emitted in the following propagation.
|
|
177
|
+
# +seeds+ si a list of procs which should be called to initiate the propagation
|
|
178
|
+
# (i.e. build an initial set of events)
|
|
179
|
+
def self.propagate_events(seeds = [])
|
|
180
|
+
if Thread.current[:propagation_exceptions]
|
|
181
|
+
raise InternalError, "recursive call to propagate_events"
|
|
182
|
+
end
|
|
183
|
+
|
|
184
|
+
Thread.current[:propagation_id] = (@@propagation_id += 1)
|
|
185
|
+
Thread.current[:propagation_exceptions] = []
|
|
186
|
+
|
|
187
|
+
initial_set = []
|
|
188
|
+
next_step = gather_propagation do
|
|
189
|
+
gather_framework_errors('initial set setup') { yield(initial_set) } if block_given?
|
|
190
|
+
gather_framework_errors('distributed events') { Roby::Distributed.process_pending }
|
|
191
|
+
seeds.each do |s|
|
|
192
|
+
gather_framework_errors("seed #{s}") { s.call }
|
|
193
|
+
end
|
|
194
|
+
end
|
|
195
|
+
|
|
196
|
+
# Problem with postponed: the object is included in already_seen while it
|
|
197
|
+
# has not been fired
|
|
198
|
+
already_seen = initial_set.to_set
|
|
199
|
+
|
|
200
|
+
while !next_step.empty?
|
|
201
|
+
next_step = event_propagation_step(next_step, already_seen)
|
|
202
|
+
end
|
|
203
|
+
Thread.current[:propagation_exceptions]
|
|
204
|
+
|
|
205
|
+
ensure
|
|
206
|
+
Thread.current[:propagation_id] = nil
|
|
207
|
+
Thread.current[:propagation_exceptions] = nil
|
|
208
|
+
end
|
|
209
|
+
|
|
210
|
+
def self.validate_timespec(timespec)
|
|
211
|
+
if timespec
|
|
212
|
+
timespec = validate_options timespec, [:delay, :at]
|
|
213
|
+
end
|
|
214
|
+
end
|
|
215
|
+
def self.make_delay(timeref, source, signalled, timespec)
|
|
216
|
+
if delay = timespec[:delay] then timeref + delay
|
|
217
|
+
elsif at = timespec[:at] then at
|
|
218
|
+
else
|
|
219
|
+
raise ArgumentError, "invalid timespec #{timespec}"
|
|
220
|
+
end
|
|
221
|
+
end
|
|
222
|
+
|
|
223
|
+
@event_ordering = Array.new
|
|
224
|
+
@event_priorities = Hash.new
|
|
225
|
+
class << self
|
|
226
|
+
# The topological ordering of events w.r.t. the Precedence relation
|
|
227
|
+
attr_reader :event_ordering
|
|
228
|
+
# The event => index hash which give the propagation priority for each
|
|
229
|
+
# event
|
|
230
|
+
attr_reader :event_priorities
|
|
231
|
+
end
|
|
232
|
+
|
|
233
|
+
# This module hooks in plan modifications to clear the event ordering cache
|
|
234
|
+
# (Propagation.event_ordering) when needed.
|
|
235
|
+
#
|
|
236
|
+
# It is included in the main plan by Control#initialize
|
|
237
|
+
module ExecutablePlanChanged
|
|
238
|
+
def discovered_events(objects)
|
|
239
|
+
super if defined? super
|
|
240
|
+
Roby::Propagation.event_ordering.clear
|
|
241
|
+
end
|
|
242
|
+
def discovered_tasks(objects)
|
|
243
|
+
super if defined? super
|
|
244
|
+
Roby::Propagation.event_ordering.clear
|
|
245
|
+
end
|
|
246
|
+
end
|
|
247
|
+
|
|
248
|
+
# This module hooks in event relation modifications to clear the event
|
|
249
|
+
# ordering cache (Propagation.event_ordering) when needed.
|
|
250
|
+
module EventPrecedenceChanged
|
|
251
|
+
def added_child_object(child, relations, info)
|
|
252
|
+
super if defined? super
|
|
253
|
+
if relations.include?(Roby::EventStructure::Precedence) && plan == Roby.plan
|
|
254
|
+
Roby::Propagation.event_ordering.clear
|
|
255
|
+
end
|
|
256
|
+
end
|
|
257
|
+
def removed_child_object(child, relations)
|
|
258
|
+
super if defined? super
|
|
259
|
+
if relations.include?(Roby::EventStructure::Precedence) && plan == Roby.plan
|
|
260
|
+
Roby::Propagation.event_ordering.clear
|
|
261
|
+
end
|
|
262
|
+
end
|
|
263
|
+
end
|
|
264
|
+
Roby::EventGenerator.include EventPrecedenceChanged
|
|
265
|
+
|
|
266
|
+
# Determines the event in +current_step+ which should be signalled now.
|
|
267
|
+
# Removes it from the set and returns the event and the associated
|
|
268
|
+
# propagation information
|
|
269
|
+
def self.next_event(pending)
|
|
270
|
+
if event_ordering.empty?
|
|
271
|
+
Roby::EventStructure::Precedence.topological_sort(event_ordering)
|
|
272
|
+
event_priorities.clear
|
|
273
|
+
i = 0
|
|
274
|
+
for ev in event_ordering
|
|
275
|
+
event_priorities[ev] = i
|
|
276
|
+
i += 1
|
|
277
|
+
end
|
|
278
|
+
end
|
|
279
|
+
|
|
280
|
+
signalled, min = nil, event_ordering.size
|
|
281
|
+
for propagation_step in pending
|
|
282
|
+
event = propagation_step[0]
|
|
283
|
+
if priority = event_priorities[event]
|
|
284
|
+
if priority < min
|
|
285
|
+
signalled = event
|
|
286
|
+
min = priority
|
|
287
|
+
end
|
|
288
|
+
else
|
|
289
|
+
signalled = event
|
|
290
|
+
break
|
|
291
|
+
end
|
|
292
|
+
end
|
|
293
|
+
[signalled, *pending.delete(signalled)]
|
|
294
|
+
end
|
|
295
|
+
|
|
296
|
+
def self.prepare_propagation(signalled, forward, info)
|
|
297
|
+
timeref = Time.now
|
|
298
|
+
|
|
299
|
+
source_events, source_generators, context = ValueSet.new, ValueSet.new, []
|
|
300
|
+
|
|
301
|
+
delayed = true
|
|
302
|
+
info.each_slice(3) do |src, ctxt, time|
|
|
303
|
+
if time && (delay = make_delay(timeref, src, signalled, time))
|
|
304
|
+
add_event_delay(delay, forward, src, signalled, ctxt)
|
|
305
|
+
next
|
|
306
|
+
end
|
|
307
|
+
|
|
308
|
+
delayed = false
|
|
309
|
+
|
|
310
|
+
# Merge identical signals. Needed because two different event handlers
|
|
311
|
+
# can both call #emit, and two signals are set up
|
|
312
|
+
if src
|
|
313
|
+
if src.respond_to?(:generator)
|
|
314
|
+
source_events << src
|
|
315
|
+
source_generators << src.generator
|
|
316
|
+
else
|
|
317
|
+
source_generators << src
|
|
318
|
+
end
|
|
319
|
+
end
|
|
320
|
+
if ctxt
|
|
321
|
+
context.concat ctxt
|
|
322
|
+
end
|
|
323
|
+
end
|
|
324
|
+
|
|
325
|
+
unless delayed
|
|
326
|
+
[source_events, source_generators, (context unless context.empty?)]
|
|
327
|
+
end
|
|
328
|
+
end
|
|
329
|
+
|
|
330
|
+
# Propagate one step
|
|
331
|
+
#
|
|
332
|
+
# +current_step+ describes all pending emissions and calls. +already_seen+
|
|
333
|
+
# is obsolete and is not used anymore.
|
|
334
|
+
#
|
|
335
|
+
# This method calls Propagation.next_event to get the description of the
|
|
336
|
+
# next event to call. If there are signals going to this event, they are
|
|
337
|
+
# processed and the forwardings will be treated in the next step.
|
|
338
|
+
#
|
|
339
|
+
# The method returns the next set of pending emissions and calls, adding
|
|
340
|
+
# the forwardings and signals that the propagation of the considered event
|
|
341
|
+
# have added.
|
|
342
|
+
def self.event_propagation_step(current_step, already_seen)
|
|
343
|
+
signalled, forward_info, call_info = next_event(current_step)
|
|
344
|
+
|
|
345
|
+
next_step = nil
|
|
346
|
+
if call_info
|
|
347
|
+
source_events, source_generators, context = prepare_propagation(signalled, false, call_info)
|
|
348
|
+
if source_events
|
|
349
|
+
for source_ev in source_events
|
|
350
|
+
source_ev.generator.signalling(source_ev, signalled)
|
|
351
|
+
end
|
|
352
|
+
|
|
353
|
+
if signalled.self_owned?
|
|
354
|
+
next_step = gather_propagation(current_step) do
|
|
355
|
+
propagation_context(source_events | source_generators) do |result|
|
|
356
|
+
begin
|
|
357
|
+
signalled.call_without_propagation(context)
|
|
358
|
+
rescue Roby::LocalizedError => e
|
|
359
|
+
add_error(e)
|
|
360
|
+
rescue Exception => e
|
|
361
|
+
add_error(Roby::CommandFailed.new(e, signalled))
|
|
362
|
+
end
|
|
363
|
+
end
|
|
364
|
+
end
|
|
365
|
+
end
|
|
366
|
+
end
|
|
367
|
+
|
|
368
|
+
if forward_info
|
|
369
|
+
next_step ||= Hash.new
|
|
370
|
+
next_step[signalled] ||= []
|
|
371
|
+
next_step[signalled][0] ||= []
|
|
372
|
+
next_step[signalled][0].concat forward_info
|
|
373
|
+
end
|
|
374
|
+
|
|
375
|
+
elsif forward_info
|
|
376
|
+
source_events, source_generators, context = prepare_propagation(signalled, true, forward_info)
|
|
377
|
+
if source_events
|
|
378
|
+
for source_ev in source_events
|
|
379
|
+
source_ev.generator.forwarding(source_ev, signalled)
|
|
380
|
+
end
|
|
381
|
+
|
|
382
|
+
# If the destination event is not owned, but if the peer is not
|
|
383
|
+
# connected, the event is our responsibility now.
|
|
384
|
+
if signalled.self_owned? || !signalled.owners.any? { |peer| peer != Roby::Distributed && peer.connected? }
|
|
385
|
+
next_step = gather_propagation(current_step) do
|
|
386
|
+
propagation_context(source_events | source_generators) do |result|
|
|
387
|
+
begin
|
|
388
|
+
signalled.emit_without_propagation(context)
|
|
389
|
+
rescue Roby::LocalizedError => e
|
|
390
|
+
add_error(e)
|
|
391
|
+
rescue Exception => e
|
|
392
|
+
add_error(Roby::EmissionFailed.new(e, signalled))
|
|
393
|
+
end
|
|
394
|
+
end
|
|
395
|
+
end
|
|
396
|
+
end
|
|
397
|
+
end
|
|
398
|
+
end
|
|
399
|
+
|
|
400
|
+
current_step.merge!(next_step) if next_step
|
|
401
|
+
current_step
|
|
402
|
+
end
|
|
403
|
+
|
|
404
|
+
# Checks if +error+ is being repaired in the corresponding plan. Note that
|
|
405
|
+
# +error+ is supposed to be the original exception, not the corresponding
|
|
406
|
+
# ExecutionException object
|
|
407
|
+
def self.remove_inhibited_exceptions(exceptions)
|
|
408
|
+
exceptions.find_all do |e, _|
|
|
409
|
+
error = e.exception
|
|
410
|
+
if !error.respond_to?(:failed_event) ||
|
|
411
|
+
!(failure_point = error.failed_event)
|
|
412
|
+
true
|
|
413
|
+
else
|
|
414
|
+
Roby.plan.repairs_for(failure_point).empty?
|
|
415
|
+
end
|
|
416
|
+
end
|
|
417
|
+
end
|
|
418
|
+
|
|
419
|
+
def self.remove_useless_repairs
|
|
420
|
+
plan = Roby.plan
|
|
421
|
+
|
|
422
|
+
finished_repairs = plan.repairs.dup.delete_if { |_, task| task.starting? || task.running? }
|
|
423
|
+
for repair in finished_repairs
|
|
424
|
+
plan.remove_repair(repair[1])
|
|
425
|
+
end
|
|
426
|
+
|
|
427
|
+
finished_repairs
|
|
428
|
+
end
|
|
429
|
+
|
|
430
|
+
# Performs exception propagation for the given ExecutionException objects
|
|
431
|
+
# Returns all exceptions which have found no handlers in the task hierarchy
|
|
432
|
+
def self.propagate_exceptions(exceptions)
|
|
433
|
+
fatal = [] # the list of exceptions for which no handler has been found
|
|
434
|
+
|
|
435
|
+
# Remove finished repairs. Those are still considered during this cycle,
|
|
436
|
+
# as it is possible that some actions have been scheduled for the
|
|
437
|
+
# beginning of the next cycle through Roby.once
|
|
438
|
+
finished_repairs = remove_useless_repairs
|
|
439
|
+
# Remove remove exceptions for which a repair exists
|
|
440
|
+
exceptions = remove_inhibited_exceptions(exceptions)
|
|
441
|
+
|
|
442
|
+
# Install new repairs based on the HandledBy task relation. If a repair
|
|
443
|
+
# is installed, remove the exception from the set of errors to handle
|
|
444
|
+
exceptions.delete_if do |e, _|
|
|
445
|
+
# Check for handled_by relations which would be able to handle +e+
|
|
446
|
+
error = e.exception
|
|
447
|
+
next unless (failed_event = error.failed_event)
|
|
448
|
+
next unless (failed_task = error.failed_task)
|
|
449
|
+
next if finished_repairs.has_key?(failed_event)
|
|
450
|
+
|
|
451
|
+
failed_generator = error.failed_generator
|
|
452
|
+
|
|
453
|
+
repair = failed_task.find_error_handler do |repairing_task, event_set|
|
|
454
|
+
event_set.find do |repaired_generator|
|
|
455
|
+
repaired_generator = failed_task.event(repaired_generator)
|
|
456
|
+
|
|
457
|
+
!repairing_task.finished? &&
|
|
458
|
+
(repaired_generator == failed_generator ||
|
|
459
|
+
Roby::EventStructure::Forwarding.reachable?(failed_generator, repaired_generator))
|
|
460
|
+
end
|
|
461
|
+
end
|
|
462
|
+
|
|
463
|
+
if repair
|
|
464
|
+
failed_task.plan.add_repair(failed_event, repair)
|
|
465
|
+
true
|
|
466
|
+
else
|
|
467
|
+
false
|
|
468
|
+
end
|
|
469
|
+
end
|
|
470
|
+
|
|
471
|
+
while !exceptions.empty?
|
|
472
|
+
by_task = Hash.new { |h, k| h[k] = Array.new }
|
|
473
|
+
by_task = exceptions.inject(by_task) do |by_task, (e, parents)|
|
|
474
|
+
unless e.task
|
|
475
|
+
Roby.log_exception(e.exception, Roby, :fatal)
|
|
476
|
+
raise NotImplementedError, "we do not yet handle exceptions from external event generators. Got #{e.exception.full_message}"
|
|
477
|
+
end
|
|
478
|
+
parents ||= e.task.parent_objects(Roby::TaskStructure::Hierarchy)
|
|
479
|
+
|
|
480
|
+
has_parent = false
|
|
481
|
+
[*parents].each do |parent|
|
|
482
|
+
next if parent.finished?
|
|
483
|
+
|
|
484
|
+
if has_parent # we have more than one parent
|
|
485
|
+
e = e.fork
|
|
486
|
+
end
|
|
487
|
+
|
|
488
|
+
parent_exceptions = by_task[parent]
|
|
489
|
+
if s = parent_exceptions.find { |s| s.siblings.include?(e) }
|
|
490
|
+
s.merge(e)
|
|
491
|
+
else parent_exceptions << e
|
|
492
|
+
end
|
|
493
|
+
|
|
494
|
+
has_parent = true
|
|
495
|
+
end
|
|
496
|
+
|
|
497
|
+
# Add unhandled exceptions to the fatal set. Merge siblings
|
|
498
|
+
# exceptions if possible
|
|
499
|
+
unless has_parent
|
|
500
|
+
if s = fatal.find { |s| s.siblings.include?(e) }
|
|
501
|
+
s.merge(e)
|
|
502
|
+
else fatal << e
|
|
503
|
+
end
|
|
504
|
+
end
|
|
505
|
+
|
|
506
|
+
by_task
|
|
507
|
+
end
|
|
508
|
+
|
|
509
|
+
parent_trees = by_task.map do |task, _|
|
|
510
|
+
[task, task.reverse_generated_subgraph(Roby::TaskStructure::Hierarchy)]
|
|
511
|
+
end
|
|
512
|
+
|
|
513
|
+
# Handle the exception in all tasks that are in no other parent trees
|
|
514
|
+
new_exceptions = ValueSet.new
|
|
515
|
+
by_task.each do |task, task_exceptions|
|
|
516
|
+
if parent_trees.find { |t, tree| t != task && tree.include?(task) }
|
|
517
|
+
task_exceptions.each { |e| new_exceptions << [e, [task]] }
|
|
518
|
+
next
|
|
519
|
+
end
|
|
520
|
+
|
|
521
|
+
task_exceptions.each do |e|
|
|
522
|
+
next if e.handled?
|
|
523
|
+
handled = task.handle_exception(e)
|
|
524
|
+
|
|
525
|
+
if handled
|
|
526
|
+
Roby::Control.handled_exception(e, task)
|
|
527
|
+
e.handled = true
|
|
528
|
+
else
|
|
529
|
+
# We do not have the framework to handle concurrent repairs
|
|
530
|
+
# For now, the first handler is the one ...
|
|
531
|
+
new_exceptions << e
|
|
532
|
+
e.trace << task
|
|
533
|
+
end
|
|
534
|
+
end
|
|
535
|
+
end
|
|
536
|
+
|
|
537
|
+
exceptions = new_exceptions
|
|
538
|
+
end
|
|
539
|
+
|
|
540
|
+
# Call global exception handlers for exceptions in +fatal+. Return the
|
|
541
|
+
# set of still unhandled exceptions
|
|
542
|
+
fatal.
|
|
543
|
+
find_all { |e| !e.handled? }.
|
|
544
|
+
reject { |e| Roby.handle_exception(e) }
|
|
545
|
+
end
|
|
546
|
+
end
|
|
547
|
+
|
|
548
|
+
module Roby
|
|
549
|
+
@exception_handlers = Array.new
|
|
550
|
+
class << self
|
|
551
|
+
attr_reader :exception_handlers
|
|
552
|
+
def each_exception_handler(&iterator); exception_handlers.each(&iterator) end
|
|
553
|
+
# define_method(:each_exception_handler, &Roby::Propagation.exception_handlers.method(:each))
|
|
554
|
+
def on_exception(*matchers, &handler)
|
|
555
|
+
check_arity(handler, 2)
|
|
556
|
+
exception_handlers.unshift [matchers, handler]
|
|
557
|
+
end
|
|
558
|
+
include ExceptionHandlingObject
|
|
559
|
+
end
|
|
560
|
+
end
|
|
561
|
+
|
|
562
|
+
|