roby 0.7
Sign up to get free protection for your applications and to get access to all the features.
- 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,70 @@
|
|
1
|
+
|
2
|
+
module Roby
|
3
|
+
module TaskStructure
|
4
|
+
Roby::Task.inherited_enumerable(:conflicting_model, :conflicting_models) { ValueSet.new }
|
5
|
+
module ModelConflicts
|
6
|
+
def conflicts_with(model)
|
7
|
+
conflicting_models << model
|
8
|
+
model.conflicting_models << self
|
9
|
+
end
|
10
|
+
|
11
|
+
def conflicts_with?(model)
|
12
|
+
each_conflicting_model do |m|
|
13
|
+
return true if m == model
|
14
|
+
end
|
15
|
+
false
|
16
|
+
end
|
17
|
+
end
|
18
|
+
Roby::Task.extend ModelConflicts
|
19
|
+
|
20
|
+
relation :Conflicts, :noinfo => true do
|
21
|
+
def conflicts_with(task)
|
22
|
+
task.event(:stop).add_precedence event(:start)
|
23
|
+
add_conflicts(task)
|
24
|
+
end
|
25
|
+
|
26
|
+
def self.included(klass) # :nodoc:
|
27
|
+
klass.extend ModelConflicts
|
28
|
+
super
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
module ConflictEventHandling
|
34
|
+
def calling(context)
|
35
|
+
super if defined? super
|
36
|
+
return unless symbol == :start
|
37
|
+
|
38
|
+
# Check for conflicting tasks
|
39
|
+
result = nil
|
40
|
+
task.each_conflicts do |conflicting_task|
|
41
|
+
result ||= ValueSet.new
|
42
|
+
result << conflicting_task
|
43
|
+
end
|
44
|
+
|
45
|
+
if result
|
46
|
+
Roby.decision_control.conflict(task, result)
|
47
|
+
end
|
48
|
+
|
49
|
+
# Add the needed conflict relations
|
50
|
+
models = task.class.conflicting_models
|
51
|
+
for model in models
|
52
|
+
if candidates = plan.task_index.by_model[model]
|
53
|
+
for t in candidates
|
54
|
+
t.conflicts_with task if t.pending?
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
def fired(event)
|
61
|
+
super if defined? super
|
62
|
+
|
63
|
+
if symbol == :stop
|
64
|
+
TaskStructure::Conflicts.remove(task)
|
65
|
+
end
|
66
|
+
end
|
67
|
+
end
|
68
|
+
Roby::TaskEventGenerator.include ConflictEventHandling
|
69
|
+
end
|
70
|
+
|
@@ -0,0 +1,20 @@
|
|
1
|
+
require 'roby/event'
|
2
|
+
module Roby::EventStructure
|
3
|
+
relation :EnsuredEvent, :noinfo => true do
|
4
|
+
def calling(context)
|
5
|
+
super if defined? super
|
6
|
+
each_ensured_event do |ev|
|
7
|
+
if !ev.happened?
|
8
|
+
postpone(ev, "waiting for ensured event #{ev}") do
|
9
|
+
ev.call(context) if ev.controlable?
|
10
|
+
end
|
11
|
+
end
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
def ensure(event)
|
16
|
+
add_ensured_event event
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
@@ -0,0 +1,23 @@
|
|
1
|
+
|
2
|
+
module Roby::TaskStructure
|
3
|
+
class Roby::TaskEventGenerator
|
4
|
+
# Mark this event as being handled by the task +task+
|
5
|
+
def handle_with(repairing_task)
|
6
|
+
if !task.child_object?(repairing_task, ErrorHandling)
|
7
|
+
task.add_error_handler repairing_task, ValueSet.new
|
8
|
+
end
|
9
|
+
|
10
|
+
task[repairing_task, ErrorHandling] << symbol
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
relation :ErrorHandling, :child_name => :error_handler do
|
15
|
+
def failed_task
|
16
|
+
each_parent_object(ErrorHandling) do |task|
|
17
|
+
return task
|
18
|
+
end
|
19
|
+
nil
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
@@ -0,0 +1,9 @@
|
|
1
|
+
require 'roby/event'
|
2
|
+
|
3
|
+
module Roby::EventStructure
|
4
|
+
relation :Signal, :noinfo => true
|
5
|
+
relation :Forwarding, :noinfo => true
|
6
|
+
relation :CausalLink, :subsets => [Signal, Forwarding], :noinfo => true
|
7
|
+
relation :Precedence, :subsets => [CausalLink], :noinfo => true
|
8
|
+
end
|
9
|
+
|
@@ -0,0 +1,193 @@
|
|
1
|
+
require 'roby/task'
|
2
|
+
|
3
|
+
module Roby::TaskStructure
|
4
|
+
# This module defines model-level definition of execution agent, for
|
5
|
+
# instance to Roby::Task
|
6
|
+
module ModelLevelExecutionAgent
|
7
|
+
# The model of execution agent for this class
|
8
|
+
def execution_agent
|
9
|
+
for klass in ancestors
|
10
|
+
if klass.instance_variable_defined?(:@execution_agent)
|
11
|
+
return klass.instance_variable_get(:@execution_agent)
|
12
|
+
end
|
13
|
+
end
|
14
|
+
nil
|
15
|
+
end
|
16
|
+
|
17
|
+
# Defines a model of execution agent. Doing
|
18
|
+
#
|
19
|
+
# TaskModel.executed_by ExecutionAgentModel
|
20
|
+
#
|
21
|
+
# is equivalent to
|
22
|
+
#
|
23
|
+
# task = TaskModel.new
|
24
|
+
# exec = <find a suitable ExecutionAgentModel instance in the plan or
|
25
|
+
# create a new one>
|
26
|
+
# task.executed_by exec
|
27
|
+
#
|
28
|
+
# for all instances of TaskModel. The actual job is done in the
|
29
|
+
# ExecutionAgentSpawn module
|
30
|
+
def executed_by(agent)
|
31
|
+
@execution_agent = agent
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
# The execution_agent defines an agent (process or otherwise) a given
|
36
|
+
# task is executed by. It allows to define a class of these execution agent,
|
37
|
+
# so that the specific agents are managed externally (load-balancing, ...)
|
38
|
+
relation :ExecutionAgent, :parent_name => :executed_task, :child_name => :execution_agent,
|
39
|
+
:noinfo => true, :distribute => false, :single_child => true do
|
40
|
+
|
41
|
+
# When ExecutionAgent support is included in a model (for instance Roby::Task), add
|
42
|
+
# the model-level classes
|
43
|
+
def self.included(klass) # :nodoc:
|
44
|
+
klass.extend Roby::TaskStructure::ModelLevelExecutionAgent
|
45
|
+
super
|
46
|
+
end
|
47
|
+
|
48
|
+
# Defines a new execution agent for this task.
|
49
|
+
def executed_by(agent)
|
50
|
+
return if execution_agent == agent
|
51
|
+
if !agent.event(:start).controlable? && !agent.running?
|
52
|
+
raise ArgumentError, "the start event of #{self}'s execution agent #{agent} is not controlable"
|
53
|
+
end
|
54
|
+
# Check that agent defines the :ready event
|
55
|
+
if !agent.has_event?(:ready)
|
56
|
+
raise ArgumentError, "execution agent tasks should define the :ready event"
|
57
|
+
end
|
58
|
+
|
59
|
+
old_agent = execution_agent
|
60
|
+
if old_agent && old_agent != agent
|
61
|
+
Roby.debug "an agent is already defined for this task"
|
62
|
+
remove_execution_agent old_agent
|
63
|
+
end
|
64
|
+
|
65
|
+
unless old_agent
|
66
|
+
# If the task did have an agent already, these event handlers
|
67
|
+
# are already set up
|
68
|
+
if running?
|
69
|
+
Roby::Distributed.update(self) do
|
70
|
+
agent.forward(:stop, self, :aborted)
|
71
|
+
end
|
72
|
+
else
|
73
|
+
on(:start) do
|
74
|
+
# The event handler will be called even if the
|
75
|
+
# execution agent has been removed. Check that there is
|
76
|
+
# actually an execution agent
|
77
|
+
if execution_agent
|
78
|
+
Roby::Distributed.update(self) do
|
79
|
+
execution_agent.forward(:stop, self, :aborted)
|
80
|
+
end
|
81
|
+
end
|
82
|
+
end
|
83
|
+
end
|
84
|
+
|
85
|
+
on(:stop) do
|
86
|
+
if execution_agent
|
87
|
+
Roby::Distributed.update(self) do
|
88
|
+
execution_agent.event(:stop).remove_forwarding event(:aborted)
|
89
|
+
remove_execution_agent execution_agent
|
90
|
+
end
|
91
|
+
end
|
92
|
+
end
|
93
|
+
end
|
94
|
+
|
95
|
+
add_execution_agent(agent)
|
96
|
+
end
|
97
|
+
|
98
|
+
end
|
99
|
+
|
100
|
+
class ExecutionAgentSpawningFailed < Roby::LocalizedError
|
101
|
+
attr_reader :agent_model, :error
|
102
|
+
def initialize(task, agent_model, error)
|
103
|
+
super(task)
|
104
|
+
@agent_model, @error = agent_model, error
|
105
|
+
end
|
106
|
+
end
|
107
|
+
|
108
|
+
# Add a suitable execution agent to +task+ if its model has a execution
|
109
|
+
# agent model (see ModelLevelExecutionAgent), either by reusing one
|
110
|
+
# that is already in the plan, or by creating a new one.
|
111
|
+
def ExecutionAgent.spawn(task)
|
112
|
+
agent_model = task.model.execution_agent
|
113
|
+
candidates = task.plan.find_tasks.
|
114
|
+
with_model(agent_model).
|
115
|
+
self_owned.
|
116
|
+
not_finished
|
117
|
+
|
118
|
+
agent = nil
|
119
|
+
|
120
|
+
if candidates.empty?
|
121
|
+
begin
|
122
|
+
agent = agent_model.new
|
123
|
+
agent.on(:stop) do
|
124
|
+
agent.each_executed_task do |task|
|
125
|
+
if task.running?
|
126
|
+
task.emit(:aborted, "execution agent #{self} failed")
|
127
|
+
elsif task.pending?
|
128
|
+
task.remove_execution_agent agent
|
129
|
+
spawn(task)
|
130
|
+
end
|
131
|
+
end
|
132
|
+
end
|
133
|
+
rescue Exception => e
|
134
|
+
Roby::Propagation.add_error(ExecutionAgentSpawningFailed.new(task, agent_model, e))
|
135
|
+
end
|
136
|
+
else
|
137
|
+
running, pending = candidates.partition { |t| t.running? }
|
138
|
+
agent = if running.empty? then pending.first
|
139
|
+
else running.first
|
140
|
+
end
|
141
|
+
end
|
142
|
+
task.executed_by agent
|
143
|
+
agent
|
144
|
+
end
|
145
|
+
|
146
|
+
# This module is hooked in Roby::TaskEventGenerator to check that a task
|
147
|
+
# which is being started has a suitable execution agent, and to start it if
|
148
|
+
# it's not the case
|
149
|
+
module ExecutionAgentStart
|
150
|
+
def calling(context)
|
151
|
+
super if defined? super
|
152
|
+
return unless symbol == :start
|
153
|
+
return unless agent = task.execution_agent
|
154
|
+
|
155
|
+
if agent.finished? || agent.finishing?
|
156
|
+
raise CommandFailed.new(nil, self), "task #{task} has an execution agent but it is dead"
|
157
|
+
elsif !agent.event(:ready).happened? && !agent.depends_on?(task)
|
158
|
+
postpone(agent.event(:ready), "spawning execution agent #{agent} for #{self}") do
|
159
|
+
if agent.pending?
|
160
|
+
agent.event(:ready).if_unreachable(true) do |reason|
|
161
|
+
self.emit_failed "execution agent #{agent} failed to initialize: #{reason}"
|
162
|
+
end
|
163
|
+
agent.start!
|
164
|
+
end
|
165
|
+
end
|
166
|
+
end
|
167
|
+
end
|
168
|
+
end
|
169
|
+
Roby::TaskEventGenerator.include ExecutionAgentStart
|
170
|
+
|
171
|
+
# This module is included in Roby::Plan to automatically add execution agents
|
172
|
+
# to tasks that require it and are discovered in the executable plan.
|
173
|
+
module ExecutionAgentSpawn
|
174
|
+
# Hook into plan discovery to add execution agents to new tasks.
|
175
|
+
# See ExecutionAgentSpawn.spawn
|
176
|
+
def discovered_tasks(tasks)
|
177
|
+
# For now, settle on adding the execution agents only in the
|
178
|
+
# main plan. Otherwise, it is possible that two transactions
|
179
|
+
# will try to add two different agents
|
180
|
+
#
|
181
|
+
# Note that it would be solved by plan merging ...
|
182
|
+
return unless executable?
|
183
|
+
|
184
|
+
for task in tasks
|
185
|
+
if !task.execution_agent && task.model.execution_agent && task.self_owned?
|
186
|
+
ExecutionAgent.spawn(task)
|
187
|
+
end
|
188
|
+
end
|
189
|
+
end
|
190
|
+
end
|
191
|
+
Roby::Plan.include ExecutionAgentSpawn
|
192
|
+
end
|
193
|
+
|
@@ -0,0 +1,239 @@
|
|
1
|
+
require 'roby/task'
|
2
|
+
require 'roby/control'
|
3
|
+
require 'set'
|
4
|
+
|
5
|
+
module Roby::TaskStructure
|
6
|
+
# Document-module: Hierarchy
|
7
|
+
relation :Hierarchy, :child_name => :child, :parent_name => :parent_task do
|
8
|
+
# True if +obj+ is a parent of this object in the hierarchy relation
|
9
|
+
# (+obj+ is realized by +self+)
|
10
|
+
def realizes?(obj); parent_object?(obj, Hierarchy) end
|
11
|
+
# True if +obj+ is a child of this object in the hierarchy relation
|
12
|
+
def realized_by?(obj); child_object?(obj, Hierarchy) end
|
13
|
+
# True if +obj+ can be reached through the Hierarchy relation by
|
14
|
+
# starting from this object
|
15
|
+
def depends_on?(obj)
|
16
|
+
generated_subgraph(Hierarchy).include?(obj)
|
17
|
+
end
|
18
|
+
# The set of parent objects in the Hierarchy relation
|
19
|
+
def parents; parent_objects(Hierarchy) end
|
20
|
+
# The set of child objects in the Hierarchy relation
|
21
|
+
def children; child_objects(Hierarchy) end
|
22
|
+
|
23
|
+
|
24
|
+
# Adds +task+ as a child of +self+ in the Hierarchy relation. The
|
25
|
+
# following options are allowed:
|
26
|
+
#
|
27
|
+
# success:: the list of success events. The default is [:success]
|
28
|
+
# failure:: the list of failing events. The default is [:failed]
|
29
|
+
# model:: a <tt>[task_model, arguments]</tt> pair which defines the task model the parent is expecting.
|
30
|
+
# The default value is to get these parameters from +task+
|
31
|
+
#
|
32
|
+
# The +success+ set describes the events of the child task that are
|
33
|
+
# _required_ by the parent task. More specifically, the child task
|
34
|
+
# remains useful for the parent task as long as none of these events are
|
35
|
+
# emitted. By default, it is the +success+ event. Of course, an error
|
36
|
+
# condition is encountered when all events of +success+ become
|
37
|
+
# unreachable. In addition, the relation is removed if the
|
38
|
+
# +remove_when_done+ flag is set to true (false by default).
|
39
|
+
#
|
40
|
+
# The +failure+ set describes the events of the child task which are an
|
41
|
+
# error condition from the parent task point of view.
|
42
|
+
#
|
43
|
+
# In both error cases, a +ChildFailedError+ exception is raised.
|
44
|
+
def realized_by(task, options = {})
|
45
|
+
options = validate_options options,
|
46
|
+
:model => [task.model, task.meaningful_arguments],
|
47
|
+
:success => [:success],
|
48
|
+
:failure => [:failed],
|
49
|
+
:remove_when_done => false
|
50
|
+
|
51
|
+
options[:success] = Array[*options[:success]]
|
52
|
+
options[:failure] = Array[*options[:failure]]
|
53
|
+
|
54
|
+
# Validate failure and success event names
|
55
|
+
options[:success].each { |ev| task.event(ev) }
|
56
|
+
options[:failure].each { |ev| task.event(ev) }
|
57
|
+
|
58
|
+
options[:model] = [options[:model], {}] unless Array === options[:model]
|
59
|
+
required_model, required_args = *options[:model]
|
60
|
+
if !task.fullfills?(required_model, required_args)
|
61
|
+
raise ArgumentError, "task #{task} does not fullfills the provided model #{options[:model]}"
|
62
|
+
end
|
63
|
+
|
64
|
+
add_child(task, options)
|
65
|
+
self
|
66
|
+
end
|
67
|
+
|
68
|
+
# Set up the event gathering needed by Hierarchy.check_structure
|
69
|
+
def added_child_object(child, relations, info) # :nodoc:
|
70
|
+
super if defined? super
|
71
|
+
if relations.include?(Hierarchy) && !respond_to?(:__getobj__) && !child.respond_to?(:__getobj__)
|
72
|
+
events = info[:success].map { |ev| child.event(ev) }
|
73
|
+
events.concat info[:failure].map { |ev| child.event(ev) }
|
74
|
+
Roby::EventGenerator.gather_events(Hierarchy.interesting_events, events)
|
75
|
+
end
|
76
|
+
end
|
77
|
+
|
78
|
+
# Return the set of this task children for which the :start event has
|
79
|
+
# no parent in CausalLinks
|
80
|
+
def first_children
|
81
|
+
result = ValueSet.new
|
82
|
+
|
83
|
+
generated_subgraph(Hierarchy).each do |task|
|
84
|
+
next if task == self
|
85
|
+
if task.event(:start).root?(Roby::EventStructure::CausalLink)
|
86
|
+
result << task
|
87
|
+
end
|
88
|
+
end
|
89
|
+
result
|
90
|
+
end
|
91
|
+
|
92
|
+
# The set of events that are needed by the parent tasks
|
93
|
+
def fullfilled_events
|
94
|
+
needed = ValueSet.new
|
95
|
+
each_parent_task do |parent|
|
96
|
+
needed.merge(parent[self, Hierarchy][:success])
|
97
|
+
end
|
98
|
+
needed
|
99
|
+
end
|
100
|
+
|
101
|
+
# Return [tags, arguments] where +tags+ is a list of task models which
|
102
|
+
# are required by the parent tasks of this task, and arguments the
|
103
|
+
# required arguments
|
104
|
+
#
|
105
|
+
# If there is a task class in the required models, it is always the
|
106
|
+
# first element of +tags+
|
107
|
+
def fullfilled_model
|
108
|
+
model, tags, arguments = Roby::Task, [], {}
|
109
|
+
|
110
|
+
each_parent_task do |parent|
|
111
|
+
m, a = parent[self, Hierarchy][:model]
|
112
|
+
if m.instance_of?(Roby::TaskModelTag)
|
113
|
+
tags << m
|
114
|
+
elsif m.has_ancestor?(model)
|
115
|
+
model = m
|
116
|
+
elsif !model.has_ancestor?(m)
|
117
|
+
raise "inconsistency in fullfilled models: #{model} and #{m} are incompatible"
|
118
|
+
end
|
119
|
+
a.merge!(arguments) do |old, new|
|
120
|
+
if old != new
|
121
|
+
raise "inconsistency in fullfilled models: #{old} and #{new}"
|
122
|
+
end
|
123
|
+
end
|
124
|
+
end
|
125
|
+
|
126
|
+
tags.unshift(model)
|
127
|
+
[tags, arguments]
|
128
|
+
end
|
129
|
+
|
130
|
+
# Remove all children that have successfully finished
|
131
|
+
def remove_finished_children
|
132
|
+
# We call #to_a to get a copy of children, since we will remove
|
133
|
+
# children in the block. Note that we can't use #delete_if here
|
134
|
+
# since #children is a relation enumerator (not the relation list
|
135
|
+
# itself)
|
136
|
+
children.to_a.each do |child|
|
137
|
+
success_events = self[child, Hierarchy][:success]
|
138
|
+
if success_events.any? { |ev| child.event(ev).happened? }
|
139
|
+
remove_child(child)
|
140
|
+
end
|
141
|
+
end
|
142
|
+
end
|
143
|
+
end
|
144
|
+
|
145
|
+
# Checks the structure of +plan+ w.r.t. the constraints of the hierarchy
|
146
|
+
# relations. It returns an array of ChildFailedError for all failed
|
147
|
+
# hierarchy relations
|
148
|
+
def Hierarchy.check_structure(plan)
|
149
|
+
result = []
|
150
|
+
|
151
|
+
events = Hierarchy.interesting_events
|
152
|
+
return result if events.empty? && failing_tasks.empty?
|
153
|
+
|
154
|
+
# Get the set of tasks for which a possible failure has been
|
155
|
+
# registered The tasks that are failing the hierarchy requirements
|
156
|
+
# are registered in Hierarchy.failing_tasks. The interesting_events
|
157
|
+
# set is cleared at cycle end (see below)
|
158
|
+
tasks = events.inject(failing_tasks) { |set, event| set << event.generator.task }
|
159
|
+
@failing_tasks = ValueSet.new
|
160
|
+
tasks.each do |child|
|
161
|
+
# Check if the task has been removed from the plan
|
162
|
+
next unless child.plan
|
163
|
+
|
164
|
+
has_error = false
|
165
|
+
child.each_parent_task do |parent|
|
166
|
+
next unless parent.self_owned?
|
167
|
+
next if parent.finished? || parent.finishing?
|
168
|
+
|
169
|
+
options = parent[child, Hierarchy]
|
170
|
+
success = options[:success]
|
171
|
+
failure = options[:failure]
|
172
|
+
|
173
|
+
if success.any? { |e| child.event(e).happened? }
|
174
|
+
if options[:remove_when_done]
|
175
|
+
parent.remove_child child
|
176
|
+
end
|
177
|
+
elsif failing_event = failure.find { |e| child.event(e).happened? }
|
178
|
+
result << Roby::ChildFailedError.new(parent, child.event(failing_event).last)
|
179
|
+
failing_tasks << child
|
180
|
+
end
|
181
|
+
end
|
182
|
+
end
|
183
|
+
|
184
|
+
events.clear
|
185
|
+
result
|
186
|
+
end
|
187
|
+
|
188
|
+
class << Hierarchy
|
189
|
+
# The set of events that have been fired in this cycle and are involved in a Hierarchy relation
|
190
|
+
attribute(:interesting_events) { Array.new }
|
191
|
+
|
192
|
+
# The set of tasks that are currently failing
|
193
|
+
attribute(:failing_tasks) { ValueSet.new }
|
194
|
+
end
|
195
|
+
end
|
196
|
+
|
197
|
+
module Roby
|
198
|
+
# This exception is raised when a {hierarchy relation}[classes/Roby/TaskStructure/Hierarchy.html] fails
|
199
|
+
class ChildFailedError < LocalizedError
|
200
|
+
# The parent in the relation
|
201
|
+
attr_reader :parent
|
202
|
+
# The child in the relation
|
203
|
+
def child; failed_task end
|
204
|
+
# The relation parameters (i.e. the hash given to #realized_by)
|
205
|
+
attr_reader :relation
|
206
|
+
|
207
|
+
# The event which is the cause of this error. This is either the task
|
208
|
+
# source of a failure event, or the reason why a positive event has
|
209
|
+
# become unreachable (if there is one)
|
210
|
+
def initialize(parent, event)
|
211
|
+
super(event.task_sources.find { true })
|
212
|
+
@parent = parent
|
213
|
+
@relation = parent[child, TaskStructure::Hierarchy]
|
214
|
+
end
|
215
|
+
|
216
|
+
def pretty_print(pp) # :nodoc:
|
217
|
+
super
|
218
|
+
pp.breakable
|
219
|
+
pp.breakable
|
220
|
+
pp.text "The failed relation is"
|
221
|
+
pp.breakable
|
222
|
+
pp.nest(2) do
|
223
|
+
pp.text " "
|
224
|
+
parent.pretty_print pp
|
225
|
+
pp.breakable
|
226
|
+
pp.text "realized_by "
|
227
|
+
child.pretty_print pp
|
228
|
+
end
|
229
|
+
end
|
230
|
+
def backtrace; [] end
|
231
|
+
|
232
|
+
# True if +obj+ is involved in this exception
|
233
|
+
def involved_plan_object?(obj)
|
234
|
+
super || obj == parent
|
235
|
+
end
|
236
|
+
end
|
237
|
+
Control.structure_checks << TaskStructure::Hierarchy.method(:check_structure)
|
238
|
+
end
|
239
|
+
|