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
@@ -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
|
data/lib/dynflow/version.rb
CHANGED
data/lib/dynflow/world.rb
CHANGED
@@ -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.
|
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::
|
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.
|
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::
|
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.
|
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.
|
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.
|
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.
|
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.
|
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.
|
234
|
+
accepted = Concurrent::Promises.resolvable_future
|
235
235
|
accepted.rescue do |reason|
|
236
|
-
done.
|
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.
|
242
|
+
accepted.reject e
|
243
243
|
end
|
244
244
|
|
245
|
-
def terminate(future = Concurrent.
|
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.
|
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.
|
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.
|
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.
|
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.
|
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
|
@@ -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.
|
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.
|
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.
|
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.
|
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.
|
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.
|
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.
|
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.
|
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.
|
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.
|
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)
|
data/test/daemon_test.rb
CHANGED
@@ -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.
|
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.
|
41
|
+
@event.resolve
|
42
42
|
@event.wait
|
43
43
|
end
|
44
44
|
|
data/test/dispatcher_test.rb
CHANGED
@@ -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.
|
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.
|
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.
|
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.
|
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.
|
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.
|
124
|
+
assert result.finished.rejected?
|
125
125
|
assert_match(/No executor available/, result.finished.reason.message)
|
126
126
|
end
|
127
127
|
end
|
data/test/execution_plan_test.rb
CHANGED
@@ -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)
|
data/test/executor_test.rb
CHANGED
@@ -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.
|
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.
|
146
|
+
sub_plan.finished.on_fulfillment! { suspended_action << 'finish' }
|
147
147
|
end
|
148
148
|
end),
|
149
149
|
(on 'finish' do
|
data/test/test_helper.rb
CHANGED
@@ -25,8 +25,8 @@ Concurrent.disable_at_exit_handlers!
|
|
25
25
|
class TestPause
|
26
26
|
|
27
27
|
def self.setup
|
28
|
-
@pause = Concurrent.
|
29
|
-
@ready = Concurrent.
|
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.
|
41
|
+
elsif @ready.resolved?
|
42
42
|
raise 'you can pause only once'
|
43
43
|
else
|
44
|
-
@ready.
|
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.
|
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::
|
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::
|
259
|
-
|
260
|
-
future_class.send :define_method, :
|
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
|
-
|
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::
|
272
|
+
non_ready_events = ObjectSpace.each_object(Concurrent::Promises::Event).map do |event|
|
273
273
|
event.wait(1)
|
274
|
-
unless event.
|
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::
|
297
|
+
wait_method = Concurrent::Promises::AbstractEventFuture.instance_method(:wait)
|
298
298
|
|
299
|
-
Concurrent::
|
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.
|
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.
|
322
|
+
@enter_future.fulfill(true)
|
323
323
|
@exit_future.wait(1)
|
324
324
|
end
|
325
325
|
|
326
326
|
def finish
|
327
|
-
@exit_future.
|
327
|
+
@exit_future.fulfill(true)
|
328
328
|
@thread.join
|
329
329
|
end
|
330
330
|
end
|