dynflow 0.8.16 → 0.8.17
Sign up to get free protection for your applications and to get access to all the features.
- 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
|