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.
Files changed (46) hide show
  1. checksums.yaml +4 -4
  2. data/Gemfile +1 -1
  3. data/dynflow.gemspec +2 -2
  4. data/examples/clock_benchmark.rb +35 -0
  5. data/examples/memory_limit_watcher.rb +1 -1
  6. data/lib/dynflow/action.rb +2 -2
  7. data/lib/dynflow/action/suspended.rb +1 -1
  8. data/lib/dynflow/action/with_sub_plans.rb +1 -1
  9. data/lib/dynflow/actor.rb +2 -2
  10. data/lib/dynflow/actors/execution_plan_cleaner.rb +1 -1
  11. data/lib/dynflow/clock.rb +11 -8
  12. data/lib/dynflow/delayed_executors/abstract.rb +1 -1
  13. data/lib/dynflow/delayed_plan.rb +1 -1
  14. data/lib/dynflow/director.rb +4 -4
  15. data/lib/dynflow/director/execution_plan_manager.rb +2 -2
  16. data/lib/dynflow/director/running_steps_manager.rb +4 -4
  17. data/lib/dynflow/dispatcher/client_dispatcher.rb +13 -12
  18. data/lib/dynflow/dispatcher/executor_dispatcher.rb +5 -5
  19. data/lib/dynflow/execution_plan.rb +1 -1
  20. data/lib/dynflow/execution_plan/steps/plan_step.rb +4 -2
  21. data/lib/dynflow/executors/abstract.rb +6 -6
  22. data/lib/dynflow/executors/parallel.rb +6 -6
  23. data/lib/dynflow/executors/parallel/core.rb +1 -1
  24. data/lib/dynflow/rails/daemon.rb +1 -1
  25. data/lib/dynflow/testing/dummy_executor.rb +2 -2
  26. data/lib/dynflow/testing/dummy_world.rb +1 -1
  27. data/lib/dynflow/testing/in_thread_executor.rb +5 -5
  28. data/lib/dynflow/testing/in_thread_world.rb +6 -6
  29. data/lib/dynflow/throttle_limiter.rb +5 -5
  30. data/lib/dynflow/utils.rb +3 -140
  31. data/lib/dynflow/utils/indifferent_hash.rb +143 -0
  32. data/lib/dynflow/utils/priority_queue.rb +64 -0
  33. data/lib/dynflow/version.rb +1 -1
  34. data/lib/dynflow/world.rb +22 -22
  35. data/lib/dynflow/world/invalidation.rb +1 -1
  36. data/test/batch_sub_tasks_test.rb +4 -4
  37. data/test/concurrency_control_test.rb +6 -6
  38. data/test/daemon_test.rb +2 -2
  39. data/test/dispatcher_test.rb +6 -6
  40. data/test/execution_plan_test.rb +11 -0
  41. data/test/executor_test.rb +1 -1
  42. data/test/support/dummy_example.rb +1 -1
  43. data/test/test_helper.rb +17 -17
  44. data/test/utils_test.rb +56 -0
  45. data/test/world_test.rb +2 -2
  46. metadata +14 -9
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 9117b88e2a562fc968ff4e9aa26ac35d2ac33fc0a519b81e4bc42d3f8b9139c1
4
- data.tar.gz: d248a508d1e525745fedd29dbc1dce7b9b0ec1a4b1bb75ca43f12ee9f16189b6
3
+ metadata.gz: 6fcc2f12b17318ec09919aae5873ee7aa9e16ab3b6f96508b94c0cf8ae3d0a91
4
+ data.tar.gz: 9a71c09beb7ed1b0cf75ca858dfacc41ae4e33f5c2b82af142f1aac809b2b8f9
5
5
  SHA512:
6
- metadata.gz: c17a37b38e654bc0db350c9c453e9badb34c3575c48c45e32760e093de79600d10768b66e4b6a70ba9975a1fa39a626d1b44d226a68676dd50a3ef5a2ac1f9a4
7
- data.tar.gz: 24c700a6590beedff832767276ff48ad01d927620634a1ef0b36e2f5020733e518809ac114dbf1f281230e7a0bcdc4f88534c9ecb2bf39d0dc038539133c5631
6
+ metadata.gz: e48b378aacfd2a9cdd2c57a3759abe33ba22439615af459a0c779ddf448cfab5b7cf76de655d2450c2190e444fc2586578e86af465488bb0c622e7c94fd96cdb
7
+ data.tar.gz: 0d99ae6fb8f1d68d503610e49f69e1ce344129d0588e2ebe9a57bf3d540790d8f68229f0fa50b7aa1305d539f902c21dbf57ff96871ddaffadf37e56cbcf0924
data/Gemfile CHANGED
@@ -3,7 +3,7 @@ source 'https://rubygems.org'
3
3
  gemspec
