dynflow 0.1.0 → 0.2.0
Sign up to get free protection for your applications and to get access to all the features.
- data/.gitignore +6 -0
- data/.travis.yml +9 -0
- data/Gemfile +0 -10
- data/MIT-LICENSE +1 -1
- data/README.md +99 -37
- data/Rakefile +2 -6
- data/doc/images/logo.png +0 -0
- data/dynflow.gemspec +10 -1
- data/examples/generate_work_for_daemon.rb +24 -0
- data/examples/orchestrate.rb +121 -0
- data/examples/run_daemon.rb +17 -0
- data/examples/web_console.rb +29 -0
- data/lib/dynflow.rb +27 -6
- data/lib/dynflow/action.rb +185 -77
- data/lib/dynflow/action/cancellable_polling.rb +18 -0
- data/lib/dynflow/action/finalize_phase.rb +18 -0
- data/lib/dynflow/action/flow_phase.rb +44 -0
- data/lib/dynflow/action/format.rb +46 -0
- data/lib/dynflow/action/missing.rb +26 -0
- data/lib/dynflow/action/plan_phase.rb +85 -0
- data/lib/dynflow/action/polling.rb +49 -0
- data/lib/dynflow/action/presenter.rb +51 -0
- data/lib/dynflow/action/progress.rb +62 -0
- data/lib/dynflow/action/run_phase.rb +43 -0
- data/lib/dynflow/action/suspended.rb +21 -0
- data/lib/dynflow/clock.rb +133 -0
- data/lib/dynflow/daemon.rb +29 -0
- data/lib/dynflow/execution_plan.rb +285 -33
- data/lib/dynflow/execution_plan/dependency_graph.rb +29 -0
- data/lib/dynflow/execution_plan/output_reference.rb +52 -0
- data/lib/dynflow/execution_plan/steps.rb +12 -0
- data/lib/dynflow/execution_plan/steps/abstract.rb +121 -0
- data/lib/dynflow/execution_plan/steps/abstract_flow_step.rb +52 -0
- data/lib/dynflow/execution_plan/steps/error.rb +33 -0
- data/lib/dynflow/execution_plan/steps/finalize_step.rb +23 -0
- data/lib/dynflow/execution_plan/steps/plan_step.rb +81 -0
- data/lib/dynflow/execution_plan/steps/run_step.rb +21 -0
- data/lib/dynflow/executors.rb +9 -0
- data/lib/dynflow/executors/abstract.rb +32 -0
- data/lib/dynflow/executors/parallel.rb +88 -0
- data/lib/dynflow/executors/parallel/core.rb +119 -0
- data/lib/dynflow/executors/parallel/execution_plan_manager.rb +120 -0
- data/lib/dynflow/executors/parallel/flow_manager.rb +48 -0
- data/lib/dynflow/executors/parallel/pool.rb +102 -0
- data/lib/dynflow/executors/parallel/running_steps_manager.rb +63 -0
- data/lib/dynflow/executors/parallel/sequence_cursor.rb +97 -0
- data/lib/dynflow/executors/parallel/sequential_manager.rb +81 -0
- data/lib/dynflow/executors/parallel/work_queue.rb +44 -0
- data/lib/dynflow/executors/parallel/worker.rb +30 -0
- data/lib/dynflow/executors/remote_via_socket.rb +38 -0
- data/lib/dynflow/executors/remote_via_socket/core.rb +150 -0
- data/lib/dynflow/flows.rb +13 -0
- data/lib/dynflow/flows/abstract.rb +36 -0
- data/lib/dynflow/flows/abstract_composed.rb +104 -0
- data/lib/dynflow/flows/atom.rb +36 -0
- data/lib/dynflow/flows/concurrence.rb +28 -0
- data/lib/dynflow/flows/sequence.rb +13 -0
- data/lib/dynflow/future.rb +173 -0
- data/lib/dynflow/listeners.rb +7 -0
- data/lib/dynflow/listeners/abstract.rb +13 -0
- data/lib/dynflow/listeners/serialization.rb +41 -0
- data/lib/dynflow/listeners/socket.rb +88 -0
- data/lib/dynflow/logger_adapters.rb +8 -0
- data/lib/dynflow/logger_adapters/abstract.rb +30 -0
- data/lib/dynflow/logger_adapters/delegator.rb +13 -0
- data/lib/dynflow/logger_adapters/formatters.rb +8 -0
- data/lib/dynflow/logger_adapters/formatters/abstract.rb +33 -0
- data/lib/dynflow/logger_adapters/formatters/exception.rb +15 -0
- data/lib/dynflow/logger_adapters/simple.rb +59 -0
- data/lib/dynflow/micro_actor.rb +102 -0
- data/lib/dynflow/persistence.rb +53 -0
- data/lib/dynflow/persistence_adapters.rb +6 -0
- data/lib/dynflow/persistence_adapters/abstract.rb +56 -0
- data/lib/dynflow/persistence_adapters/sequel.rb +160 -0
- data/lib/dynflow/persistence_adapters/sequel_migrations/001_initial.rb +52 -0
- data/lib/dynflow/serializable.rb +66 -0
- data/lib/dynflow/simple_world.rb +18 -0
- data/lib/dynflow/stateful.rb +40 -0
- data/lib/dynflow/testing.rb +32 -0
- data/lib/dynflow/testing/assertions.rb +64 -0
- data/lib/dynflow/testing/dummy_execution_plan.rb +40 -0
- data/lib/dynflow/testing/dummy_executor.rb +29 -0
- data/lib/dynflow/testing/dummy_planned_action.rb +18 -0
- data/lib/dynflow/testing/dummy_step.rb +19 -0
- data/lib/dynflow/testing/dummy_world.rb +33 -0
- data/lib/dynflow/testing/factories.rb +83 -0
- data/lib/dynflow/testing/managed_clock.rb +23 -0
- data/lib/dynflow/testing/mimic.rb +38 -0
- data/lib/dynflow/transaction_adapters.rb +9 -0
- data/lib/dynflow/transaction_adapters/abstract.rb +26 -0
- data/lib/dynflow/transaction_adapters/active_record.rb +27 -0
- data/lib/dynflow/transaction_adapters/none.rb +12 -0
- data/lib/dynflow/version.rb +1 -1
- data/lib/dynflow/web_console.rb +277 -0
- data/lib/dynflow/world.rb +168 -0
- data/test/action_test.rb +89 -11
- data/test/clock_test.rb +59 -0
- data/test/code_workflow_example.rb +382 -0
- data/test/execution_plan_test.rb +195 -64
- data/test/executor_test.rb +692 -0
- data/test/persistance_adapters_test.rb +173 -0
- data/test/test_helper.rb +316 -1
- data/test/testing_test.rb +148 -0
- data/test/web_console_test.rb +38 -0
- data/web/assets/javascripts/application.js +25 -0
- data/web/assets/stylesheets/application.css +101 -0
- data/web/assets/vendor/bootstrap/css/bootstrap-responsive.css +1109 -0
- data/web/assets/vendor/bootstrap/css/bootstrap-responsive.min.css +9 -0
- data/web/assets/vendor/bootstrap/css/bootstrap.css +6167 -0
- data/web/assets/vendor/bootstrap/css/bootstrap.min.css +9 -0
- data/web/assets/vendor/bootstrap/img/glyphicons-halflings-white.png +0 -0
- data/web/assets/vendor/bootstrap/img/glyphicons-halflings.png +0 -0
- data/web/assets/vendor/bootstrap/js/bootstrap.js +2280 -0
- data/web/assets/vendor/bootstrap/js/bootstrap.min.js +6 -0
- data/web/assets/vendor/google-code-prettify/lang-basic.js +3 -0
- data/web/assets/vendor/google-code-prettify/prettify.css +1 -0
- data/web/assets/vendor/google-code-prettify/prettify.js +30 -0
- data/web/assets/vendor/google-code-prettify/run_prettify.js +34 -0
- data/web/assets/vendor/jquery/jquery.js +9807 -0
- data/web/views/flow.erb +19 -0
- data/web/views/flow_step.erb +31 -0
- data/web/views/index.erb +39 -0
- data/web/views/layout.erb +20 -0
- data/web/views/plan_step.erb +11 -0
- data/web/views/show.erb +54 -0
- metadata +250 -11
- data/examples/events.rb +0 -71
- data/examples/workflow.rb +0 -140
- data/lib/dynflow/bus.rb +0 -168
- data/lib/dynflow/dispatcher.rb +0 -36
- data/lib/dynflow/logger.rb +0 -34
- data/lib/dynflow/step.rb +0 -234
- data/test/bus_test.rb +0 -150
@@ -0,0 +1,119 @@
|
|
1
|
+
module Dynflow
|
2
|
+
module Executors
|
3
|
+
class Parallel < Abstract
|
4
|
+
|
5
|
+
# TODO add dynflow error handling to avoid getting stuck and report errors to the future
|
6
|
+
class Core < MicroActor
|
7
|
+
def initialize(world, pool_size)
|
8
|
+
super(world.logger, world, pool_size)
|
9
|
+
end
|
10
|
+
|
11
|
+
private
|
12
|
+
|
13
|
+
def delayed_initialize(world, pool_size)
|
14
|
+
@world = Type! world, World
|
15
|
+
@pool = Pool.new(self, pool_size, world.transaction_adapter)
|
16
|
+
@execution_plan_managers = {}
|
17
|
+
end
|
18
|
+
|
19
|
+
def on_message(message)
|
20
|
+
match message,
|
21
|
+
(on ~Execution do |(execution_plan_id, finished)|
|
22
|
+
start_executing track_execution_plan(execution_plan_id, finished)
|
23
|
+
true
|
24
|
+
end),
|
25
|
+
(on ~Event do |event|
|
26
|
+
event(event)
|
27
|
+
end),
|
28
|
+
(on PoolDone.(~any) do |step|
|
29
|
+
update_manager(step)
|
30
|
+
end)
|
31
|
+
end
|
32
|
+
|
33
|
+
def termination
|
34
|
+
logger.info 'shutting down Core ...'
|
35
|
+
try_to_terminate
|
36
|
+
end
|
37
|
+
|
38
|
+
# @return false on problem
|
39
|
+
def track_execution_plan(execution_plan_id, finished)
|
40
|
+
execution_plan = @world.persistence.load_execution_plan(execution_plan_id)
|
41
|
+
|
42
|
+
if terminating?
|
43
|
+
raise Dynflow::Error, "cannot accept execution_plan_id:#{execution_plan_id} core is terminating"
|
44
|
+
end
|
45
|
+
|
46
|
+
if @execution_plan_managers[execution_plan_id]
|
47
|
+
raise Dynflow::Error, "cannot execute execution_plan_id:#{execution_plan_id} it's already running"
|
48
|
+
end
|
49
|
+
|
50
|
+
if execution_plan.state == :stopped
|
51
|
+
raise Dynflow::Error, "cannot execute execution_plan_id:#{execution_plan_id} it's stopped"
|
52
|
+
end
|
53
|
+
|
54
|
+
@execution_plan_managers[execution_plan_id] =
|
55
|
+
ExecutionPlanManager.new(@world, execution_plan, finished)
|
56
|
+
|
57
|
+
rescue Dynflow::Error => e
|
58
|
+
finished.fail e
|
59
|
+
raise e
|
60
|
+
end
|
61
|
+
|
62
|
+
def start_executing(manager)
|
63
|
+
Type! manager, ExecutionPlanManager
|
64
|
+
|
65
|
+
next_work = manager.start
|
66
|
+
continue_manager manager, next_work
|
67
|
+
end
|
68
|
+
|
69
|
+
def update_manager(finished_work)
|
70
|
+
manager = @execution_plan_managers[finished_work.execution_plan_id]
|
71
|
+
next_work = manager.what_is_next(finished_work)
|
72
|
+
continue_manager manager, next_work
|
73
|
+
end
|
74
|
+
|
75
|
+
def continue_manager(manager, next_work)
|
76
|
+
if manager.done?
|
77
|
+
loose_manager_and_set_future manager.execution_plan.id
|
78
|
+
try_to_terminate
|
79
|
+
else
|
80
|
+
feed_pool next_work
|
81
|
+
end
|
82
|
+
end
|
83
|
+
|
84
|
+
def feed_pool(work_items)
|
85
|
+
Type! work_items, Array, Work, NilClass
|
86
|
+
return if work_items.nil?
|
87
|
+
work_items = [work_items] if work_items.is_a? Work
|
88
|
+
work_items.all? { |i| Type! i, Work }
|
89
|
+
work_items.each { |new_work| @pool << new_work }
|
90
|
+
end
|
91
|
+
|
92
|
+
def loose_manager_and_set_future(execution_plan_id)
|
93
|
+
manager = @execution_plan_managers.delete(execution_plan_id)
|
94
|
+
manager.future.resolve manager.execution_plan
|
95
|
+
end
|
96
|
+
|
97
|
+
def event(event)
|
98
|
+
Type! event, Event
|
99
|
+
execution_plan_manager = @execution_plan_managers[event.execution_plan_id]
|
100
|
+
if execution_plan_manager
|
101
|
+
feed_pool execution_plan_manager.event(event)
|
102
|
+
true
|
103
|
+
else
|
104
|
+
logger.warn "dropping event #{event} - no manager for #{event.execution_plan_id}:#{event.step_id}"
|
105
|
+
event.result.fail UnprocessableEvent.new("no manager for #{event.execution_plan_id}:#{event.step_id}")
|
106
|
+
end
|
107
|
+
end
|
108
|
+
|
109
|
+
def try_to_terminate
|
110
|
+
if terminating? && @execution_plan_managers.empty?
|
111
|
+
@pool.ask(Terminate).wait
|
112
|
+
logger.info '... Core terminated.'
|
113
|
+
terminate!
|
114
|
+
end
|
115
|
+
end
|
116
|
+
end
|
117
|
+
end
|
118
|
+
end
|
119
|
+
end
|
@@ -0,0 +1,120 @@
|
|
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, 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.update_state(:running)
|
20
|
+
end
|
21
|
+
|
22
|
+
def start
|
23
|
+
raise "The future was already set" if @future.ready?
|
24
|
+
start_run or start_finalize or finish
|
25
|
+
end
|
26
|
+
|
27
|
+
def prepare_next_step(step)
|
28
|
+
Work::Step[step, execution_plan.id].tap do |work|
|
29
|
+
@running_steps_manager.add(step, work)
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
# @return [Array<Work>] of Work items to continue with
|
34
|
+
def what_is_next(work)
|
35
|
+
Type! work, Work
|
36
|
+
|
37
|
+
compute_next_from_step =-> step do
|
38
|
+
raise unless @run_manager
|
39
|
+
raise if @run_manager.done?
|
40
|
+
|
41
|
+
next_steps = @run_manager.what_is_next(step)
|
42
|
+
if @run_manager.done?
|
43
|
+
start_finalize or finish
|
44
|
+
else
|
45
|
+
next_steps.map { |s| prepare_next_step(s) }
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
match work,
|
50
|
+
|
51
|
+
Work::Step.(step: ~any) >-> step do
|
52
|
+
suspended, work = @running_steps_manager.done(step)
|
53
|
+
if suspended
|
54
|
+
raise 'assert' unless compute_next_from_step.call(step).empty?
|
55
|
+
work
|
56
|
+
else
|
57
|
+
execution_plan.update_execution_time step.execution_time
|
58
|
+
compute_next_from_step.call step
|
59
|
+
end
|
60
|
+
end,
|
61
|
+
|
62
|
+
Work::Event.(step: ~any) >-> step do
|
63
|
+
suspended, work = @running_steps_manager.done(step)
|
64
|
+
|
65
|
+
if suspended
|
66
|
+
work
|
67
|
+
else
|
68
|
+
execution_plan.update_execution_time step.execution_time
|
69
|
+
compute_next_from_step.call step
|
70
|
+
end
|
71
|
+
end,
|
72
|
+
|
73
|
+
Work::Finalize >-> do
|
74
|
+
raise unless @finalize_manager
|
75
|
+
finish
|
76
|
+
end
|
77
|
+
end
|
78
|
+
|
79
|
+
def event(event)
|
80
|
+
Type! event, Event
|
81
|
+
raise unless event.execution_plan_id == @execution_plan.id
|
82
|
+
@running_steps_manager.event(event)
|
83
|
+
end
|
84
|
+
|
85
|
+
def done?
|
86
|
+
(!@run_manager || @run_manager.done?) && (!@finalize_manager || @finalize_manager.done?)
|
87
|
+
end
|
88
|
+
|
89
|
+
private
|
90
|
+
|
91
|
+
def no_work
|
92
|
+
raise "No work but not done" unless done?
|
93
|
+
[]
|
94
|
+
end
|
95
|
+
|
96
|
+
def start_run
|
97
|
+
unless execution_plan.run_flow.empty?
|
98
|
+
raise 'run phase already started' if @run_manager
|
99
|
+
@run_manager = FlowManager.new(execution_plan, execution_plan.run_flow)
|
100
|
+
@run_manager.start.map { |s| prepare_next_step(s) }.tap { |a| raise if a.empty? }
|
101
|
+
end
|
102
|
+
end
|
103
|
+
|
104
|
+
def start_finalize
|
105
|
+
unless execution_plan.finalize_flow.empty?
|
106
|
+
raise 'finalize phase already started' if @finalize_manager
|
107
|
+
@finalize_manager = SequentialManager.new(@world, execution_plan)
|
108
|
+
Work::Finalize[@finalize_manager, execution_plan.id]
|
109
|
+
end
|
110
|
+
end
|
111
|
+
|
112
|
+
def finish
|
113
|
+
@execution_plan.update_state(execution_plan.error? ? :paused : :stopped)
|
114
|
+
return no_work
|
115
|
+
end
|
116
|
+
|
117
|
+
end
|
118
|
+
end
|
119
|
+
end
|
120
|
+
end
|
@@ -0,0 +1,48 @@
|
|
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
|
+
execution_plan.steps[flow_step.id] = flow_step
|
23
|
+
# TODO can be probably disabled to improve performance, execution time will not be updated, maybe more - check
|
24
|
+
execution_plan.save
|
25
|
+
return [] if flow_step.state == :suspended
|
26
|
+
|
27
|
+
success = flow_step.state != :error
|
28
|
+
return cursor_index[flow_step.id].what_is_next(flow_step, success)
|
29
|
+
end
|
30
|
+
|
31
|
+
# @return [Set] of steps to continue with
|
32
|
+
def start
|
33
|
+
return @cursor.what_is_next.tap do |steps|
|
34
|
+
raise 'invalid state' if steps.empty? && !done?
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
private
|
39
|
+
|
40
|
+
def build_root_cursor
|
41
|
+
# the root cursor has to always run against sequence
|
42
|
+
sequence = @flow.is_a?(Flows::Sequence) ? @flow : Flows::Sequence.new([@flow])
|
43
|
+
return SequenceCursor.new(self, sequence, nil)
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
@@ -0,0 +1,102 @@
|
|
1
|
+
module Dynflow
|
2
|
+
module Executors
|
3
|
+
class Parallel < Abstract
|
4
|
+
class Pool < MicroActor
|
5
|
+
class RoundRobin
|
6
|
+
def initialize
|
7
|
+
@data = []
|
8
|
+
@cursor = 0
|
9
|
+
end
|
10
|
+
|
11
|
+
def add(item)
|
12
|
+
@data.push item
|
13
|
+
self
|
14
|
+
end
|
15
|
+
|
16
|
+
def delete(item)
|
17
|
+
@data.delete item
|
18
|
+
self
|
19
|
+
end
|
20
|
+
|
21
|
+
def next
|
22
|
+
@cursor = 0 if @cursor > @data.size-1
|
23
|
+
@data[@cursor].tap { @cursor += 1 }
|
24
|
+
end
|
25
|
+
|
26
|
+
def empty?
|
27
|
+
@data.empty?
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
class JobStorage
|
32
|
+
def initialize
|
33
|
+
@round_robin = RoundRobin.new
|
34
|
+
@jobs = Hash.new { |h, k| h[k] = [] }
|
35
|
+
end
|
36
|
+
|
37
|
+
def add(work)
|
38
|
+
@round_robin.add work.execution_plan_id unless tracked?(work)
|
39
|
+
@jobs[work.execution_plan_id] << work
|
40
|
+
end
|
41
|
+
|
42
|
+
def pop
|
43
|
+
return nil if empty?
|
44
|
+
execution_plan_id = @round_robin.next
|
45
|
+
@jobs[execution_plan_id].shift.tap { delete execution_plan_id if @jobs[execution_plan_id].empty? }
|
46
|
+
end
|
47
|
+
|
48
|
+
def empty?
|
49
|
+
@jobs.empty?
|
50
|
+
end
|
51
|
+
|
52
|
+
private
|
53
|
+
|
54
|
+
def tracked?(work)
|
55
|
+
@jobs.has_key? work.execution_plan_id
|
56
|
+
end
|
57
|
+
|
58
|
+
def delete(execution_plan_id)
|
59
|
+
@round_robin.delete execution_plan_id
|
60
|
+
@jobs.delete execution_plan_id
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
def initialize(core, pool_size, transaction_adapter)
|
65
|
+
super(core.logger, core, pool_size, transaction_adapter)
|
66
|
+
end
|
67
|
+
|
68
|
+
private
|
69
|
+
|
70
|
+
def delayed_initialize(core, pool_size, transaction_adapter)
|
71
|
+
@core = core
|
72
|
+
@pool_size = pool_size
|
73
|
+
@free_workers = Array.new(pool_size) { Worker.new(self, transaction_adapter) }
|
74
|
+
@jobs = JobStorage.new
|
75
|
+
end
|
76
|
+
|
77
|
+
def on_message(message)
|
78
|
+
match message,
|
79
|
+
~Work >-> work do
|
80
|
+
@jobs.add work
|
81
|
+
distribute_jobs
|
82
|
+
end,
|
83
|
+
WorkerDone.(~any, ~any) >-> step, worker do
|
84
|
+
@core << PoolDone[step]
|
85
|
+
@free_workers << worker
|
86
|
+
distribute_jobs
|
87
|
+
end
|
88
|
+
end
|
89
|
+
|
90
|
+
def termination
|
91
|
+
raise unless @free_workers.size == @pool_size
|
92
|
+
@free_workers.map { |worker| worker.ask(Terminate) }.each(&:wait)
|
93
|
+
super
|
94
|
+
end
|
95
|
+
|
96
|
+
def distribute_jobs
|
97
|
+
@free_workers.pop << @jobs.pop until @free_workers.empty? || @jobs.empty?
|
98
|
+
end
|
99
|
+
end
|
100
|
+
end
|
101
|
+
end
|
102
|
+
end
|
@@ -0,0 +1,63 @@
|
|
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 add(step, work)
|
17
|
+
Type! step, ExecutionPlan::Steps::RunStep
|
18
|
+
@running_steps[step.id] = step
|
19
|
+
# we make sure not to run any event when the step is still being executed
|
20
|
+
@events.push(step.id, work)
|
21
|
+
self
|
22
|
+
end
|
23
|
+
|
24
|
+
# @returns [Work, nil]
|
25
|
+
def done(step)
|
26
|
+
Type! step, ExecutionPlan::Steps::RunStep
|
27
|
+
@events.shift(step.id).tap do |work|
|
28
|
+
work.event.result.resolve true if Work::Event === work
|
29
|
+
end
|
30
|
+
|
31
|
+
if step.state == :suspended
|
32
|
+
return true, @events.first(step.id)
|
33
|
+
else
|
34
|
+
while (event = @events.shift(step.id))
|
35
|
+
message = "step #{step.execution_plan_id}:#{step.id} dropping event #{event.event}"
|
36
|
+
@world.logger.warn message
|
37
|
+
event.event.result.fail UnprocessableEvent.new(message).tap { |e| e.set_backtrace(caller) }
|
38
|
+
end
|
39
|
+
raise 'assert' unless @events.empty?(step.id)
|
40
|
+
@running_steps.delete(step.id)
|
41
|
+
return false, nil
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
# @returns [Work, nil]
|
46
|
+
def event(event)
|
47
|
+
Type! event, Event
|
48
|
+
|
49
|
+
step = @running_steps[event.step_id]
|
50
|
+
unless step
|
51
|
+
event.result.fail UnprocessableEvent.new('step is not suspended, it cannot process events')
|
52
|
+
return nil
|
53
|
+
end
|
54
|
+
|
55
|
+
can_run_event = @events.empty?(step.id)
|
56
|
+
work = Work::Event[step, event.execution_plan_id, event]
|
57
|
+
@events.push(step.id, work)
|
58
|
+
work if can_run_event
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end
|