dynflow 1.1.6 → 1.2.0.pre1
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/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
|