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,160 @@
|
|
|
1
|
+
module Roby
|
|
2
|
+
module Test
|
|
3
|
+
class << self
|
|
4
|
+
def sampling(duration, period, *fields)
|
|
5
|
+
Test.info "starting sampling #{fields.join(", ")} every #{period}s for #{duration}s"
|
|
6
|
+
|
|
7
|
+
samples = Array.new
|
|
8
|
+
fields.map! { |n| n.to_sym }
|
|
9
|
+
if fields.include?(:dt)
|
|
10
|
+
raise ArgumentError, "dt is reserved by #sampling"
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
if compute_time = !fields.include?(:t)
|
|
14
|
+
fields << :t
|
|
15
|
+
end
|
|
16
|
+
fields << :dt
|
|
17
|
+
|
|
18
|
+
sample_type = Struct.new(*fields)
|
|
19
|
+
|
|
20
|
+
start = Time.now
|
|
21
|
+
Roby.condition_variable(true) do |cv, mt|
|
|
22
|
+
first_sample = nil
|
|
23
|
+
mt.synchronize do
|
|
24
|
+
id = Roby::Control.every(period) do
|
|
25
|
+
result = yield
|
|
26
|
+
if result
|
|
27
|
+
if compute_time
|
|
28
|
+
result << Roby.control.cycle_start
|
|
29
|
+
end
|
|
30
|
+
new_sample = sample_type.new(*result)
|
|
31
|
+
|
|
32
|
+
unless samples.empty?
|
|
33
|
+
new_sample.dt = new_sample.t- samples.last.t
|
|
34
|
+
end
|
|
35
|
+
samples << new_sample
|
|
36
|
+
|
|
37
|
+
if samples.last.t - samples.first.t > duration
|
|
38
|
+
mt.synchronize do
|
|
39
|
+
cv.broadcast
|
|
40
|
+
end
|
|
41
|
+
end
|
|
42
|
+
end
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
cv.wait(mt)
|
|
46
|
+
Roby::Control.remove_periodic_handler(id)
|
|
47
|
+
end
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
samples
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
Stat = Struct.new :total, :count, :mean, :stddev, :min, :max
|
|
54
|
+
|
|
55
|
+
# Computes mean and standard deviation about the samples in
|
|
56
|
+
# +samples+ +spec+ describes what to compute:
|
|
57
|
+
# * if nothing is specified, we compute the statistics on
|
|
58
|
+
# v(i - 1) - v(i)
|
|
59
|
+
# * if spec['fieldname'] is 'rate', we compute the statistics on
|
|
60
|
+
# (v(i - 1) - v(i)) / (t(i - 1) / t(i))
|
|
61
|
+
# * if spec['fieldname'] is 'absolute', we compute the
|
|
62
|
+
# statistics on
|
|
63
|
+
# v(i)
|
|
64
|
+
# * if spec['fieldname'] is 'absolute_rate', we compute the
|
|
65
|
+
# statistics on
|
|
66
|
+
# v(i) / (t(i - 1) / t(i))
|
|
67
|
+
#
|
|
68
|
+
# The returned value is a struct with the same fields than the
|
|
69
|
+
# samples. Each element is a Stats object
|
|
70
|
+
def stats(samples, spec)
|
|
71
|
+
return if samples.empty?
|
|
72
|
+
type = samples.first.class
|
|
73
|
+
spec = spec.inject(Hash.new) do |h, (k, v)|
|
|
74
|
+
spec[k.to_sym] = v.to_sym
|
|
75
|
+
spec
|
|
76
|
+
end
|
|
77
|
+
spec[:t] = :exclude
|
|
78
|
+
spec[:dt] = :absolute
|
|
79
|
+
|
|
80
|
+
# Initialize the result value
|
|
81
|
+
fields = type.members.
|
|
82
|
+
find_all { |n| spec[n.to_sym] != :exclude }.
|
|
83
|
+
map { |n| n.to_sym }
|
|
84
|
+
result = Struct.new(*fields).new
|
|
85
|
+
fields.each do |name|
|
|
86
|
+
result[name] = Stat.new(0, 0, 0, 0, nil, nil)
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
# Compute the deltas if the mode is not absolute
|
|
90
|
+
last_sample = nil
|
|
91
|
+
samples = samples.map do |original_sample|
|
|
92
|
+
sample = original_sample.dup
|
|
93
|
+
fields.each do |name|
|
|
94
|
+
next unless value = sample[name]
|
|
95
|
+
unless spec[name] == :absolute || spec[name] == :absolute_rate
|
|
96
|
+
if last_sample && last_sample[name]
|
|
97
|
+
sample[name] -= last_sample[name]
|
|
98
|
+
else
|
|
99
|
+
sample[name] = nil
|
|
100
|
+
next
|
|
101
|
+
end
|
|
102
|
+
end
|
|
103
|
+
end
|
|
104
|
+
last_sample = original_sample
|
|
105
|
+
sample
|
|
106
|
+
end
|
|
107
|
+
|
|
108
|
+
# Compute the rates if needed
|
|
109
|
+
samples = samples.map do |sample|
|
|
110
|
+
fields.each do |name|
|
|
111
|
+
next unless value = sample[name]
|
|
112
|
+
if spec[name] == :rate || spec[name] == :absolute_rate
|
|
113
|
+
if sample.dt
|
|
114
|
+
sample[name] = value / sample.dt
|
|
115
|
+
else
|
|
116
|
+
sample[name] = nil
|
|
117
|
+
next
|
|
118
|
+
end
|
|
119
|
+
end
|
|
120
|
+
end
|
|
121
|
+
sample
|
|
122
|
+
end
|
|
123
|
+
|
|
124
|
+
samples.each do |sample|
|
|
125
|
+
fields.each do |name|
|
|
126
|
+
next unless value = sample[name]
|
|
127
|
+
if !result[name].max || value > result[name].max
|
|
128
|
+
result[name].max = value
|
|
129
|
+
end
|
|
130
|
+
if !result[name].min || value < result[name].min
|
|
131
|
+
result[name].min = value
|
|
132
|
+
end
|
|
133
|
+
|
|
134
|
+
result[name].total += value
|
|
135
|
+
result[name].count += 1
|
|
136
|
+
end
|
|
137
|
+
last_sample = sample
|
|
138
|
+
end
|
|
139
|
+
|
|
140
|
+
result.each do |r|
|
|
141
|
+
r.mean = Float(r.total) / r.count
|
|
142
|
+
end
|
|
143
|
+
|
|
144
|
+
samples.each do |sample|
|
|
145
|
+
fields.each do |name|
|
|
146
|
+
next unless value = sample[name]
|
|
147
|
+
result[name].stddev += (value - result[name].mean) ** 2
|
|
148
|
+
end
|
|
149
|
+
end
|
|
150
|
+
|
|
151
|
+
result.each do |r|
|
|
152
|
+
r.stddev = Math.sqrt(r.stddev / r.count)
|
|
153
|
+
end
|
|
154
|
+
|
|
155
|
+
result
|
|
156
|
+
end
|
|
157
|
+
end
|
|
158
|
+
end
|
|
159
|
+
end
|
|
160
|
+
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
module Roby
|
|
2
|
+
# This task represents a separate thread in the plan. At definition, the
|
|
3
|
+
# thread's implementation is defined using the +implementation+ statement:
|
|
4
|
+
#
|
|
5
|
+
# class MyThread < ThreadTask
|
|
6
|
+
# implementation do
|
|
7
|
+
# do_some_stuff
|
|
8
|
+
# end
|
|
9
|
+
# end
|
|
10
|
+
#
|
|
11
|
+
# The task will emit +failed+ if the given block raises an exception,
|
|
12
|
+
# and emit +success+ otherwise. In that latter case, the returned value
|
|
13
|
+
# is saved in the +result+ attribute.
|
|
14
|
+
#
|
|
15
|
+
# By default, the task is not interruptible (i.e. +stop+ is not
|
|
16
|
+
# controllable). The +interruptible+ statement allows to change that, in
|
|
17
|
+
# which case, the thread must call #interruption_point explicitely when the
|
|
18
|
+
# interruption can be safely performed by raising an exception.
|
|
19
|
+
class ThreadTask < Roby::Task
|
|
20
|
+
# The thread object. Only valid when the task is running
|
|
21
|
+
attr_reader :thread
|
|
22
|
+
# The thread result if the execution was successful
|
|
23
|
+
attr_reader :result
|
|
24
|
+
|
|
25
|
+
class << self
|
|
26
|
+
# The implementation block for that task model
|
|
27
|
+
attr_reader :implementation_block
|
|
28
|
+
|
|
29
|
+
# Defines the block which should be executed in the separate
|
|
30
|
+
# thread. The currently defined block can be accessed
|
|
31
|
+
# through the implementation_block attribute.
|
|
32
|
+
def implementation(&block)
|
|
33
|
+
@implementation_block = block
|
|
34
|
+
end
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
# True if an interruption has been requested
|
|
38
|
+
attr_predicate :interruption_requested?, true
|
|
39
|
+
|
|
40
|
+
# Call that method in the interruption thread at points where an
|
|
41
|
+
# interruption is safe. It will raise Interrupt if an interruption has
|
|
42
|
+
# been requested through the task's events.
|
|
43
|
+
def interruption_point
|
|
44
|
+
if interruption_requested?
|
|
45
|
+
raise Interrupt, "interruption requested"
|
|
46
|
+
end
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
event :start do |context|
|
|
50
|
+
emit :start
|
|
51
|
+
@thread = Thread.new do
|
|
52
|
+
Thread.current.priority = 0
|
|
53
|
+
instance_eval(&self.class.implementation_block)
|
|
54
|
+
end
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
poll do
|
|
58
|
+
if thread.alive?
|
|
59
|
+
return
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
begin
|
|
63
|
+
result = thread.value
|
|
64
|
+
rescue Exception => e
|
|
65
|
+
error = e
|
|
66
|
+
end
|
|
67
|
+
@thread = nil
|
|
68
|
+
|
|
69
|
+
if error
|
|
70
|
+
emit :failed, error
|
|
71
|
+
else
|
|
72
|
+
@result = result
|
|
73
|
+
emit :success
|
|
74
|
+
end
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
# Call this method in the model definition to declare that the thread
|
|
78
|
+
# implementation will call #interruption_point regularly.
|
|
79
|
+
def self.interruptible
|
|
80
|
+
event :failed, :terminal => true do |context|
|
|
81
|
+
self.interruption_requested = true
|
|
82
|
+
end
|
|
83
|
+
super
|
|
84
|
+
end
|
|
85
|
+
end
|
|
86
|
+
end
|
|
87
|
+
|
|
@@ -0,0 +1,462 @@
|
|
|
1
|
+
require 'roby/plan'
|
|
2
|
+
require 'utilrb/value_set'
|
|
3
|
+
require 'utilrb/kernel/swap'
|
|
4
|
+
require 'roby/transactions/proxy'
|
|
5
|
+
|
|
6
|
+
module Roby
|
|
7
|
+
# Exception raised when someone tries do commit an invalid transaction
|
|
8
|
+
class InvalidTransaction < RuntimeError; end
|
|
9
|
+
|
|
10
|
+
# A transaction is a special kind of plan. It allows to build plans in a separate
|
|
11
|
+
# sandbox, and then to apply the modifications to the real plan (using #commit_transaction), or
|
|
12
|
+
# to discard all modifications (using #discard)
|
|
13
|
+
class Transaction < Plan
|
|
14
|
+
Proxy = Transactions::Proxy
|
|
15
|
+
|
|
16
|
+
# A transaction is not an executable plan
|
|
17
|
+
def executable?; false end
|
|
18
|
+
def freezed?; @freezed end
|
|
19
|
+
|
|
20
|
+
def do_wrap(object, do_include = false) # :nodoc:
|
|
21
|
+
raise "transaction #{self} has been either committed or discarded. No modification allowed" if freezed?
|
|
22
|
+
|
|
23
|
+
proxy = proxy_objects[object] = Proxy.proxy_class(object).new(object, self)
|
|
24
|
+
if do_include && object.root_object?
|
|
25
|
+
proxy.plan = self
|
|
26
|
+
discover(proxy)
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
copy_object_relations(object, proxy)
|
|
30
|
+
proxy
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
def propose; end
|
|
34
|
+
def edit
|
|
35
|
+
yield if block_given?
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
# This method copies on +proxy+ all relations of +object+ for which
|
|
39
|
+
# both ends of the relation are already in the transaction.
|
|
40
|
+
def copy_object_relations(object, proxy)
|
|
41
|
+
Roby::Control.synchronize do
|
|
42
|
+
# Create edges between the neighbours that are really in the transaction
|
|
43
|
+
object.each_relation do |rel|
|
|
44
|
+
object.each_parent_object(rel) do |parent|
|
|
45
|
+
if parent_proxy = self[parent, false]
|
|
46
|
+
parent_proxy.add_child_object(proxy, rel, parent[object, rel])
|
|
47
|
+
end
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
object.each_child_object(rel) do |child|
|
|
51
|
+
if child_proxy = self[child, false]
|
|
52
|
+
proxy.add_child_object(child_proxy, rel, object[child, rel])
|
|
53
|
+
end
|
|
54
|
+
end
|
|
55
|
+
end
|
|
56
|
+
end
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
# Get the transaction proxy for +object+
|
|
60
|
+
def wrap(object, create = true)
|
|
61
|
+
if object.kind_of?(PlanObject)
|
|
62
|
+
if object.plan == self then return object
|
|
63
|
+
elsif proxy = proxy_objects[object] then return proxy
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
if create
|
|
67
|
+
if !object.plan
|
|
68
|
+
object.plan = self
|
|
69
|
+
discover(object)
|
|
70
|
+
return object
|
|
71
|
+
elsif object.plan == self.plan
|
|
72
|
+
wrapped = do_wrap(object, true)
|
|
73
|
+
if plan.mission?(object)
|
|
74
|
+
insert(wrapped)
|
|
75
|
+
elsif plan.permanent?(object)
|
|
76
|
+
permanent(wrapped)
|
|
77
|
+
end
|
|
78
|
+
return wrapped
|
|
79
|
+
else
|
|
80
|
+
raise ArgumentError, "#{object} is in #{object.plan}, this transaction #{self} applies on #{self.plan}"
|
|
81
|
+
end
|
|
82
|
+
end
|
|
83
|
+
nil
|
|
84
|
+
elsif object.respond_to?(:each)
|
|
85
|
+
object.map { |o| wrap(o, create) }
|
|
86
|
+
else
|
|
87
|
+
raise TypeError, "don't know how to wrap #{object || 'nil'} of type #{object.class.ancestors}"
|
|
88
|
+
end
|
|
89
|
+
end
|
|
90
|
+
alias :[] :wrap
|
|
91
|
+
|
|
92
|
+
# Remove +proxy+ from this transaction. While #remove_object is also
|
|
93
|
+
# removing the object from the plan itself, this method only removes it
|
|
94
|
+
# from the transaction, forgetting all modifications that have been
|
|
95
|
+
# done on +object+ in the transaction
|
|
96
|
+
def discard_modifications(object)
|
|
97
|
+
object = may_unwrap(object)
|
|
98
|
+
if object.respond_to?(:each_plan_child)
|
|
99
|
+
object.each_plan_child do |child|
|
|
100
|
+
discard_modifications(child)
|
|
101
|
+
end
|
|
102
|
+
end
|
|
103
|
+
removed_objects.delete(object)
|
|
104
|
+
discarded_tasks.delete(object)
|
|
105
|
+
auto_tasks.delete(object)
|
|
106
|
+
|
|
107
|
+
return unless proxy = proxy_objects.delete(object)
|
|
108
|
+
proxy.clear_vertex
|
|
109
|
+
|
|
110
|
+
missions.delete(proxy)
|
|
111
|
+
known_tasks.delete(proxy)
|
|
112
|
+
free_events.delete(proxy)
|
|
113
|
+
end
|
|
114
|
+
|
|
115
|
+
def restore_relation(proxy, relation)
|
|
116
|
+
object = proxy.__getobj__
|
|
117
|
+
|
|
118
|
+
Control.synchronize do
|
|
119
|
+
proxy_children = proxy.child_objects(relation)
|
|
120
|
+
object.child_objects(relation).each do |object_child|
|
|
121
|
+
next unless proxy_child = wrap(object_child, false)
|
|
122
|
+
if proxy_children.include?(proxy_child)
|
|
123
|
+
relation.unlink(proxy, proxy_child)
|
|
124
|
+
end
|
|
125
|
+
end
|
|
126
|
+
|
|
127
|
+
proxy_parents = proxy.parent_objects(relation)
|
|
128
|
+
object.parent_objects(relation).each do |object_parent|
|
|
129
|
+
next unless proxy_parent = wrap(object_parent, false)
|
|
130
|
+
if proxy_parents.include?(proxy_parent)
|
|
131
|
+
relation.unlink(parent, proxy_parent)
|
|
132
|
+
end
|
|
133
|
+
end
|
|
134
|
+
end
|
|
135
|
+
|
|
136
|
+
discovered_objects.delete(proxy)
|
|
137
|
+
proxy.discovered_relations.delete(relation)
|
|
138
|
+
proxy.do_discover(relation, false)
|
|
139
|
+
end
|
|
140
|
+
|
|
141
|
+
alias :remove_plan_object :remove_object
|
|
142
|
+
def remove_object(object)
|
|
143
|
+
raise "transaction #{self} has been either committed or discarded. No modification allowed" if freezed?
|
|
144
|
+
|
|
145
|
+
object = may_unwrap(object)
|
|
146
|
+
proxy = proxy_objects[object] || object
|
|
147
|
+
|
|
148
|
+
# removing the proxy may trigger some discovery (event relations
|
|
149
|
+
# for instance, if proxy is a task). Do it first, or #discover
|
|
150
|
+
# will be called and the modifications of internal structures
|
|
151
|
+
# nulled (like #removed_objects) ...
|
|
152
|
+
remove_plan_object(proxy)
|
|
153
|
+
proxy_objects.delete(object)
|
|
154
|
+
|
|
155
|
+
if object.plan == self.plan
|
|
156
|
+
# +object+ is new in the transaction
|
|
157
|
+
removed_objects.insert(object)
|
|
158
|
+
end
|
|
159
|
+
end
|
|
160
|
+
|
|
161
|
+
def may_wrap(object, create = true)
|
|
162
|
+
(wrap(object, create) || object) rescue object
|
|
163
|
+
end
|
|
164
|
+
|
|
165
|
+
# may_unwrap may return objects from transaction
|
|
166
|
+
def may_unwrap(object)
|
|
167
|
+
if object.respond_to?(:plan)
|
|
168
|
+
if object.plan == self && object.respond_to?(:__getobj__)
|
|
169
|
+
object.__getobj__
|
|
170
|
+
elsif object.plan == self.plan
|
|
171
|
+
object
|
|
172
|
+
else
|
|
173
|
+
object
|
|
174
|
+
end
|
|
175
|
+
else object
|
|
176
|
+
end
|
|
177
|
+
end
|
|
178
|
+
|
|
179
|
+
# The list of discarded
|
|
180
|
+
attr_reader :discarded_tasks
|
|
181
|
+
# The list of removed tasks and events
|
|
182
|
+
attr_reader :removed_objects
|
|
183
|
+
# The list of permanent tasks that have been auto'ed
|
|
184
|
+
attr_reader :auto_tasks
|
|
185
|
+
# The plan this transaction applies on
|
|
186
|
+
attr_reader :plan
|
|
187
|
+
# The proxy objects built for this transaction
|
|
188
|
+
attr_reader :proxy_objects
|
|
189
|
+
|
|
190
|
+
attr_reader :conflict_solver
|
|
191
|
+
attr_reader :options
|
|
192
|
+
|
|
193
|
+
def conflict_solver=(value)
|
|
194
|
+
@conflict_solver = case value
|
|
195
|
+
when :update
|
|
196
|
+
SolverUpdateRelations
|
|
197
|
+
when :invalidate
|
|
198
|
+
SolverInvalidateTransaction
|
|
199
|
+
when :ignore
|
|
200
|
+
SolverIgnoreUpdate.new
|
|
201
|
+
else value
|
|
202
|
+
end
|
|
203
|
+
end
|
|
204
|
+
|
|
205
|
+
# Creates a new transaction which applies on +plan+
|
|
206
|
+
def initialize(plan, options = {})
|
|
207
|
+
options = validate_options options,
|
|
208
|
+
:conflict_solver => :invalidate
|
|
209
|
+
|
|
210
|
+
@options = options
|
|
211
|
+
self.conflict_solver = options[:conflict_solver]
|
|
212
|
+
super()
|
|
213
|
+
|
|
214
|
+
@plan = plan
|
|
215
|
+
|
|
216
|
+
@proxy_objects = Hash.new
|
|
217
|
+
@removed_objects = ValueSet.new
|
|
218
|
+
@discarded_tasks = ValueSet.new
|
|
219
|
+
@auto_tasks = ValueSet.new
|
|
220
|
+
|
|
221
|
+
Roby::Control.synchronize do
|
|
222
|
+
plan.transactions << self
|
|
223
|
+
plan.added_transaction(self)
|
|
224
|
+
end
|
|
225
|
+
end
|
|
226
|
+
|
|
227
|
+
def discover_neighborhood(object)
|
|
228
|
+
self[object]
|
|
229
|
+
object.each_relation do |rel|
|
|
230
|
+
object.each_parent_object(rel) { |obj| self[obj] }
|
|
231
|
+
object.each_child_object(rel) { |obj| self[obj] }
|
|
232
|
+
end
|
|
233
|
+
end
|
|
234
|
+
|
|
235
|
+
def replace(from, to)
|
|
236
|
+
# Make sure +from+, its events and all the related tasks and events
|
|
237
|
+
# are in the transaction
|
|
238
|
+
from = may_unwrap(from)
|
|
239
|
+
discover_neighborhood(from)
|
|
240
|
+
from.each_event do |ev|
|
|
241
|
+
discover_neighborhood(ev)
|
|
242
|
+
end
|
|
243
|
+
|
|
244
|
+
super(self[from], self[to])
|
|
245
|
+
end
|
|
246
|
+
|
|
247
|
+
def insert(t)
|
|
248
|
+
raise "transaction #{self} has been either committed or discarded. No modification allowed" if freezed?
|
|
249
|
+
if proxy = self[t, false]
|
|
250
|
+
discarded_tasks.delete(may_unwrap(proxy))
|
|
251
|
+
end
|
|
252
|
+
super(self[t, true])
|
|
253
|
+
end
|
|
254
|
+
def permanent(t)
|
|
255
|
+
raise "transaction #{self} has been either committed or discarded. No modification allowed" if freezed?
|
|
256
|
+
if proxy = self[t, false]
|
|
257
|
+
auto_tasks.delete(may_unwrap(proxy))
|
|
258
|
+
end
|
|
259
|
+
super(self[t, true])
|
|
260
|
+
end
|
|
261
|
+
def discover(objects)
|
|
262
|
+
raise "transaction #{self} has been either committed or discarded. No modification allowed" if freezed?
|
|
263
|
+
super(self[objects, true])
|
|
264
|
+
self
|
|
265
|
+
end
|
|
266
|
+
|
|
267
|
+
def auto(t)
|
|
268
|
+
raise "transaction #{self} has been either committed or discarded. No modification allowed" if freezed?
|
|
269
|
+
if proxy = self[t, false]
|
|
270
|
+
super(proxy)
|
|
271
|
+
end
|
|
272
|
+
|
|
273
|
+
t = may_unwrap(t)
|
|
274
|
+
if t.plan == self.plan
|
|
275
|
+
auto_tasks.insert(t)
|
|
276
|
+
end
|
|
277
|
+
end
|
|
278
|
+
|
|
279
|
+
def discard(t)
|
|
280
|
+
raise "transaction #{self} has been either committed or discarded. No modification allowed" if freezed?
|
|
281
|
+
if proxy = self[t, false]
|
|
282
|
+
super(proxy)
|
|
283
|
+
end
|
|
284
|
+
|
|
285
|
+
t = may_unwrap(t)
|
|
286
|
+
if t.plan == self.plan
|
|
287
|
+
discarded_tasks.insert(t)
|
|
288
|
+
end
|
|
289
|
+
end
|
|
290
|
+
|
|
291
|
+
attribute(:invalidation_reasons) { Array.new }
|
|
292
|
+
|
|
293
|
+
def invalid=(flag)
|
|
294
|
+
if !flag
|
|
295
|
+
invalidation_reasons.clear
|
|
296
|
+
end
|
|
297
|
+
@invalid = flag
|
|
298
|
+
end
|
|
299
|
+
|
|
300
|
+
def invalid?; @invalid end
|
|
301
|
+
def valid_transaction?; transactions.empty? && !invalid? end
|
|
302
|
+
def invalidate(reason = nil)
|
|
303
|
+
self.invalid = true
|
|
304
|
+
invalidation_reasons << [reason, caller(1)] if reason
|
|
305
|
+
Roby.debug do
|
|
306
|
+
"invalidating #{self}: #{reason}"
|
|
307
|
+
end
|
|
308
|
+
end
|
|
309
|
+
def check_valid_transaction
|
|
310
|
+
return if valid_transaction?
|
|
311
|
+
|
|
312
|
+
unless transactions.empty?
|
|
313
|
+
raise InvalidTransaction, "there is still transactions on top of this one"
|
|
314
|
+
end
|
|
315
|
+
message = invalidation_reasons.map do |reason, trace|
|
|
316
|
+
"#{trace[0]}: #{reason}\n #{trace[1..-1].join("\n ")}"
|
|
317
|
+
end.join("\n")
|
|
318
|
+
raise InvalidTransaction, "invalid transaction: #{message}"
|
|
319
|
+
end
|
|
320
|
+
|
|
321
|
+
# Commit all modifications that have been registered
|
|
322
|
+
# in this transaction
|
|
323
|
+
def commit_transaction
|
|
324
|
+
# if !Roby.control.running?
|
|
325
|
+
# raise "#commit_transaction requires the presence of a control thread"
|
|
326
|
+
# end
|
|
327
|
+
|
|
328
|
+
check_valid_transaction
|
|
329
|
+
freezed!
|
|
330
|
+
|
|
331
|
+
Roby.execute do
|
|
332
|
+
auto_tasks.each { |t| plan.auto(t) }
|
|
333
|
+
discarded_tasks.each { |t| plan.discard(t) }
|
|
334
|
+
removed_objects.each do |obj|
|
|
335
|
+
plan.remove_object(obj) if plan.include?(obj)
|
|
336
|
+
end
|
|
337
|
+
|
|
338
|
+
discover_tasks = ValueSet.new
|
|
339
|
+
discover_events = ValueSet.new
|
|
340
|
+
insert = ValueSet.new
|
|
341
|
+
permanent = ValueSet.new
|
|
342
|
+
known_tasks.dup.each do |t|
|
|
343
|
+
unwrapped = if t.kind_of?(Transactions::Proxy)
|
|
344
|
+
finalized_task(t)
|
|
345
|
+
t.__getobj__
|
|
346
|
+
else
|
|
347
|
+
known_tasks.delete(t)
|
|
348
|
+
t
|
|
349
|
+
end
|
|
350
|
+
|
|
351
|
+
if missions.include?(t) && t.self_owned?
|
|
352
|
+
missions.delete(t)
|
|
353
|
+
insert << unwrapped
|
|
354
|
+
elsif keepalive.include?(t) && t.self_owned?
|
|
355
|
+
keepalive.delete(t)
|
|
356
|
+
permanent << unwrapped
|
|
357
|
+
end
|
|
358
|
+
|
|
359
|
+
discover_tasks << unwrapped
|
|
360
|
+
end
|
|
361
|
+
|
|
362
|
+
free_events.dup.each do |ev|
|
|
363
|
+
unwrapped = if ev.kind_of?(Transactions::Proxy)
|
|
364
|
+
finalized_event(ev)
|
|
365
|
+
ev.__getobj__
|
|
366
|
+
else
|
|
367
|
+
free_events.delete(ev)
|
|
368
|
+
ev
|
|
369
|
+
end
|
|
370
|
+
|
|
371
|
+
discover_events << unwrapped
|
|
372
|
+
end
|
|
373
|
+
|
|
374
|
+
new_tasks = plan.discover_task_set(discover_tasks)
|
|
375
|
+
new_tasks.each do |task|
|
|
376
|
+
if task.respond_to?(:commit_transaction)
|
|
377
|
+
task.commit_transaction
|
|
378
|
+
end
|
|
379
|
+
end
|
|
380
|
+
|
|
381
|
+
new_events = plan.discover_event_set(discover_events)
|
|
382
|
+
new_events.each do |event|
|
|
383
|
+
if event.respond_to?(:commit_transaction)
|
|
384
|
+
event.commit_transaction
|
|
385
|
+
end
|
|
386
|
+
end
|
|
387
|
+
|
|
388
|
+
# Set the plan to nil in known tasks to avoid having the checks on
|
|
389
|
+
# #plan to raise an exception
|
|
390
|
+
proxy_objects.each_value { |proxy| proxy.commit_transaction }
|
|
391
|
+
proxy_objects.each_value { |proxy| proxy.clear_relations }
|
|
392
|
+
|
|
393
|
+
insert.each { |t| plan.insert(t) }
|
|
394
|
+
permanent.each { |t| plan.permanent(t) }
|
|
395
|
+
|
|
396
|
+
proxies = proxy_objects.dup
|
|
397
|
+
clear
|
|
398
|
+
# Replace proxies by forwarder objects
|
|
399
|
+
proxies.each do |object, proxy|
|
|
400
|
+
forwarder = Proxy.forwarder(object)
|
|
401
|
+
forwarder.freeze
|
|
402
|
+
Kernel.swap! proxy, forwarder
|
|
403
|
+
end
|
|
404
|
+
|
|
405
|
+
committed_transaction
|
|
406
|
+
plan.remove_transaction(self)
|
|
407
|
+
@plan = nil
|
|
408
|
+
end
|
|
409
|
+
end
|
|
410
|
+
def committed_transaction; super if defined? super end
|
|
411
|
+
def finalized?; !plan end
|
|
412
|
+
|
|
413
|
+
def enable_proxying; @disable_proxying = false end
|
|
414
|
+
def disable_proxying
|
|
415
|
+
@disable_proxying = true
|
|
416
|
+
if block_given?
|
|
417
|
+
begin
|
|
418
|
+
yield
|
|
419
|
+
ensure
|
|
420
|
+
@disable_proxying = false
|
|
421
|
+
end
|
|
422
|
+
end
|
|
423
|
+
end
|
|
424
|
+
def proxying?; !@freezed && !@disable_proxying end
|
|
425
|
+
|
|
426
|
+
# Discard all the modifications that have been registered
|
|
427
|
+
# in this transaction
|
|
428
|
+
def discard_transaction
|
|
429
|
+
# if !Roby.control.running?
|
|
430
|
+
# raise "#commit_transaction requires the presence of a control thread"
|
|
431
|
+
if !transactions.empty?
|
|
432
|
+
raise InvalidTransaction, "there is still transactions on top of this one"
|
|
433
|
+
end
|
|
434
|
+
|
|
435
|
+
freezed!
|
|
436
|
+
proxy_objects.each_value { |proxy| proxy.discard_transaction }
|
|
437
|
+
clear
|
|
438
|
+
|
|
439
|
+
discarded_transaction
|
|
440
|
+
Roby.execute do
|
|
441
|
+
plan.remove_transaction(self)
|
|
442
|
+
end
|
|
443
|
+
@plan = nil
|
|
444
|
+
end
|
|
445
|
+
def discarded_transaction; super if defined? super end
|
|
446
|
+
|
|
447
|
+
def freezed!
|
|
448
|
+
@freezed = true
|
|
449
|
+
end
|
|
450
|
+
|
|
451
|
+
def clear
|
|
452
|
+
removed_objects.clear
|
|
453
|
+
discarded_tasks.clear
|
|
454
|
+
proxy_objects.each_value { |proxy| proxy.clear_relations }
|
|
455
|
+
proxy_objects.clear
|
|
456
|
+
super
|
|
457
|
+
end
|
|
458
|
+
end
|
|
459
|
+
end
|
|
460
|
+
|
|
461
|
+
require 'roby/transactions/updates'
|
|
462
|
+
|