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.
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