dynflow 0.1.0 → 0.2.0
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 +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
|