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
@@ -0,0 +1,64 @@
1
+ module Dynflow
2
+ module Utils
3
+ # Heavily inspired by rubyworks/pqueue
4
+ class PriorityQueue
5
+ def initialize(&block) # :yields: a, b
6
+ @backing_store = []
7
+ @comparator = block || :<=>.to_proc
8
+ end
9
+
10
+ def size
11
+ @backing_store.size
12
+ end
13
+
14
+ def top
15
+ @backing_store.last
16
+ end
17
+
18
+ def push(element)
19
+ @backing_store << element
20
+ reposition_element(@backing_store.size - 1)
21
+ end
22
+
23
+ def pop
24
+ @backing_store.pop
25
+ end
26
+
27
+ def to_a
28
+ @backing_store
29
+ end
30
+
31
+ private
32
+
33
+ # The element at index k will be repositioned to its proper place.
34
+ def reposition_element(index)
35
+ return if size <= 1
36
+
37
+ element = @backing_store.delete_at(index)
38
+ index = binary_index(@backing_store, element)
39
+
40
+ @backing_store.insert(index, element)
41
+ end
42
+
43
+ # Find index where a new element should be inserted using binary search
44
+ def binary_index(array, target)
45
+ upper = array.size - 1
46
+ lower = 0
47
+
48
+ while upper >= lower
49
+ center = lower + (upper - lower) / 2
50
+
51
+ case @comparator.call(target, array[center])
52
+ when 0
53
+ return center
54
+ when 1
55
+ lower = center + 1
56
+ when -1
57
+ upper = center - 1
58
+ end
59
+ end
60
+ lower
61
+ end
62
+ end
63
+ end
64
+ end
@@ -1,3 +1,3 @@
1
1
  module Dynflow
2
- VERSION = '1.1.6'.freeze
2
+ VERSION = '1.2.0.pre1'.freeze
3
3
  end
@@ -21,8 +21,8 @@ module Dynflow
21
21
  Dynflow::Telemetry.register_metrics!
22
22
 
23
23
  @id = SecureRandom.uuid
24
- @clock = spawn_and_wait(Clock, 'clock')
25
24
  @logger_adapter = @config.logger_adapter
25
+ @clock = spawn_and_wait(Clock, 'clock', logger)
26
26
  @config.validate
27
27
  @transaction_adapter = @config.transaction_adapter
