dynflow 1.1.6 → 1.2.0.pre1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/Gemfile +1 -1
- data/dynflow.gemspec +2 -2
- data/examples/clock_benchmark.rb +35 -0
- data/examples/memory_limit_watcher.rb +1 -1
- data/lib/dynflow/action.rb +2 -2
- data/lib/dynflow/action/suspended.rb +1 -1
- data/lib/dynflow/action/with_sub_plans.rb +1 -1
- data/lib/dynflow/actor.rb +2 -2
- data/lib/dynflow/actors/execution_plan_cleaner.rb +1 -1
- data/lib/dynflow/clock.rb +11 -8
- data/lib/dynflow/delayed_executors/abstract.rb +1 -1
- data/lib/dynflow/delayed_plan.rb +1 -1
- data/lib/dynflow/director.rb +4 -4
- data/lib/dynflow/director/execution_plan_manager.rb +2 -2
- data/lib/dynflow/director/running_steps_manager.rb +4 -4
- data/lib/dynflow/dispatcher/client_dispatcher.rb +13 -12
- data/lib/dynflow/dispatcher/executor_dispatcher.rb +5 -5
- data/lib/dynflow/execution_plan.rb +1 -1
- data/lib/dynflow/execution_plan/steps/plan_step.rb +4 -2
- data/lib/dynflow/executors/abstract.rb +6 -6
- data/lib/dynflow/executors/parallel.rb +6 -6
- data/lib/dynflow/executors/parallel/core.rb +1 -1
- data/lib/dynflow/rails/daemon.rb +1 -1
- data/lib/dynflow/testing/dummy_executor.rb +2 -2
- data/lib/dynflow/testing/dummy_world.rb +1 -1
- data/lib/dynflow/testing/in_thread_executor.rb +5 -5
- data/lib/dynflow/testing/in_thread_world.rb +6 -6
- data/lib/dynflow/throttle_limiter.rb +5 -5
- data/lib/dynflow/utils.rb +3 -140
- data/lib/dynflow/utils/indifferent_hash.rb +143 -0
- data/lib/dynflow/utils/priority_queue.rb +64 -0
- data/lib/dynflow/version.rb +1 -1
- data/lib/dynflow/world.rb +22 -22
- data/lib/dynflow/world/invalidation.rb +1 -1
- data/test/batch_sub_tasks_test.rb +4 -4
- data/test/concurrency_control_test.rb +6 -6
- data/test/daemon_test.rb +2 -2
- data/test/dispatcher_test.rb +6 -6
- data/test/execution_plan_test.rb +11 -0
- data/test/executor_test.rb +1 -1
- data/test/support/dummy_example.rb +1 -1
- data/test/test_helper.rb +17 -17
- data/test/utils_test.rb +56 -0
- data/test/world_test.rb +2 -2
- metadata +14 -9
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 6fcc2f12b17318ec09919aae5873ee7aa9e16ab3b6f96508b94c0cf8ae3d0a91
|
4
|
+
data.tar.gz: 9a71c09beb7ed1b0cf75ca858dfacc41ae4e33f5c2b82af142f1aac809b2b8f9
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: e48b378aacfd2a9cdd2c57a3759abe33ba22439615af459a0c779ddf448cfab5b7cf76de655d2450c2190e444fc2586578e86af465488bb0c622e7c94fd96cdb
|
7
|
+
data.tar.gz: 0d99ae6fb8f1d68d503610e49f69e1ce344129d0588e2ebe9a57bf3d540790d8f68229f0fa50b7aa1305d539f902c21dbf57ff96871ddaffadf37e56cbcf0924
|
data/Gemfile
CHANGED
data/dynflow.gemspec
CHANGED
@@ -21,8 +21,8 @@ Gem::Specification.new do |s|
|
|
21
21
|
s.add_dependency "multi_json"
|
22
22
|
s.add_dependency "apipie-params"
|
23
23
|
s.add_dependency "algebrick", '~> 0.7.0'
|
24
|
-
s.add_dependency "concurrent-ruby", '~> 1.
|
25
|
-
s.add_dependency "concurrent-ruby-edge", '~> 0.
|
24
|
+
s.add_dependency "concurrent-ruby", '~> 1.1.3'
|
25
|
+
s.add_dependency "concurrent-ruby-edge", '~> 0.4.1'
|
26
26
|
s.add_dependency "sequel", '>= 4.0.0'
|
27
27
|
|
28
28
|
s.add_development_dependency "rake"
|
@@ -0,0 +1,35 @@
|
|
1
|
+
require 'dynflow'
|
2
|
+
require 'benchmark'
|
3
|
+
|
4
|
+
class Receiver
|
5
|
+
def initialize(limit, future)
|
6
|
+
@limit = limit
|
7
|
+
@future = future
|
8
|
+
@counter = 0
|
9
|
+
end
|
10
|
+
|
11
|
+
def null
|
12
|
+
@counter += 1
|
13
|
+
@future.fulfill(true) if @counter >= @limit
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
def test_case(count)
|
18
|
+
future = Concurrent::Promises.resolvable_future
|
19
|
+
clock = Dynflow::Clock.spawn(:name => 'clock')
|
20
|
+
receiver = Receiver.new(count, future)
|
21
|
+
|
22
|
+
count.times do
|
23
|
+
clock.ping(receiver, 0, nil, :null)
|
24
|
+
end
|
25
|
+
future.wait
|
26
|
+
end
|
27
|
+
|
28
|
+
Benchmark.bm do |bm|
|
29
|
+
bm.report(' 100') { test_case 100 }
|
30
|
+
bm.report(' 1000') { test_case 1_000 }
|
31
|
+
bm.report(' 5000') { test_case 5_000 }
|
32
|
+
bm.report(' 10000') { test_case 10_000 }
|
33
|
+
bm.report(' 50000') { test_case 50_000 }
|
34
|
+
bm.report('100000') { test_case 100_000 }
|
35
|
+
end
|
data/lib/dynflow/action.rb
CHANGED
@@ -161,11 +161,11 @@ module Dynflow
|
|
161
161
|
end
|
162
162
|
|
163
163
|
def caller_action
|
164
|
-
|
164
|
+
phase! Present
|
165
165
|
return nil if @caller_action_id
|
166
166
|
return @caller_action if @caller_action
|
167
167
|
|
168
|
-
caller_execution_plan = if @caller_execution_plan_id
|
168
|
+
caller_execution_plan = if @caller_execution_plan_id.nil?
|
169
169
|
execution_plan
|
170
170
|
else
|
171
171
|
world.persistence.load_execution_plan(@caller_execution_plan_id)
|
@@ -171,7 +171,7 @@ module Dynflow
|
|
171
171
|
def notify_on_finish(plans)
|
172
172
|
suspend do |suspended_action|
|
173
173
|
plans.each do |plan|
|
174
|
-
plan.finished.
|
174
|
+
plan.finished.on_resolution! do |success, value|
|
175
175
|
suspended_action << SubPlanFinished[plan.id, success && (value.result == :success)]
|
176
176
|
end
|
177
177
|
end
|
data/lib/dynflow/actor.rb
CHANGED
@@ -20,7 +20,7 @@ module Dynflow
|
|
20
20
|
message, terminated_future = envelope
|
21
21
|
if :start_termination == message
|
22
22
|
context.start_termination(terminated_future)
|
23
|
-
envelope.future.
|
23
|
+
envelope.future.fulfill true if !envelope.future.nil?
|
24
24
|
Concurrent::Actor::Behaviour::MESSAGE_PROCESSED
|
25
25
|
else
|
26
26
|
pass envelope
|
@@ -35,7 +35,7 @@ module Dynflow
|
|
35
35
|
end
|
36
36
|
|
37
37
|
def finish_termination
|
38
|
-
@terminated.
|
38
|
+
@terminated.fulfill(true)
|
39
39
|
reference.tell(:terminate!)
|
40
40
|
end
|
41
41
|
|
@@ -13,7 +13,7 @@ module Dynflow
|
|
13
13
|
end
|
14
14
|
|
15
15
|
def spawn
|
16
|
-
Concurrent.
|
16
|
+
Concurrent::Promises.resolvable_future.tap do |initialized|
|
17
17
|
@core = core_class.spawn(:name => 'execution-plan-cleaner',
|
18
18
|
:args => [@world, @options],
|
19
19
|
:initialized => initialized)
|
data/lib/dynflow/clock.rb
CHANGED
@@ -1,6 +1,4 @@
|
|
1
1
|
module Dynflow
|
2
|
-
require 'set'
|
3
|
-
|
4
2
|
class Clock < Actor
|
5
3
|
|
6
4
|
include Algebrick::Types
|
@@ -48,8 +46,9 @@ module Dynflow
|
|
48
46
|
Pill = type { fields Float }
|
49
47
|
end
|
50
48
|
|
51
|
-
def initialize
|
52
|
-
@
|
49
|
+
def initialize(logger = nil)
|
50
|
+
@logger = logger
|
51
|
+
@timers = Utils::PriorityQueue.new { |a, b| b <=> a }
|
53
52
|
@sleeping_pill = None
|
54
53
|
@sleep_barrier = Mutex.new
|
55
54
|
@sleeper = Thread.new { sleeping }
|
@@ -72,7 +71,7 @@ module Dynflow
|
|
72
71
|
end
|
73
72
|
|
74
73
|
def add_timer(timer)
|
75
|
-
@timers.
|
74
|
+
@timers.push timer
|
76
75
|
if @timers.size == 1
|
77
76
|
sleep_to timer
|
78
77
|
else
|
@@ -84,13 +83,17 @@ module Dynflow
|
|
84
83
|
|
85
84
|
def run_ready_timers
|
86
85
|
while first_timer && first_timer.when <= Time.now
|
87
|
-
|
88
|
-
|
86
|
+
begin
|
87
|
+
first_timer.apply
|
88
|
+
rescue => e
|
89
|
+
@logger && @logger.error("Failed to apply clock event #{first_timer}, exception: #{e}")
|
90
|
+
end
|
91
|
+
@timers.pop
|
89
92
|
end
|
90
93
|
end
|
91
94
|
|
92
95
|
def first_timer
|
93
|
-
@timers.
|
96
|
+
@timers.top
|
94
97
|
end
|
95
98
|
|
96
99
|
def wakeup
|
data/lib/dynflow/delayed_plan.rb
CHANGED
@@ -47,7 +47,7 @@ module Dynflow
|
|
47
47
|
return true
|
48
48
|
end
|
49
49
|
|
50
|
-
def execute(future = Concurrent.
|
50
|
+
def execute(future = Concurrent::Promises.resolvable_future)
|
51
51
|
@world.execute(@execution_plan_uuid, future)
|
52
52
|
::Dynflow::World::Triggered[@execution_plan_uuid, future]
|
53
53
|
end
|
data/lib/dynflow/director.rb
CHANGED
@@ -13,7 +13,7 @@ module Dynflow
|
|
13
13
|
fields! execution_plan_id: String,
|
14
14
|
step_id: Integer,
|
15
15
|
event: Object,
|
16
|
-
result: Concurrent::
|
16
|
+
result: Concurrent::Promises::ResolvableFuture
|
17
17
|
end
|
18
18
|
|
19
19
|
UnprocessableEvent = Class.new(Dynflow::Error)
|
@@ -103,7 +103,7 @@ module Dynflow
|
|
103
103
|
raise Dynflow::Error, "no manager for #{event.inspect}"
|
104
104
|
end
|
105
105
|
rescue Dynflow::Error => e
|
106
|
-
event.result.
|
106
|
+
event.result.reject e.message
|
107
107
|
raise e
|
108
108
|
end
|
109
109
|
|
@@ -203,13 +203,13 @@ module Dynflow
|
|
203
203
|
@execution_plan_managers[execution_plan_id] =
|
204
204
|
ExecutionPlanManager.new(@world, execution_plan, finished)
|
205
205
|
rescue Dynflow::Error => e
|
206
|
-
finished.
|
206
|
+
finished.reject e
|
207
207
|
nil
|
208
208
|
end
|
209
209
|
|
210
210
|
def set_future(manager)
|
211
211
|
@rescued_steps.delete(manager.execution_plan.id)
|
212
|
-
manager.future.
|
212
|
+
manager.future.fulfill manager.execution_plan
|
213
213
|
end
|
214
214
|
end
|
215
215
|
end
|
@@ -9,7 +9,7 @@ module Dynflow
|
|
9
9
|
def initialize(world, execution_plan, future)
|
10
10
|
@world = Type! world, World
|
11
11
|
@execution_plan = Type! execution_plan, ExecutionPlan
|
12
|
-
@future = Type! future, Concurrent::
|
12
|
+
@future = Type! future, Concurrent::Promises::ResolvableFuture
|
13
13
|
@running_steps_manager = RunningStepsManager.new(world)
|
14
14
|
|
15
15
|
unless [:planned, :paused].include? execution_plan.state
|
@@ -20,7 +20,7 @@ module Dynflow
|
|
20
20
|
end
|
21
21
|
|
22
22
|
def start
|
23
|
-
raise "The future was already set" if @future.
|
23
|
+
raise "The future was already set" if @future.resolved?
|
24
24
|
start_run or start_finalize or finish
|
25
25
|
end
|
26
26
|
|
@@ -15,7 +15,7 @@ module Dynflow
|
|
15
15
|
pending_work = @events.clear.values.flatten(1)
|
16
16
|
pending_work.each do |w|
|
17
17
|
if EventWorkItem === w
|
18
|
-
w.event.result.
|
18
|
+
w.event.result.reject UnprocessableEvent.new("dropping due to termination")
|
19
19
|
end
|
20
20
|
end
|
21
21
|
end
|
@@ -32,7 +32,7 @@ module Dynflow
|
|
32
32
|
def done(step)
|
33
33
|
Type! step, ExecutionPlan::Steps::RunStep
|
34
34
|
@events.shift(step.id).tap do |work|
|
35
|
-
work.event.result.
|
35
|
+
work.event.result.fulfill true if EventWorkItem === work
|
36
36
|
end
|
37
37
|
|
38
38
|
if step.state == :suspended
|
@@ -41,7 +41,7 @@ module Dynflow
|
|
41
41
|
while (event = @events.shift(step.id))
|
42
42
|
message = "step #{step.execution_plan_id}:#{step.id} dropping event #{event.event}"
|
43
43
|
@world.logger.warn message
|
44
|
-
event.event.result.
|
44
|
+
event.event.result.reject UnprocessableEvent.new(message).
|
45
45
|
tap { |e| e.set_backtrace(caller) }
|
46
46
|
end
|
47
47
|
raise 'assert' unless @events.empty?(step.id)
|
@@ -64,7 +64,7 @@ module Dynflow
|
|
64
64
|
|
65
65
|
step = @running_steps[event.step_id]
|
66
66
|
unless step
|
67
|
-
event.result.
|
67
|
+
event.result.reject UnprocessableEvent.new('step is not suspended, it cannot process events')
|
68
68
|
return next_work_items
|
69
69
|
end
|
70
70
|
|
@@ -4,24 +4,24 @@ module Dynflow
|
|
4
4
|
|
5
5
|
TrackedRequest = Algebrick.type do
|
6
6
|
fields! id: Integer, request: Request,
|
7
|
-
accepted: Concurrent::
|
7
|
+
accepted: Concurrent::Promises::ResolvableFuture, finished: Concurrent::Promises::ResolvableFuture
|
8
8
|
end
|
9
9
|
|
10
10
|
module TrackedRequest
|
11
11
|
def accept!
|
12
|
-
accepted.
|
12
|
+
accepted.fulfill true unless accepted.resolved?
|
13
13
|
self
|
14
14
|
end
|
15
15
|
|
16
16
|
def fail!(error)
|
17
|
-
accepted.
|
18
|
-
finished.
|
17
|
+
accepted.reject error unless accepted.resolved?
|
18
|
+
finished.reject error
|
19
19
|
self
|
20
20
|
end
|
21
21
|
|
22
22
|
def success!(resolve_to)
|
23
|
-
accepted.
|
24
|
-
finished.
|
23
|
+
accepted.fulfill true unless accepted.resolved?
|
24
|
+
finished.fulfill(resolve_to)
|
25
25
|
self
|
26
26
|
end
|
27
27
|
end
|
@@ -198,7 +198,7 @@ module Dynflow
|
|
198
198
|
|
199
199
|
def track_request(finished, request, timeout)
|
200
200
|
id = @last_id += 1
|
201
|
-
tracked_request = TrackedRequest[id, request, Concurrent.
|
201
|
+
tracked_request = TrackedRequest[id, request, Concurrent::Promises.resolvable_future, finished]
|
202
202
|
@tracked_requests[id] = tracked_request
|
203
203
|
@world.clock.ping(self, timeout, [:timeout, id]) if timeout
|
204
204
|
yield tracked_request
|
@@ -208,13 +208,14 @@ module Dynflow
|
|
208
208
|
end
|
209
209
|
|
210
210
|
def reset_tracked_request(tracked_request)
|
211
|
-
if tracked_request.finished.
|
211
|
+
if tracked_request.finished.resolved?
|
212
212
|
raise Dynflow::Error.new('Can not reset resolved tracked request')
|
213
213
|
end
|
214
|
-
unless tracked_request.accepted.
|
214
|
+
unless tracked_request.accepted.resolved?
|
215
215
|
tracked_request.accept! # otherwise nobody would set the accept future
|
216
216
|
end
|
217
|
-
|
217
|
+
future = Concurrent::Promises.resolvable_future
|
218
|
+
@tracked_requests[tracked_request.id] = TrackedRequest[tracked_request.id, tracked_request.request, future, tracked_request.finished]
|
218
219
|
end
|
219
220
|
|
220
221
|
def resolve_tracked_request(id, error = nil)
|
@@ -246,10 +247,10 @@ module Dynflow
|
|
246
247
|
return yield unless request.use_cache
|
247
248
|
|
248
249
|
if @ping_cache.fresh_record?(request.receiver_id)
|
249
|
-
future.
|
250
|
+
future.fulfill(true)
|
250
251
|
else
|
251
252
|
if @ping_cache.executor?(request.receiver_id)
|
252
|
-
future.
|
253
|
+
future.reject
|
253
254
|
else
|
254
255
|
yield
|
255
256
|
end
|
@@ -29,7 +29,7 @@ module Dynflow
|
|
29
29
|
@world.executor.execute(execution.execution_plan_id, future)
|
30
30
|
respond(envelope, Accepted)
|
31
31
|
rescue Dynflow::Error => e
|
32
|
-
future.
|
32
|
+
future.reject(e) if future && !future.resolved?
|
33
33
|
respond(envelope, Failed[e.message])
|
34
34
|
end
|
35
35
|
|
@@ -52,7 +52,7 @@ module Dynflow
|
|
52
52
|
end
|
53
53
|
@world.executor.event(event_request.execution_plan_id, event_request.step_id, event_request.event, future)
|
54
54
|
rescue Dynflow::Error => e
|
55
|
-
future.
|
55
|
+
future.reject(e) if future && !future.resolved?
|
56
56
|
end
|
57
57
|
|
58
58
|
def start_termination(*args)
|
@@ -60,7 +60,7 @@ module Dynflow
|
|
60
60
|
if @current_futures.empty?
|
61
61
|
reference.tell(:finish_termination)
|
62
62
|
else
|
63
|
-
Concurrent.
|
63
|
+
Concurrent::Promises.zip_futures(*@current_futures).then { reference.tell(:finish_termination) }
|
64
64
|
end
|
65
65
|
end
|
66
66
|
|
@@ -78,13 +78,13 @@ module Dynflow
|
|
78
78
|
|
79
79
|
def on_finish
|
80
80
|
raise "Dispatcher terminating: no new work can be started" if terminating?
|
81
|
-
future = Concurrent.
|
81
|
+
future = Concurrent::Promises.resolvable_future
|
82
82
|
callbacks_future = (yield future).rescue { |reason| @world.logger.error("Unexpected fail on future #{reason}") }
|
83
83
|
# we track currently running futures to make sure to not
|
84
84
|
# terminate until the execution is finished (including
|
85
85
|
# cleaning of locks etc)
|
86
86
|
@current_futures << callbacks_future
|
87
|
-
callbacks_future.
|
87
|
+
callbacks_future.on_resolution! { reference.tell([:finish_execution, callbacks_future]) }
|
88
88
|
return future
|
89
89
|
end
|
90
90
|
|
@@ -305,7 +305,7 @@ module Dynflow
|
|
305
305
|
# array with the future value of the cancel result)
|
306
306
|
def cancel(force = false)
|
307
307
|
if state == :scheduled
|
308
|
-
[Concurrent.
|
308
|
+
[Concurrent::Promises.resolvable_future.tap { |f| f.fulfill delay_record.cancel }]
|
309
309
|
else
|
310
310
|
event = force ? ::Dynflow::Action::Cancellable::Abort : ::Dynflow::Action::Cancellable::Cancel
|
311
311
|
steps_to_cancel.map do |step|
|
@@ -98,8 +98,10 @@ module Dynflow
|
|
98
98
|
finalize_step_id: nil,
|
99
99
|
phase: phase }
|
100
100
|
if caller_action
|
101
|
-
|
102
|
-
|
101
|
+
if caller_action.execution_plan_id != execution_plan_id
|
102
|
+
attributes.update(caller_execution_plan_id: caller_action.execution_plan_id)
|
103
|
+
end
|
104
|
+
attributes.update(caller_action_id: caller_action.id)
|
103
105
|
end
|
104
106
|
@action = action_class.new(attributes, world)
|
105
107
|
persistence.save_action(execution_plan_id, @action)
|
@@ -10,20 +10,20 @@ module Dynflow
|
|
10
10
|
end
|
11
11
|
|
12
12
|
# @param execution_plan_id [String] id of execution plan
|
13
|
-
# @param finished [Concurrent::
|
13
|
+
# @param finished [Concurrent::Promises::ResolvableFuture]
|
14
14
|
# @param wait_for_acceptance [TrueClass|FalseClass] should the executor confirm receiving
|
15
15
|
# the event, disable if calling executor from within executor
|
16
|
-
# @return [Concurrent::
|
16
|
+
# @return [Concurrent::Promises::ResolvableFuture]
|
17
17
|
# @raise when execution_plan_id is not accepted
|
18
|
-
def execute(execution_plan_id, finished = Concurrent.
|
18
|
+
def execute(execution_plan_id, finished = Concurrent::Promises.resolvable_future, wait_for_acceptance = true)
|
19
19
|
raise NotImplementedError
|
20
20
|
end
|
21
21
|
|
22
|
-
def event(execution_plan_id, step_id, event, future = Concurrent.
|
22
|
+
def event(execution_plan_id, step_id, event, future = Concurrent::Promises.resolvable_future)
|
23
23
|
raise NotImplementedError
|
24
24
|
end
|
25
25
|
|
26
|
-
def terminate(future = Concurrent.
|
26
|
+
def terminate(future = Concurrent::Promises.resolvable_future)
|
27
27
|
raise NotImplementedError
|
28
28
|
end
|
29
29
|
|
@@ -31,7 +31,7 @@ module Dynflow
|
|
31
31
|
raise NotImplementedError
|
32
32
|
end
|
33
33
|
|
34
|
-
# @return [Concurrent::
|
34
|
+
# @return [Concurrent::Promises::ResolvableFuture]
|
35
35
|
def initialized
|
36
36
|
raise NotImplementedError
|
37
37
|
end
|