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
@@ -28,7 +28,7 @@ module Support
|
|
28
28
|
triages = all_planned_actions(Triage)
|
29
29
|
assignees = triages.map do |triage|
|
30
30
|
triage.output[:classification] &&
|
31
|
-
|
31
|
+
triage.output[:classification][:assignee]
|
32
32
|
end.compact.uniq
|
33
33
|
{ assignees: assignees }
|
34
34
|
end
|
data/test/test_helper.rb
CHANGED
@@ -97,8 +97,8 @@ module WorldFactory
|
|
97
97
|
end
|
98
98
|
|
99
99
|
# The worlds created by this method are getting terminated after each test run
|
100
|
-
def self.create_world(&block)
|
101
|
-
|
100
|
+
def self.create_world(klass = Dynflow::World, &block)
|
101
|
+
klass.new(test_world_config(&block)).tap do |world|
|
102
102
|
created_worlds << world
|
103
103
|
end
|
104
104
|
end
|
@@ -194,7 +194,7 @@ module TestHelpers
|
|
194
194
|
client_world.persistence.load_execution_plan(triggered.id).state == :running
|
195
195
|
end
|
196
196
|
|
197
|
-
executor
|
197
|
+
executor = WorldFactory.created_worlds.find { |e| e.id == executor_id }
|
198
198
|
raise "Could not find an executor with id #{executor_id}" unless executor
|
199
199
|
yield executor
|
200
200
|
return triggered
|
@@ -214,10 +214,10 @@ module TestHelpers
|
|
214
214
|
assert_equal :stopped, plan.state
|
215
215
|
assert_equal :success, plan.result
|
216
216
|
assert_equal plan.execution_history.map(&:name),
|
217
|
-
|
218
|
-
|
219
|
-
|
220
|
-
|
217
|
+
['start execution',
|
218
|
+
'terminate execution',
|
219
|
+
'start execution',
|
220
|
+
'finish execution']
|
221
221
|
refute_equal plan.execution_history.first.world_id, plan.execution_history.to_a.last.world_id
|
222
222
|
end
|
223
223
|
end
|
@@ -239,7 +239,7 @@ events_test = -> do
|
|
239
239
|
|
240
240
|
Concurrent::Edge::Event.singleton_class.send :define_method, :new do |*args, &block|
|
241
241
|
super(*args, &block).tap do |event|
|
242
|
-
event_creations[event.object_id]
|
242
|
+
event_creations[event.object_id] = caller(4)
|
243
243
|
end
|
244
244
|
end
|
245
245
|
|
@@ -332,10 +332,7 @@ module PlanAssertions
|
|
332
332
|
end
|
333
333
|
|
334
334
|
def assert_planning_success(execution_plan)
|
335
|
-
plan_steps
|
336
|
-
step.is_a? Dynflow::ExecutionPlan::Steps::PlanStep
|
337
|
-
end
|
338
|
-
plan_steps.all? { |plan_step| plan_step.state.must_equal :success, plan_step.error }
|
335
|
+
execution_plan.plan_steps.all? { |plan_step| plan_step.state.must_equal :success, plan_step.error }
|
339
336
|
end
|
340
337
|
|
341
338
|
def assert_run_flow(expected, execution_plan)
|
data/test/testing_test.rb
CHANGED
@@ -74,7 +74,6 @@ module Dynflow
|
|
74
74
|
|
75
75
|
5.times { progress_action_time action }
|
76
76
|
|
77
|
-
|
78
77
|
action.output.must_equal('task' => { 'progress' => 100, 'done' => true },
|
79
78
|
'poll_attempts' => {'total' => 9, 'failed' => 0 })
|
80
79
|
action.run_progress.must_equal 1
|
@@ -166,6 +165,79 @@ module Dynflow
|
|
166
165
|
end
|
167
166
|
end
|
168
167
|
end
|
169
|
-
end
|
170
168
|
|
169
|
+
describe "in thread executor" do
|
170
|
+
let :world do
|
171
|
+
Dynflow::Testing::InThreadWorld.instance
|
172
|
+
end
|
173
|
+
|
174
|
+
let :issues_data do
|
175
|
+
[{ 'author' => 'Peter Smith', 'text' => 'Failing test' },
|
176
|
+
{ 'author' => 'John Doe', 'text' => 'Internal server error' }]
|
177
|
+
end
|
178
|
+
|
179
|
+
let :failing_issues_data do
|
180
|
+
[{ 'author' => 'Peter Smith', 'text' => 'Failing test' },
|
181
|
+
{ 'author' => 'John Doe', 'text' => 'trolling' }]
|
182
|
+
end
|
183
|
+
|
184
|
+
let :finalize_failing_issues_data do
|
185
|
+
[{ 'author' => 'Peter Smith', 'text' => 'Failing test' },
|
186
|
+
{ 'author' => 'John Doe', 'text' => 'trolling in finalize' }]
|
187
|
+
end
|
188
|
+
|
189
|
+
let :execution_plan do
|
190
|
+
world.plan(Support::CodeWorkflowExample::IncomingIssues, issues_data)
|
191
|
+
end
|
192
|
+
|
193
|
+
let :failed_execution_plan do
|
194
|
+
plan = world.plan(Support::CodeWorkflowExample::IncomingIssues, failing_issues_data)
|
195
|
+
plan = world.execute(plan.id).value
|
196
|
+
plan.state.must_equal :paused
|
197
|
+
plan
|
198
|
+
end
|
199
|
+
|
200
|
+
let :polling_execution_plan do
|
201
|
+
world.plan(Support::DummyExample::Polling, { :external_task_id => '123' })
|
202
|
+
end
|
203
|
+
|
204
|
+
it "is able to execute plans inside the thread" do
|
205
|
+
world.execute(execution_plan.id).value.tap do |plan|
|
206
|
+
plan.state.must_equal :stopped
|
207
|
+
end
|
208
|
+
end
|
209
|
+
|
210
|
+
it "is able to handle errors in the plan" do
|
211
|
+
world.execute(failed_execution_plan.id).value.tap do |plan|
|
212
|
+
plan.state.must_equal :paused
|
213
|
+
end
|
214
|
+
end
|
215
|
+
|
216
|
+
it "is able to handle when events" do
|
217
|
+
world.execute(polling_execution_plan.id).value.tap do |plan|
|
218
|
+
plan.state.must_equal :stopped
|
219
|
+
end
|
220
|
+
end
|
221
|
+
|
222
|
+
describe 'auto rescue' do
|
223
|
+
let(:world) do
|
224
|
+
WorldFactory.create_world(Dynflow::Testing::InThreadWorld) do |config|
|
225
|
+
config.auto_rescue = true
|
226
|
+
end
|
227
|
+
end
|
228
|
+
|
229
|
+
describe 'of plan with skips' do
|
230
|
+
let :execution_plan do
|
231
|
+
plan = world.plan(Support::RescueExample::ComplexActionWithSkip, :error_on_run)
|
232
|
+
world.execute(plan.id).value
|
233
|
+
end
|
234
|
+
|
235
|
+
it 'skips the action and continues automatically' do
|
236
|
+
execution_plan.state.must_equal :stopped
|
237
|
+
execution_plan.result.must_equal :warning
|
238
|
+
end
|
239
|
+
end
|
240
|
+
end
|
241
|
+
end
|
242
|
+
end
|
171
243
|
end
|
data/web/views/plan_step.erb
CHANGED
@@ -4,6 +4,8 @@
|
|
4
4
|
<span class="label label-<%= step_css_class(step) %>"><%= h("#{step.action_class}") %></span>
|
5
5
|
</p>
|
6
6
|
<div class="action">
|
7
|
+
<p><b>Started at:</b> <%= h(step.started_at) %></p>
|
8
|
+
<p><b>Ended at:</b> <%= h(step.ended_at) %> (duration <%= duration_to_s(step.real_time) %>)</p>
|
7
9
|
<% action = step.action @plan %>
|
8
10
|
<%= show_action_data("Input:", action.input) %>
|
9
11
|
<%= step_error(step) %>
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: dynflow
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.8.
|
4
|
+
version: 0.8.17
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Ivan Necas
|
@@ -9,7 +9,7 @@ authors:
|
|
9
9
|
autorequire:
|
10
10
|
bindir: bin
|
11
11
|
cert_chain: []
|
12
|
-
date: 2016-
|
12
|
+
date: 2016-11-15 00:00:00.000000000 Z
|
13
13
|
dependencies:
|
14
14
|
- !ruby/object:Gem::Dependency
|
15
15
|
name: multi_json
|
@@ -377,6 +377,13 @@ files:
|
|
377
377
|
- lib/dynflow/delayed_executors/abstract_core.rb
|
378
378
|
- lib/dynflow/delayed_executors/polling.rb
|
379
379
|
- lib/dynflow/delayed_plan.rb
|
380
|
+
- lib/dynflow/director.rb
|
381
|
+
- lib/dynflow/director/execution_plan_manager.rb
|
382
|
+
- lib/dynflow/director/flow_manager.rb
|
383
|
+
- lib/dynflow/director/running_steps_manager.rb
|
384
|
+
- lib/dynflow/director/sequence_cursor.rb
|
385
|
+
- lib/dynflow/director/sequential_manager.rb
|
386
|
+
- lib/dynflow/director/work_queue.rb
|
380
387
|
- lib/dynflow/dispatcher.rb
|
381
388
|
- lib/dynflow/dispatcher/abstract.rb
|
382
389
|
- lib/dynflow/dispatcher/client_dispatcher.rb
|
@@ -397,13 +404,7 @@ files:
|
|
397
404
|
- lib/dynflow/executors/abstract.rb
|
398
405
|
- lib/dynflow/executors/parallel.rb
|
399
406
|
- lib/dynflow/executors/parallel/core.rb
|
400
|
-
- lib/dynflow/executors/parallel/execution_plan_manager.rb
|
401
|
-
- lib/dynflow/executors/parallel/flow_manager.rb
|
402
407
|
- lib/dynflow/executors/parallel/pool.rb
|
403
|
-
- lib/dynflow/executors/parallel/running_steps_manager.rb
|
404
|
-
- lib/dynflow/executors/parallel/sequence_cursor.rb
|
405
|
-
- lib/dynflow/executors/parallel/sequential_manager.rb
|
406
|
-
- lib/dynflow/executors/parallel/work_queue.rb
|
407
408
|
- lib/dynflow/executors/parallel/worker.rb
|
408
409
|
- lib/dynflow/flows.rb
|
409
410
|
- lib/dynflow/flows/abstract.rb
|
@@ -457,6 +458,8 @@ files:
|
|
457
458
|
- lib/dynflow/testing/dummy_step.rb
|
458
459
|
- lib/dynflow/testing/dummy_world.rb
|
459
460
|
- lib/dynflow/testing/factories.rb
|
461
|
+
- lib/dynflow/testing/in_thread_executor.rb
|
462
|
+
- lib/dynflow/testing/in_thread_world.rb
|
460
463
|
- lib/dynflow/testing/managed_clock.rb
|
461
464
|
- lib/dynflow/testing/mimic.rb
|
462
465
|
- lib/dynflow/throttle_limiter.rb
|
@@ -1,111 +0,0 @@
|
|
1
|
-
module Dynflow
|
2
|
-
module Executors
|
3
|
-
class Parallel < Abstract
|
4
|
-
class ExecutionPlanManager
|
5
|
-
include Algebrick::TypeCheck
|
6
|
-
include Algebrick::Matching
|
7
|
-
|
8
|
-
attr_reader :execution_plan, :future
|
9
|
-
|
10
|
-
def initialize(world, execution_plan, future)
|
11
|
-
@world = Type! world, World
|
12
|
-
@execution_plan = Type! execution_plan, ExecutionPlan
|
13
|
-
@future = Type! future, Concurrent::Edge::Future
|
14
|
-
@running_steps_manager = RunningStepsManager.new(world)
|
15
|
-
|
16
|
-
unless [:planned, :paused].include? execution_plan.state
|
17
|
-
raise "execution_plan is not in pending or paused state, it's #{execution_plan.state}"
|
18
|
-
end
|
19
|
-
execution_plan.execution_history.add('start execution', @world.id)
|
20
|
-
execution_plan.update_state(:running)
|
21
|
-
end
|
22
|
-
|
23
|
-
def start
|
24
|
-
raise "The future was already set" if @future.completed?
|
25
|
-
start_run or start_finalize or finish
|
26
|
-
end
|
27
|
-
|
28
|
-
def prepare_next_step(step)
|
29
|
-
Work::Step[step, execution_plan.id].tap do |work|
|
30
|
-
@running_steps_manager.add(step, work)
|
31
|
-
end
|
32
|
-
end
|
33
|
-
|
34
|
-
# @return [Array<Work>] of Work items to continue with
|
35
|
-
def what_is_next(work)
|
36
|
-
Type! work, Work
|
37
|
-
|
38
|
-
compute_next_from_step =-> step do
|
39
|
-
raise unless @run_manager
|
40
|
-
raise if @run_manager.done?
|
41
|
-
|
42
|
-
next_steps = @run_manager.what_is_next(step)
|
43
|
-
if @run_manager.done?
|
44
|
-
start_finalize or finish
|
45
|
-
else
|
46
|
-
next_steps.map { |s| prepare_next_step(s) }
|
47
|
-
end
|
48
|
-
end
|
49
|
-
|
50
|
-
match(work,
|
51
|
-
(on Work::Step.(step: ~any) | Work::Event.(step: ~any) do |step|
|
52
|
-
execution_plan.steps[step.id] = step
|
53
|
-
suspended, work = @running_steps_manager.done(step)
|
54
|
-
unless suspended
|
55
|
-
work = compute_next_from_step.call step
|
56
|
-
end
|
57
|
-
work
|
58
|
-
end),
|
59
|
-
(on Work::Finalize do
|
60
|
-
raise unless @finalize_manager
|
61
|
-
finish
|
62
|
-
end))
|
63
|
-
end
|
64
|
-
|
65
|
-
def event(event)
|
66
|
-
Type! event, Parallel::Event
|
67
|
-
raise unless event.execution_plan_id == @execution_plan.id
|
68
|
-
@running_steps_manager.event(event)
|
69
|
-
end
|
70
|
-
|
71
|
-
def done?
|
72
|
-
(!@run_manager || @run_manager.done?) && (!@finalize_manager || @finalize_manager.done?)
|
73
|
-
end
|
74
|
-
|
75
|
-
def terminate
|
76
|
-
@running_steps_manager.terminate
|
77
|
-
end
|
78
|
-
|
79
|
-
private
|
80
|
-
|
81
|
-
def no_work
|
82
|
-
raise "No work but not done" unless done?
|
83
|
-
[]
|
84
|
-
end
|
85
|
-
|
86
|
-
def start_run
|
87
|
-
unless execution_plan.run_flow.empty?
|
88
|
-
raise 'run phase already started' if @run_manager
|
89
|
-
@run_manager = FlowManager.new(execution_plan, execution_plan.run_flow)
|
90
|
-
@run_manager.start.map { |s| prepare_next_step(s) }.tap { |a| raise if a.empty? }
|
91
|
-
end
|
92
|
-
end
|
93
|
-
|
94
|
-
def start_finalize
|
95
|
-
unless execution_plan.finalize_flow.empty?
|
96
|
-
raise 'finalize phase already started' if @finalize_manager
|
97
|
-
@finalize_manager = SequentialManager.new(@world, execution_plan)
|
98
|
-
Work::Finalize[@finalize_manager, execution_plan.id]
|
99
|
-
end
|
100
|
-
end
|
101
|
-
|
102
|
-
def finish
|
103
|
-
execution_plan.execution_history.add('finish execution', @world.id)
|
104
|
-
@execution_plan.update_state(execution_plan.error? ? :paused : :stopped)
|
105
|
-
return no_work
|
106
|
-
end
|
107
|
-
|
108
|
-
end
|
109
|
-
end
|
110
|
-
end
|
111
|
-
end
|
@@ -1,45 +0,0 @@
|
|
1
|
-
module Dynflow
|
2
|
-
module Executors
|
3
|
-
class Parallel < Abstract
|
4
|
-
class FlowManager
|
5
|
-
include Algebrick::TypeCheck
|
6
|
-
|
7
|
-
attr_reader :execution_plan, :cursor_index
|
8
|
-
|
9
|
-
def initialize(execution_plan, flow)
|
10
|
-
@execution_plan = Type! execution_plan, ExecutionPlan
|
11
|
-
@flow = flow
|
12
|
-
@cursor_index = {}
|
13
|
-
@cursor = build_root_cursor
|
14
|
-
end
|
15
|
-
|
16
|
-
def done?
|
17
|
-
@cursor.done?
|
18
|
-
end
|
19
|
-
|
20
|
-
# @return [Set] of steps to continue with
|
21
|
-
def what_is_next(flow_step)
|
22
|
-
return [] if flow_step.state == :suspended
|
23
|
-
|
24
|
-
success = flow_step.state != :error
|
25
|
-
return cursor_index[flow_step.id].what_is_next(flow_step, success)
|
26
|
-
end
|
27
|
-
|
28
|
-
# @return [Set] of steps to continue with
|
29
|
-
def start
|
30
|
-
return @cursor.what_is_next.tap do |steps|
|
31
|
-
raise 'invalid state' if steps.empty? && !done?
|
32
|
-
end
|
33
|
-
end
|
34
|
-
|
35
|
-
private
|
36
|
-
|
37
|
-
def build_root_cursor
|
38
|
-
# the root cursor has to always run against sequence
|
39
|
-
sequence = @flow.is_a?(Flows::Sequence) ? @flow : Flows::Sequence.new([@flow])
|
40
|
-
return SequenceCursor.new(self, sequence, nil)
|
41
|
-
end
|
42
|
-
end
|
43
|
-
end
|
44
|
-
end
|
45
|
-
end
|
@@ -1,81 +0,0 @@
|
|
1
|
-
module Dynflow
|
2
|
-
module Executors
|
3
|
-
class Parallel < Abstract
|
4
|
-
|
5
|
-
# Handles the events generated while running actions, makes sure
|
6
|
-
# the events are sent to the action only when in suspended state
|
7
|
-
class RunningStepsManager
|
8
|
-
include Algebrick::TypeCheck
|
9
|
-
|
10
|
-
def initialize(world)
|
11
|
-
@world = Type! world, World
|
12
|
-
@running_steps = {}
|
13
|
-
@events = WorkQueue.new(Integer, Work)
|
14
|
-
end
|
15
|
-
|
16
|
-
def terminate
|
17
|
-
pending_work = @events.clear.values.flatten(1)
|
18
|
-
pending_work.each do |w|
|
19
|
-
if Work::Event === w
|
20
|
-
w.event.result.fail UnprocessableEvent.new("dropping due to termination")
|
21
|
-
end
|
22
|
-
end
|
23
|
-
end
|
24
|
-
|
25
|
-
def add(step, work)
|
26
|
-
Type! step, ExecutionPlan::Steps::RunStep
|
27
|
-
@running_steps[step.id] = step
|
28
|
-
# we make sure not to run any event when the step is still being executed
|
29
|
-
@events.push(step.id, work)
|
30
|
-
self
|
31
|
-
end
|
32
|
-
|
33
|
-
# @returns [Work, nil]
|
34
|
-
def done(step)
|
35
|
-
Type! step, ExecutionPlan::Steps::RunStep
|
36
|
-
@events.shift(step.id).tap do |work|
|
37
|
-
work.event.result.success true if Work::Event === work
|
38
|
-
end
|
39
|
-
|
40
|
-
if step.state == :suspended
|
41
|
-
return true, @events.first(step.id)
|
42
|
-
else
|
43
|
-
while (event = @events.shift(step.id))
|
44
|
-
message = "step #{step.execution_plan_id}:#{step.id} dropping event #{event.event}"
|
45
|
-
@world.logger.warn message
|
46
|
-
event.event.result.fail UnprocessableEvent.new(message).
|
47
|
-
tap { |e| e.set_backtrace(caller) }
|
48
|
-
end
|
49
|
-
raise 'assert' unless @events.empty?(step.id)
|
50
|
-
@running_steps.delete(step.id)
|
51
|
-
return false, nil
|
52
|
-
end
|
53
|
-
end
|
54
|
-
|
55
|
-
def try_to_terminate
|
56
|
-
@running_steps.delete_if do |_, step|
|
57
|
-
step.state != :running
|
58
|
-
end
|
59
|
-
return @running_steps.empty?
|
60
|
-
end
|
61
|
-
|
62
|
-
# @returns [Work, nil]
|
63
|
-
def event(event)
|
64
|
-
Type! event, Parallel::Event
|
65
|
-
|
66
|
-
step = @running_steps[event.step_id]
|
67
|
-
unless step
|
68
|
-
event.result.fail UnprocessableEvent.new(
|
69
|
-
'step is not suspended, it cannot process events')
|
70
|
-
return nil
|
71
|
-
end
|
72
|
-
|
73
|
-
can_run_event = @events.empty?(step.id)
|
74
|
-
work = Work::Event[step, event.execution_plan_id, event]
|
75
|
-
@events.push(step.id, work)
|
76
|
-
work if can_run_event
|
77
|
-
end
|
78
|
-
end
|
79
|
-
end
|
80
|
-
end
|
81
|
-
end
|