28
28
  @persistence = Persistence.new(self, @config.persistence_adapter,
@@ -41,7 +41,7 @@ module Dynflow
41
41
  @auto_validity_check = @config.auto_validity_check
42
42
  @validity_check_timeout = @config.validity_check_timeout
43
43
  @throttle_limiter = @config.throttle_limiter
44
- @terminated = Concurrent.event
44
+ @terminated = Concurrent::Promises.resolvable_event
45
45
  @termination_timeout = @config.termination_timeout
46
46
  calculate_subscription_index
47
47
 
@@ -132,7 +132,7 @@ module Dynflow
132
132
  PlaningFailed = type { fields! execution_plan_id: String, error: Exception }
133
133
  # Returned by #trigger when planning is successful, #future will resolve after
134
134
  # ExecutionPlan is executed.
135
- Triggered = type { fields! execution_plan_id: String, future: Concurrent::Edge::Future }
135
+ Triggered = type { fields! execution_plan_id: String, future: Concurrent::Promises::ResolvableFuture }
136
136
 
137
137
  Scheduled = type { fields! execution_plan_id: String }
138
138
 
@@ -176,7 +176,7 @@ module Dynflow
176
176
  planned = execution_plan.state == :planned
177
177
 
178
178
  if planned
179
- done = execute(execution_plan.id, Concurrent.future)
179
+ done = execute(execution_plan.id, Concurrent::Promises.resolvable_future)
180
180
  Triggered[execution_plan.id, done]
181
181
  else
182
182
  PlaningFailed[execution_plan.id, execution_plan.errors.first.exception]
@@ -208,41 +208,41 @@ module Dynflow
208
208
  end
209
209
  end
210
210
 
211
- # @return [Concurrent::Edge::Future] containing execution_plan when finished
211
+ # @return [Concurrent::Promises::ResolvableFuture] containing execution_plan when finished
212
212
  # raises when ExecutionPlan is not accepted for execution
213
- def execute(execution_plan_id, done = Concurrent.future)
213
+ def execute(execution_plan_id, done = Concurrent::Promises.resolvable_future)
214
214
  publish_request(Dispatcher::Execution[execution_plan_id], done, true)
215
215
  end
216
216
 
217
- def event(execution_plan_id, step_id, event, done = Concurrent.future)
217
+ def event(execution_plan_id, step_id, event, done = Concurrent::Promises.resolvable_future)
218
218
  publish_request(Dispatcher::Event[execution_plan_id, step_id, event], done, false)
219
219
  end
220
220
 
221
- def ping(world_id, timeout, done = Concurrent.future)
221
+ def ping(world_id, timeout, done = Concurrent::Promises.resolvable_future)
222
222
  publish_request(Dispatcher::Ping[world_id, true], done, false, timeout)
223
223
  end
224
224
 
225
- def ping_without_cache(world_id, timeout, done = Concurrent.future)
225
+ def ping_without_cache(world_id, timeout, done = Concurrent::Promises.resolvable_future)
226
226
  publish_request(Dispatcher::Ping[world_id, false], done, false, timeout)
227
227
  end
228
228
 
229
- def get_execution_status(world_id, execution_plan_id, timeout, done = Concurrent.future)
229
+ def get_execution_status(world_id, execution_plan_id, timeout, done = Concurrent::Promises.resolvable_future)
230
230
  publish_request(Dispatcher::Status[world_id, execution_plan_id], done, false, timeout)
231
231
  end
232
232
 
233
233
  def publish_request(request, done, wait_for_accepted, timeout = nil)
234
- accepted = Concurrent.future
234
+ accepted = Concurrent::Promises.resolvable_future
235
235
  accepted.rescue do |reason|
236
- done.fail reason if reason
236
+ done.reject reason if reason
237
237
  end
238
238
  client_dispatcher.ask([:publish_request, done, request, timeout], accepted)
239
239
  accepted.wait if wait_for_accepted
240
240
  done
241
241
  rescue => e
242
- accepted.fail e
242
+ accepted.reject e
243
243
  end
244
244
 
245
- def terminate(future = Concurrent.future)
245
+ def terminate(future = Concurrent::Promises.resolvable_future)
246
246
  start_termination.tangle(future)
247
247
  future
248
248
  end
@@ -285,7 +285,7 @@ module Dynflow
285
285
  def start_termination
286
286
  @termination_barrier.synchronize do
287
287
  return @terminating if @terminating
288
- termination_future ||= Concurrent.future do
288
+ termination_future ||= Concurrent::Promises.future do
289
289
  begin
290
290
  run_before_termination_hooks
291
291
 
@@ -304,13 +304,13 @@ module Dynflow
304
304
  executor.terminate.wait(termination_timeout)
305
305
 
306
306
  logger.info "start terminating executor dispatcher..."
307
- executor_dispatcher_terminated = Concurrent.future
307
+ executor_dispatcher_terminated = Concurrent::Promises.resolvable_future
308
308
  executor_dispatcher.ask([:start_termination, executor_dispatcher_terminated])
309
309
  executor_dispatcher_terminated.wait(termination_timeout)
310
310
  end
311
311
 
312
312
  logger.info "start terminating client dispatcher..."
313
- client_dispatcher_terminated = Concurrent.future
313
+ client_dispatcher_terminated = Concurrent::Promises.resolvable_future
314
314
  client_dispatcher.ask([:start_termination, client_dispatcher_terminated])
315
315
  client_dispatcher_terminated.wait(termination_timeout)
316
316
 
@@ -323,15 +323,15 @@ module Dynflow
323
323
  end
324
324
 
325
325
  coordinator.delete_world(registered_world)
326
- @terminated.complete
326
+ @terminated.resolve
327
327
  true
328
328
  rescue => e
329
329
  logger.fatal(e)
330
330
  end
331
331
  end
332
- @terminating = Concurrent.future do
332
+ @terminating = Concurrent::Promises.future do
333
333
  termination_future.wait(termination_timeout)
334
- end.on_completion do
334
+ end.on_resolution do
335
335
  @terminated.complete
336
336
  Thread.new { Kernel.exit } if @exit_on_terminate.true?
337
337
  end
@@ -350,7 +350,7 @@ module Dynflow
350
350
 
351
351
  def run_before_termination_hooks
352
352
  until @before_termination_hooks.empty?
353
- hook_run = Concurrent.future do
353
+ hook_run = Concurrent::Promises.future do
354
354
  begin
355
355
  @before_termination_hooks.pop.call
356
356
  rescue => e
@@ -362,7 +362,7 @@ module Dynflow
362
362
  end
363
363
 
364
364
  def spawn_and_wait(klass, name, *args)
365
- initialized = Concurrent.future
365
+ initialized = Concurrent::Promises.resolvable_future
366
366
  actor = klass.spawn(name: name, args: args, initialized: initialized)
367
367
  initialized.wait
368
368
  return actor
@@ -118,7 +118,7 @@ module Dynflow
118
118
 
119
119
  results = {}
120
120
  world_checks.each do |world, check|
121
- if check.success?
121
+ if check.fulfilled?
122
122
  result = :valid
123
123
  else
124
124
  if auto_invalidate
@@ -68,7 +68,7 @@ module Dynflow
68
68
  FailureSimulator.wont_fail!
69
69
  plan = world.plan(ParentAction, 20)
70
70
  future = world.execute plan.id
71
- wait_for { future.completed? }
71
+ wait_for { future.resolved? }
72
72
  plan = world.persistence.load_execution_plan(plan.id)
73
73
  action = plan.entry_action
74
74
 
@@ -79,7 +79,7 @@ module Dynflow
79
79
  FailureSimulator.should_fail!
80
80
  plan = world.plan(ParentAction, 20)
81
81
  future = world.execute plan.id
82
- wait_for { future.completed? }
82
+ wait_for { future.resolved? }
83
83
  plan = world.persistence.load_execution_plan(plan.id)
84
84
  action = plan.entry_action
85
85
  action.output[:batch_count].must_equal 1
@@ -87,7 +87,7 @@ module Dynflow
87
87
 
88
88
  FailureSimulator.wont_fail!
89
89
  future = world.execute plan.id
90
- wait_for { future.completed? }
90
+ wait_for { future.resolved? }
91
91
  action = future.value.entry_action
92
92
  future.value.state.must_equal :stopped
93
93
  action.output[:batch_count].must_equal (action.total_count / action.batch_size) + 1
@@ -98,7 +98,7 @@ module Dynflow
98
98
  it 'is controlled only by total_count and output[:planned_count]' do
99
99
  plan = world.plan(ParentAction, 10)
100
100
  future = world.execute plan.id
101
- wait_for { future.completed? }
101
+ wait_for { future.resolved? }
102
102
  plan = world.persistence.load_execution_plan(plan.id)
103
103
  action = plan.entry_action
104
104
  action.send(:can_spawn_next_batch?).must_equal false
@@ -124,7 +124,7 @@ module Dynflow
124
124
  total = 10
125
125
  plan = world.plan(ParentAction, 10)
126
126
  future = world.execute plan.id
127
- wait_for { future.completed? }
127
+ wait_for { future.resolved? }
128
128
  plan.sub_plans.all? { |sub| successful? sub }
129
129
  world.throttle_limiter.core.ask!(:running).must_equal [0]
130
130
  end
@@ -134,7 +134,7 @@ module Dynflow
134
134
  level = 4
135
135
  plan = world.plan(ParentAction, total, level)
136
136
  future = world.execute plan.id
137
- wait_for { future.completed? }
137
+ wait_for { future.resolved? }
138
138
  world.throttle_limiter.core.ask!(:running).max.must_be :<=, level
139
139
  end
140
140
 
@@ -145,7 +145,7 @@ module Dynflow
145
145
  triggered = world.execute(plan.id)
146
146
  wait_for { plan.sub_plans_count == total }
147
147
  world.event(plan.id, plan.steps.values.last.id, ::Dynflow::Action::Cancellable::Cancel)
148
- wait_for { triggered.completed? }
148
+ wait_for { triggered.resolved? }
149
149
  plan = world.persistence.load_execution_plan(plan.id)
150
150
  plan.entry_action.output[:failed_count].must_equal total
151
151
  world.throttle_limiter.core.ask!(:running).max.must_be :<=, 0
@@ -198,7 +198,7 @@ module Dynflow
198
198
  world.throttle_limiter.observe(plan.id).dup.each do |triggered|
199
199
  triggered.future.tap do |future|
200
200
  klok.progress
201
- wait_for { future.completed? }
201
+ wait_for { future.resolved? }
202
202
  end
203
203
  finished += 1
204
204
  check_step(plan, total, finished)
@@ -217,7 +217,7 @@ module Dynflow
217
217
  time_span = 10
218
218
  plan = world.plan(ParentAction, total, level, time_span)
219
219
  future = world.execute(plan.id)
220
- wait_for { future.completed? }
220
+ wait_for { future.resolved? }
221
221
  plan.sub_plans.all? { |sub| sub.result == :error }.must_equal true
222
222
  end
223
223
 
@@ -234,7 +234,7 @@ module Dynflow
234
234
  running.count.must_equal level
235
235
  world.throttle_limiter.observe(plan.id).length.must_equal (total - 1)
236
236
  4.times { klok.progress }
237
- wait_for { future.completed? }
237
+ wait_for { future.resolved? }
238
238
  finished, stopped = plan.sub_plans.partition { |sub| successful? sub }
239
239
  finished.count.must_equal level
240
240
  stopped.count.must_equal (total - level)
@@ -18,7 +18,7 @@ class DaemonTest < ActiveSupport::TestCase
18
18
  @dummy_world.stubs(:id => '123')
19
19
  @dummy_world.stubs(:auto_execute)
20
20
  @dummy_world.stubs(:perform_validity_checks => 0)
21
- @event = Concurrent.event
21
+ @event = Concurrent::Promises.resolvable_event
22
22
  @dummy_world.stubs(:terminated).returns(@event)
23
23
  @world_class.stubs(:new).returns(@dummy_world)
24
24
  @dynflow = ::Dynflow::Rails.new(
@@ -38,7 +38,7 @@ class DaemonTest < ActiveSupport::TestCase
38
38
  end
39
39
 
40
40
  teardown do
41
- @event.complete
41
+ @event.resolve
42
42
  @event.wait
43
43
  end
44
44
 
@@ -40,7 +40,7 @@ module Dynflow
40
40
  step = plan.steps.values.first
41
41
  future = client_world.event(plan.id, step.id, 'finish')
42
42
  future.wait
43
- assert future.failed?
43
+ assert future.rejected?
44
44
  end
45
45
 
46
46
  it 'succeeds when executor acts as client' do
@@ -73,27 +73,27 @@ module Dynflow
73
73
  it 'succeeds when the world is available' do
74
74
  ping_response = client_world.ping(executor_world.id, 0.5)
75
75
  ping_response.wait
76
- assert ping_response.success?
76
+ assert ping_response.fulfilled?
77
77
  end
78
78
 
79
79
  it 'succeeds when the world is available without cache' do
80
80
  ping_response = client_world.ping_without_cache(executor_world.id, 0.5)
81
81
  ping_response.wait
82
- assert ping_response.success?
82
+ assert ping_response.fulfilled?
83
83
  end
84
84
 
85
85
  it 'time-outs when the world is not responding' do
86
86
  executor_world.terminate.wait
87
87
  ping_response = client_world.ping(executor_world.id, 0.5)
88
88
  ping_response.wait
89
- assert ping_response.failed?
89
+ assert ping_response.rejected?
90
90
  end
91
91
 
92
92
  it 'time-outs when the world is not responding without cache' do
93
93
  executor_world.terminate.wait
94
94
  ping_response = client_world.ping_without_cache(executor_world.id, 0.5)
95
95
  ping_response.wait
96
- assert ping_response.failed?
96
+ assert ping_response.rejected?
97
97
  end
98
98
 
99
99
  it 'caches the pings and pongs' do
@@ -121,7 +121,7 @@ module Dynflow
121
121
  executor_world_2.terminate.wait
122
122
  result = client_world.trigger(Support::DummyExample::Dummy)
123
123
  result.finished.wait
124
- assert result.finished.failed?
124
+ assert result.finished.rejected?
125
125
  assert_match(/No executor available/, result.finished.reason.message)
126
126
  end
127
127
  end
@@ -141,6 +141,17 @@ module Dynflow
141
141
 
142
142
  end
143
143
 
144
+ describe 'sub plans' do
145
+ let(:execution_plan) do
146
+ world.plan(Support::CodeWorkflowExample::IncomingIssues, issues_data)
147
+ end
148
+
149
+ it 'does not have itself as a sub plan' do
150
+ assert execution_plan.actions.count >= 2
151
+ execution_plan.sub_plans.must_be :empty?
152
+ end
153
+ end
154
+
144
155
  describe 'plan steps' do
145
156
  let :execution_plan do
146
157
  world.plan(Support::CodeWorkflowExample::IncomingIssues, issues_data)
@@ -671,7 +671,7 @@ module Dynflow
671
671
  result = world.trigger(Support::DummyExample::Slow, 0.02)
672
672
  result.must_be :planned?
673
673
  result.finished.wait
674
- assert result.finished.failed?
674
+ assert result.finished.rejected?
675
675
  result.finished.reason.must_be_kind_of Concurrent::Actor::ActorTerminated
676
676
  end
677
677
 
@@ -143,7 +143,7 @@ module Support
143
143
  world.clock.ping suspended_action, input[:timeout], "timeout"
144
144
  end
145
145
 
146
- sub_plan.finished.on_success! { suspended_action << 'finish' }
146
+ sub_plan.finished.on_fulfillment! { suspended_action << 'finish' }
147
147
  end
148
148
  end),
149
149
  (on 'finish' do
@@ -25,8 +25,8 @@ Concurrent.disable_at_exit_handlers!
25
25
  class TestPause
26
26
 
27
27
  def self.setup
28
- @pause = Concurrent.future
29
- @ready = Concurrent.future
28
+ @pause = Concurrent::Promises.resolvable_future
29
+ @ready = Concurrent::Promises.resolvable_future
30
30
  end
31
31
 
32
32
  def self.teardown
@@ -38,10 +38,10 @@ class TestPause
38
38
  def self.pause
39
39
  if !@pause
40
40
  raise 'the TestPause class was not setup'
41
- elsif @ready.completed?
41
+ elsif @ready.resolved?
42
42
  raise 'you can pause only once'
43
43
  else
44
- @ready.success(true)
44
+ @ready.fulfill(true)
45
45
  @pause.wait
46
46
  end
47
47
  end
@@ -51,7 +51,7 @@ class TestPause
51
51
  if @pause
52
52
  @ready.wait # wait till we are paused
53
53
  yield
54
- @pause.success(true) # resume the run
54
+ @pause.fulfill(true) # resume the run
55
55
  else
56
56
  raise 'the TestPause class was not setup'
57
57
  end
@@ -249,17 +249,17 @@ events_test = -> do
249
249
  event_creations = {}
250
250
  non_ready_events = {}
251
251
 
252
- Concurrent::Edge::Event.singleton_class.send :define_method, :new do |*args, &block|
252
+ Concurrent::Promises::Event.singleton_class.send :define_method, :new do |*args, &block|
253
253
  super(*args, &block).tap do |event|
254
254
  event_creations[event.object_id] = caller(4)
255
255
  end
256
256
  end
257
257
 
258
- [Concurrent::Edge::Event, Concurrent::Edge::Future].each do |future_class|
259
- original_complete_method = future_class.instance_method :complete_with
260
- future_class.send :define_method, :complete_with do |*args|
258
+ [Concurrent::Promises::Event, Concurrent::Promises::ResolvableFuture].each do |future_class|
259
+ original_resolved_method = future_class.instance_method :resolve_with
260
+ future_class.send :define_method, :resolve_with do |*args|
261
261
  begin
262
- original_complete_method.bind(self).call(*args)
262
+ original_resolved_method.bind(self).call(*args)
263
263
  ensure
264
264
  event_creations.delete(self.object_id)
265
265
  end
@@ -269,9 +269,9 @@ events_test = -> do
269
269
  MiniTest.after_run do
270
270
  Concurrent::Actor.root.ask!(:terminate!)
271
271
 
272
- non_ready_events = ObjectSpace.each_object(Concurrent::Edge::Event).map do |event|
272
+ non_ready_events = ObjectSpace.each_object(Concurrent::Promises::Event).map do |event|
273
273
  event.wait(1)
274
- unless event.completed?
274
+ unless event.resolved?
275
275
  event.object_id
276
276
  end
277
277
  end.compact
@@ -294,9 +294,9 @@ events_test = -> do
294
294
 
295
295
  # time out all futures by default
296
296
  default_timeout = 8
297
- wait_method = Concurrent::Edge::Event.instance_method(:wait)
297
+ wait_method = Concurrent::Promises::AbstractEventFuture.instance_method(:wait)
298
298
 
299
- Concurrent::Edge::Event.class_eval do
299
+ Concurrent::Promises::AbstractEventFuture.class_eval do
300
300
  define_method :wait do |timeout = nil|
301
301
  wait_method.bind(self).call(timeout || default_timeout)
302
302
  end
@@ -308,7 +308,7 @@ events_test.call
308
308
 
309
309
  class ConcurrentRunTester
310
310
  def initialize
311
- @enter_future, @exit_future = Concurrent.future, Concurrent.future
311
+ @enter_future, @exit_future = Concurrent::Promises.resolvable_future, Concurrent::Promises.resolvable_future
312
312
  end
313
313
 
314
314
  def while_executing(&block)
@@ -319,12 +319,12 @@ class ConcurrentRunTester
319
319
  end
320
320
 
321
321
  def pause
322
- @enter_future.success(true)
322
+ @enter_future.fulfill(true)
323
323
  @exit_future.wait(1)
324
324
  end
325
325
 
326
326
  def finish
327
- @exit_future.success(true)
327
+ @exit_future.fulfill(true)
328
328
  @thread.join
329
329
  end
330
330
  end