dynflow 0.8.16 → 0.8.17
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.
- checksums.yaml +4 -4
- data/.rubocop.yml +21 -0
- data/.rubocop_todo.yml +0 -25
- data/doc/pages/plugins/div_tag.rb +1 -1
- data/doc/pages/plugins/tags.rb +0 -1
- data/examples/orchestrate.rb +0 -1
- data/examples/remote_executor.rb +3 -3
- data/examples/sub_plan_concurrency_control.rb +0 -1
- data/examples/sub_plans.rb +0 -1
- data/lib/dynflow.rb +1 -0
- data/lib/dynflow/action.rb +6 -6
- data/lib/dynflow/config.rb +2 -2
- data/lib/dynflow/connectors/database.rb +1 -1
- data/lib/dynflow/connectors/direct.rb +1 -1
- data/lib/dynflow/coordinator.rb +4 -4
- data/lib/dynflow/director.rb +190 -0
- data/lib/dynflow/director/execution_plan_manager.rb +107 -0
- data/lib/dynflow/director/flow_manager.rb +43 -0
- data/lib/dynflow/director/running_steps_manager.rb +79 -0
- data/lib/dynflow/director/sequence_cursor.rb +91 -0
- data/lib/dynflow/{executors/parallel → director}/sequential_manager.rb +2 -2
- data/lib/dynflow/director/work_queue.rb +48 -0
- data/lib/dynflow/dispatcher/client_dispatcher.rb +24 -24
- data/lib/dynflow/dispatcher/executor_dispatcher.rb +1 -1
- data/lib/dynflow/execution_plan.rb +32 -15
- data/lib/dynflow/execution_plan/steps/abstract.rb +14 -14
- data/lib/dynflow/execution_plan/steps/error.rb +1 -1
- data/lib/dynflow/execution_plan/steps/finalize_step.rb +0 -1
- data/lib/dynflow/execution_plan/steps/plan_step.rb +11 -12
- data/lib/dynflow/execution_plan/steps/run_step.rb +1 -1
- data/lib/dynflow/executors/abstract.rb +5 -8
- data/lib/dynflow/executors/parallel.rb +4 -34
- data/lib/dynflow/executors/parallel/core.rb +18 -118
- data/lib/dynflow/executors/parallel/pool.rb +2 -2
- data/lib/dynflow/executors/parallel/worker.rb +3 -11
- data/lib/dynflow/persistence_adapters/sequel.rb +1 -2
- data/lib/dynflow/testing.rb +2 -0
- data/lib/dynflow/testing/in_thread_executor.rb +52 -0
- data/lib/dynflow/testing/in_thread_world.rb +64 -0
- data/lib/dynflow/testing/managed_clock.rb +1 -1
- data/lib/dynflow/throttle_limiter.rb +1 -1
- data/lib/dynflow/version.rb +1 -1
- data/lib/dynflow/world.rb +13 -7
- data/test/abnormal_states_recovery_test.rb +10 -0
- data/test/action_test.rb +9 -9
- data/test/clock_test.rb +0 -2
- data/test/concurrency_control_test.rb +1 -1
- data/test/execution_plan_test.rb +0 -2
- data/test/executor_test.rb +6 -13
- data/test/support/code_workflow_example.rb +1 -1
- data/test/support/rescue_example.rb +0 -1
- data/test/test_helper.rb +9 -12
- data/test/testing_test.rb +74 -2
- data/web/views/plan_step.erb +2 -0
- metadata +11 -8
- data/lib/dynflow/executors/parallel/execution_plan_manager.rb +0 -111
- data/lib/dynflow/executors/parallel/flow_manager.rb +0 -45
- data/lib/dynflow/executors/parallel/running_steps_manager.rb +0 -81
- data/lib/dynflow/executors/parallel/sequence_cursor.rb +0 -97
- data/lib/dynflow/executors/parallel/work_queue.rb +0 -50
@@ -35,17 +35,17 @@ module Dynflow
|
|
35
35
|
|
36
36
|
# all params with default values are part of *private* api
|
37
37
|
def initialize(world,
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
38
|
+
id = SecureRandom.uuid,
|
39
|
+
state = :pending,
|
40
|
+
root_plan_step = nil,
|
41
|
+
run_flow = Flows::Concurrence.new([]),
|
42
|
+
finalize_flow = Flows::Sequence.new([]),
|
43
|
+
steps = {},
|
44
|
+
started_at = nil,
|
45
|
+
ended_at = nil,
|
46
|
+
execution_time = nil,
|
47
|
+
real_time = 0.0,
|
48
|
+
execution_history = ExecutionHistory.new)
|
49
49
|
|
50
50
|
@id = Type! id, String
|
51
51
|
@world = Type! world, World
|
@@ -133,6 +133,18 @@ module Dynflow
|
|
133
133
|
end
|
134
134
|
end
|
135
135
|
|
136
|
+
def plan_steps
|
137
|
+
steps_of_type(Dynflow::ExecutionPlan::Steps::PlanStep)
|
138
|
+
end
|
139
|
+
|
140
|
+
def run_steps
|
141
|
+
steps_of_type(Dynflow::ExecutionPlan::Steps::RunStep)
|
142
|
+
end
|
143
|
+
|
144
|
+
def finalize_steps
|
145
|
+
steps_of_type(Dynflow::ExecutionPlan::Steps::FinalizeStep)
|
146
|
+
end
|
147
|
+
|
136
148
|
def failed_steps
|
137
149
|
steps_in_state(:error)
|
138
150
|
end
|
@@ -165,10 +177,10 @@ module Dynflow
|
|
165
177
|
execution_history.add("delay", @world.id)
|
166
178
|
serializer = root_plan_step.delay(delay_options, args)
|
167
179
|
delayed_plan = DelayedPlan.new(@world,
|
168
|
-
|
169
|
-
|
170
|
-
|
171
|
-
|
180
|
+
id,
|
181
|
+
delay_options[:start_at],
|
182
|
+
delay_options.fetch(:start_before, nil),
|
183
|
+
serializer)
|
172
184
|
persistence.save_delayed_plan(delayed_plan)
|
173
185
|
ensure
|
174
186
|
update_state(error? ? :stopped : :scheduled)
|
@@ -263,6 +275,11 @@ module Dynflow
|
|
263
275
|
end
|
264
276
|
|
265
277
|
# @api private
|
278
|
+
|
279
|
+
def steps_of_type(type)
|
280
|
+
steps.values.find_all { |step| step.is_a?(type) }
|
281
|
+
end
|
282
|
+
|
266
283
|
def current_run_flow
|
267
284
|
@run_flow_stack.last
|
268
285
|
end
|
@@ -9,18 +9,18 @@ module Dynflow
|
|
9
9
|
attr_accessor :error
|
10
10
|
|
11
11
|
def initialize(execution_plan_id,
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
12
|
+
id,
|
13
|
+
state,
|
14
|
+
action_class,
|
15
|
+
action_id,
|
16
|
+
error,
|
17
|
+
world,
|
18
|
+
started_at = nil,
|
19
|
+
ended_at = nil,
|
20
|
+
execution_time = 0.0,
|
21
|
+
real_time = 0.0,
|
22
|
+
progress_done = nil,
|
23
|
+
progress_weight = nil)
|
24
24
|
|
25
25
|
@id = id || raise(ArgumentError, 'missing id')
|
26
26
|
@execution_plan_id = Type! execution_plan_id, String
|
@@ -155,9 +155,9 @@ module Dynflow
|
|
155
155
|
block.call
|
156
156
|
ensure
|
157
157
|
@progress_done, @progress_weight = action.calculated_progress
|
158
|
-
@ended_at
|
158
|
+
@ended_at = Time.now
|
159
159
|
@execution_time += @ended_at - start
|
160
|
-
@real_time
|
160
|
+
@real_time = @ended_at - @started_at
|
161
161
|
end
|
162
162
|
end
|
163
163
|
end
|
@@ -5,17 +5,17 @@ module Dynflow
|
|
5
5
|
|
6
6
|
# @param [Array] children is a private API parameter
|
7
7
|
def initialize(execution_plan_id,
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
8
|
+
id,
|
9
|
+
state,
|
10
|
+
action_class,
|
11
|
+
action_id,
|
12
|
+
error,
|
13
|
+
world,
|
14
|
+
started_at = nil,
|
15
|
+
ended_at = nil,
|
16
|
+
execution_time = 0.0,
|
17
|
+
real_time = 0.0,
|
18
|
+
children = [])
|
19
19
|
|
20
20
|
super execution_plan_id, id, state, action_class, action_id, error, world, started_at,
|
21
21
|
ended_at, execution_time, real_time
|
@@ -68,7 +68,6 @@ module Dynflow
|
|
68
68
|
error: [] }
|
69
69
|
end
|
70
70
|
|
71
|
-
|
72
71
|
def self.new_from_hash(hash, execution_plan_id, world)
|
73
72
|
check_class_matching hash
|
74
73
|
new execution_plan_id,
|
@@ -1,13 +1,6 @@
|
|
1
1
|
module Dynflow
|
2
2
|
module Executors
|
3
3
|
class Abstract
|
4
|
-
Event = Algebrick.type do
|
5
|
-
fields! execution_plan_id: String,
|
6
|
-
step_id: Fixnum,
|
7
|
-
event: Object,
|
8
|
-
result: Concurrent::Edge::Future
|
9
|
-
end
|
10
|
-
|
11
4
|
include Algebrick::TypeCheck
|
12
5
|
attr_reader :world, :logger
|
13
6
|
|
@@ -16,9 +9,13 @@ module Dynflow
|
|
16
9
|
@logger = world.logger
|
17
10
|
end
|
18
11
|
|
12
|
+
# @param execution_plan_id [String] id of execution plan
|
13
|
+
# @param finished [Concurrent::Edge::Future]
|
14
|
+
# @param wait_for_acceptance [TrueClass|FalseClass] should the executor confirm receiving
|
15
|
+
# the event, disable if calling executor from within executor
|
19
16
|
# @return [Concurrent::Edge::Future]
|
20
17
|
# @raise when execution_plan_id is not accepted
|
21
|
-
def execute(execution_plan_id)
|
18
|
+
def execute(execution_plan_id, finished = Concurrent.future, wait_for_acceptance = true)
|
22
19
|
raise NotImplementedError
|
23
20
|
end
|
24
21
|
|
@@ -1,41 +1,10 @@
|
|
1
1
|
module Dynflow
|
2
2
|
module Executors
|
3
3
|
class Parallel < Abstract
|
4
|
-
|
5
|
-
require 'dynflow/executors/parallel/sequence_cursor'
|
6
|
-
require 'dynflow/executors/parallel/flow_manager'
|
7
|
-
require 'dynflow/executors/parallel/work_queue'
|
8
|
-
require 'dynflow/executors/parallel/execution_plan_manager'
|
9
|
-
require 'dynflow/executors/parallel/sequential_manager'
|
10
|
-
require 'dynflow/executors/parallel/running_steps_manager'
|
11
4
|
require 'dynflow/executors/parallel/core'
|
12
5
|
require 'dynflow/executors/parallel/pool'
|
13
6
|
require 'dynflow/executors/parallel/worker'
|
14
7
|
|
15
|
-
UnprocessableEvent = Class.new(Dynflow::Error)
|
16
|
-
|
17
|
-
Algebrick.type do |work|
|
18
|
-
Work = work
|
19
|
-
|
20
|
-
Work::Finalize = type do
|
21
|
-
fields! sequential_manager: SequentialManager,
|
22
|
-
execution_plan_id: String
|
23
|
-
end
|
24
|
-
|
25
|
-
Work::Step = type do
|
26
|
-
fields! step: ExecutionPlan::Steps::AbstractFlowStep,
|
27
|
-
execution_plan_id: String
|
28
|
-
end
|
29
|
-
|
30
|
-
Work::Event = type do
|
31
|
-
fields! step: ExecutionPlan::Steps::AbstractFlowStep,
|
32
|
-
execution_plan_id: String,
|
33
|
-
event: Event
|
34
|
-
end
|
35
|
-
|
36
|
-
variants Work::Step, Work::Event, Work::Finalize
|
37
|
-
end
|
38
|
-
|
39
8
|
def initialize(world, pool_size = 10)
|
40
9
|
super(world)
|
41
10
|
@core = Core.spawn name: 'parallel-executor-core',
|
@@ -43,8 +12,9 @@ module Dynflow
|
|
43
12
|
initialized: @core_initialized = Concurrent.future
|
44
13
|
end
|
45
14
|
|
46
|
-
def execute(execution_plan_id, finished = Concurrent.future)
|
47
|
-
@core.ask([:handle_execution, execution_plan_id, finished])
|
15
|
+
def execute(execution_plan_id, finished = Concurrent.future, wait_for_acceptance = true)
|
16
|
+
accepted = @core.ask([:handle_execution, execution_plan_id, finished])
|
17
|
+
accepted.value! if wait_for_acceptance
|
48
18
|
finished
|
49
19
|
rescue Concurrent::Actor::ActorTerminated => error
|
50
20
|
dynflow_error = Dynflow::Error.new('executor terminated')
|
@@ -56,7 +26,7 @@ module Dynflow
|
|
56
26
|
end
|
57
27
|
|
58
28
|
def event(execution_plan_id, step_id, event, future = Concurrent.future)
|
59
|
-
@core.ask([:handle_event, Event[execution_plan_id, step_id, event, future]])
|
29
|
+
@core.ask([:handle_event, Director::Event[execution_plan_id, step_id, event, future]])
|
60
30
|
future
|
61
31
|
end
|
62
32
|
|
@@ -6,38 +6,33 @@ module Dynflow
|
|
6
6
|
attr_reader :logger
|
7
7
|
|
8
8
|
def initialize(world, pool_size)
|
9
|
-
@logger
|
10
|
-
@world
|
11
|
-
@pool
|
12
|
-
@
|
13
|
-
@
|
14
|
-
@terminated = nil
|
9
|
+
@logger = world.logger
|
10
|
+
@world = Type! world, World
|
11
|
+
@pool = Pool.spawn('pool', reference, pool_size, world.transaction_adapter)
|
12
|
+
@terminated = nil
|
13
|
+
@director = Director.new(@world)
|
15
14
|
end
|
16
15
|
|
17
16
|
def handle_execution(execution_plan_id, finished)
|
18
|
-
|
17
|
+
if terminating?
|
18
|
+
raise Dynflow::Error,
|
19
|
+
"cannot accept execution_plan_id:#{execution_plan_id} core is terminating"
|
20
|
+
end
|
21
|
+
|
22
|
+
feed_pool(@director.start_execution(execution_plan_id, finished))
|
19
23
|
end
|
20
24
|
|
21
25
|
def handle_event(event)
|
22
|
-
Type! event,
|
26
|
+
Type! event, Director::Event
|
23
27
|
if terminating?
|
24
28
|
raise Dynflow::Error,
|
25
29
|
"cannot accept event: #{event} core is terminating"
|
26
30
|
end
|
27
|
-
|
28
|
-
if execution_plan_manager
|
29
|
-
feed_pool execution_plan_manager.event(event)
|
30
|
-
true
|
31
|
-
else
|
32
|
-
raise Dynflow::Error, "no manager for #{event.inspect}"
|
33
|
-
end
|
34
|
-
rescue Dynflow::Error => e
|
35
|
-
event.result.fail e.message
|
36
|
-
raise e
|
31
|
+
feed_pool(@director.handle_event(event))
|
37
32
|
end
|
38
33
|
|
39
|
-
def
|
40
|
-
|
34
|
+
def work_finished(work)
|
35
|
+
feed_pool(@director.work_finished(work))
|
41
36
|
end
|
42
37
|
|
43
38
|
def handle_persistence_error(error)
|
@@ -53,19 +48,7 @@ module Dynflow
|
|
53
48
|
end
|
54
49
|
|
55
50
|
def finish_termination
|
56
|
-
|
57
|
-
logger.error "... cleaning #{@execution_plan_managers.size} execution plans ..."
|
58
|
-
begin
|
59
|
-
@execution_plan_managers.values.each do |manager|
|
60
|
-
manager.terminate
|
61
|
-
end
|
62
|
-
rescue Errors::PersistenceError
|
63
|
-
logger.error "could not to clean the data properly"
|
64
|
-
end
|
65
|
-
@execution_plan_managers.values.each do |manager|
|
66
|
-
finish_plan(manager.execution_plan.id)
|
67
|
-
end
|
68
|
-
end
|
51
|
+
@director.terminate
|
69
52
|
logger.error '... core terminated.'
|
70
53
|
super
|
71
54
|
end
|
@@ -78,96 +61,13 @@ module Dynflow
|
|
78
61
|
self.tell(:handle_persistence_error, e)
|
79
62
|
end
|
80
63
|
|
81
|
-
# @return
|
82
|
-
def track_execution_plan(execution_plan_id, finished)
|
83
|
-
execution_plan = @world.persistence.load_execution_plan(execution_plan_id)
|
84
|
-
|
85
|
-
if terminating?
|
86
|
-
raise Dynflow::Error,
|
87
|
-
"cannot accept execution_plan_id:#{execution_plan_id} core is terminating"
|
88
|
-
end
|
89
|
-
|
90
|
-
if @execution_plan_managers[execution_plan_id]
|
91
|
-
raise Dynflow::Error,
|
92
|
-
"cannot execute execution_plan_id:#{execution_plan_id} it's already running"
|
93
|
-
end
|
94
|
-
|
95
|
-
if execution_plan.state == :stopped
|
96
|
-
raise Dynflow::Error,
|
97
|
-
"cannot execute execution_plan_id:#{execution_plan_id} it's stopped"
|
98
|
-
end
|
99
|
-
|
100
|
-
@execution_plan_managers[execution_plan_id] =
|
101
|
-
ExecutionPlanManager.new(@world, execution_plan, finished)
|
102
|
-
|
103
|
-
rescue Dynflow::Error => e
|
104
|
-
finished.fail e
|
105
|
-
nil
|
106
|
-
end
|
107
|
-
|
108
|
-
def update_manager(finished_work)
|
109
|
-
manager = @execution_plan_managers[finished_work.execution_plan_id]
|
110
|
-
next_work = manager.what_is_next(finished_work)
|
111
|
-
continue_manager manager, next_work
|
112
|
-
end
|
113
|
-
|
114
|
-
def continue_manager(manager, next_work)
|
115
|
-
if manager.done?
|
116
|
-
finish_plan manager.execution_plan.id
|
117
|
-
else
|
118
|
-
feed_pool next_work
|
119
|
-
end
|
120
|
-
end
|
121
|
-
|
122
|
-
def rescue?(manager)
|
123
|
-
return false if terminating?
|
124
|
-
@world.auto_rescue && manager.execution_plan.state == :paused &&
|
125
|
-
!@plan_ids_in_rescue.include?(manager.execution_plan.id)
|
126
|
-
end
|
127
|
-
|
128
|
-
def rescue!(manager)
|
129
|
-
# TODO: after moving to concurrent-ruby actors, there should be better place
|
130
|
-
# to put this logic of making sure we don't run rescues in endless loop
|
131
|
-
@plan_ids_in_rescue << manager.execution_plan.id
|
132
|
-
rescue_plan_id = manager.execution_plan.rescue_plan_id
|
133
|
-
if rescue_plan_id
|
134
|
-
reference.tell([:handle_execution, rescue_plan_id, manager.future])
|
135
|
-
else
|
136
|
-
set_future(manager)
|
137
|
-
end
|
138
|
-
end
|
139
|
-
|
140
64
|
def feed_pool(work_items)
|
141
65
|
return if terminating?
|
142
|
-
Type! work_items, Array, Work, NilClass
|
143
66
|
return if work_items.nil?
|
144
|
-
work_items = [work_items] if work_items.is_a?
|
145
|
-
work_items.all? { |i| Type! i,
|
67
|
+
work_items = [work_items] if work_items.is_a? Director::WorkItem
|
68
|
+
work_items.all? { |i| Type! i, Director::WorkItem }
|
146
69
|
work_items.each { |new_work| @pool.tell([:schedule_work, new_work]) }
|
147
70
|
end
|
148
|
-
|
149
|
-
def finish_plan(execution_plan_id)
|
150
|
-
manager = @execution_plan_managers.delete(execution_plan_id)
|
151
|
-
if rescue?(manager)
|
152
|
-
rescue!(manager)
|
153
|
-
else
|
154
|
-
set_future(manager)
|
155
|
-
end
|
156
|
-
end
|
157
|
-
|
158
|
-
def set_future(manager)
|
159
|
-
@plan_ids_in_rescue.delete(manager.execution_plan.id)
|
160
|
-
manager.future.success manager.execution_plan
|
161
|
-
end
|
162
|
-
|
163
|
-
def start_executing(manager)
|
164
|
-
return if manager.nil?
|
165
|
-
Type! manager, ExecutionPlanManager
|
166
|
-
|
167
|
-
next_work = manager.start
|
168
|
-
continue_manager manager, next_work
|
169
|
-
end
|
170
|
-
|
171
71
|
end
|
172
72
|
end
|
173
73
|
end
|