4
4
 
5
5
  group :concurrent_ruby_ext do
6
- gem 'concurrent-ruby-ext', '= 1.0.3'
6
+ gem 'concurrent-ruby-ext', '~> 1.1.3'
7
7
  end
8
8
 
9
9
  group :pry do
@@ -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.0.0'
25
- s.add_dependency "concurrent-ruby-edge", '~> 0.2.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
@@ -32,7 +32,7 @@ if $0 == __FILE__
32
32
  config.exit_on_terminate = false
33
33
  end
34
34
 
35
- world.terminated.on_completion do
35
+ world.terminated.on_resolution do
36
36
  puts '[world] The world has been terminated'
37
37
  end
38
38
 
@@ -161,11 +161,11 @@ module Dynflow
161
161
  end
162
162
 
163
163
  def caller_action
164
- plase! Present
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 == 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)
@@ -8,7 +8,7 @@ module Dynflow
8
8
  @step_id = action.run_step_id
9
9
  end
10
10
 
11
- def event(event, future = Concurrent.future)
11
+ def event(event, future = Concurrent::Promises.resolvable_future)
12
12
  @world.event execution_plan_id, step_id, event, future
13
13
  end
14
14
 
@@ -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.on_completion! do |success, value|
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
@@ -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.success true if !envelope.future.nil?
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.success(true)
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.future.tap do |initialized|
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)
@@ -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
- @timers = SortedSet.new
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.add timer
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
- first_timer.apply
88
- @timers.delete(first_timer)
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.first
96
+ @timers.top
94
97
  end
95
98
 
96
99
  def wakeup
@@ -26,7 +26,7 @@ module Dynflow
26
26
  end
27
27
 
28
28
  def spawn
29
- Concurrent.future.tap do |initialized|
29
+ Concurrent::Promises.resolvable_future.tap do |initialized|
30
30
  @core = core_class.spawn name: 'delayed-executor',
31
31
  args: [@world, @options],
32
32
  initialized: initialized
@@ -47,7 +47,7 @@ module Dynflow
47
47
  return true
48
48
  end
49
49
 
50
- def execute(future = Concurrent.future)
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
@@ -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::Edge::Future
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.fail e.message
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.fail e
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.success manager.execution_plan
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::Edge::Future
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.completed?
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.fail UnprocessableEvent.new("dropping due to termination")
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.success true if EventWorkItem === work
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.fail UnprocessableEvent.new(message).
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.fail UnprocessableEvent.new('step is not suspended, it cannot process events')
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::Edge::Future, finished: Concurrent::Edge::Future
7
+ accepted: Concurrent::Promises::ResolvableFuture, finished: Concurrent::Promises::ResolvableFuture
8
8
  end
9
9
 
10
10
  module TrackedRequest
11
11
  def accept!
12
- accepted.success true unless accepted.completed?
12
+ accepted.fulfill true unless accepted.resolved?
13
13
  self
14
14
  end
15
15
 
16
16
  def fail!(error)
17
- accepted.fail error unless accepted.completed?
18
- finished.fail error
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.success true unless accepted.completed?
24
- finished.success(resolve_to)
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.future, finished]
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.completed?
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.completed?
214
+ unless tracked_request.accepted.resolved?
215
215
  tracked_request.accept! # otherwise nobody would set the accept future
216
216
  end
217
- @tracked_requests[tracked_request.id] = TrackedRequest[tracked_request.id, tracked_request.request, Concurrent.future, tracked_request.finished]
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.success(true)
250
+ future.fulfill(true)
250
251
  else
251
252
  if @ping_cache.executor?(request.receiver_id)
252
- future.fail
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.fail(e) if future && !future.completed?
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.fail(e) if future && !future.completed?
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.zip(*@current_futures).then { reference.tell(:finish_termination) }
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.future
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.on_completion! { reference.tell([:finish_execution, 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.future.tap { |f| f.success delay_record.cancel }]
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
- attributes.update(caller_execution_plan_id: caller_action.execution_plan_id,
102
- caller_action_id: caller_action.id)
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::Edge::Future]
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::Edge::Future]
16
+ # @return [Concurrent::Promises::ResolvableFuture]
17
17
  # @raise when execution_plan_id is not accepted
18
- def execute(execution_plan_id, finished = Concurrent.future, wait_for_acceptance = true)
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.future)
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.future)
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::Edge::Future]
34
+ # @return [Concurrent::Promises::ResolvableFuture]
35
35
  def initialized
36
36
  raise NotImplementedError
37
37
  end