quack_concurrency 0.5.4 → 0.6.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/README.md +1 -1
- data/lib/quack_concurrency.rb +6 -13
- data/lib/quack_concurrency/condition_variable.rb +91 -85
- data/lib/quack_concurrency/condition_variable/waitable.rb +108 -0
- data/lib/quack_concurrency/error.rb +1 -1
- data/lib/quack_concurrency/future.rb +31 -30
- data/lib/quack_concurrency/future/canceled.rb +1 -0
- data/lib/quack_concurrency/future/complete.rb +1 -0
- data/lib/quack_concurrency/mutex.rb +140 -38
- data/lib/quack_concurrency/queue.rb +32 -28
- data/lib/quack_concurrency/reentrant_mutex.rb +64 -76
- data/lib/quack_concurrency/safe_condition_variable.rb +23 -0
- data/lib/quack_concurrency/safe_condition_variable/waitable.rb +21 -0
- data/lib/quack_concurrency/safe_sleeper.rb +80 -0
- data/lib/quack_concurrency/sleeper.rb +100 -0
- data/lib/quack_concurrency/waiter.rb +32 -23
- data/spec/condition_variable_spec.rb +216 -0
- data/spec/future_spec.rb +145 -79
- data/spec/mutex_spec.rb +441 -0
- data/spec/queue_spec.rb +217 -77
- data/spec/reentrant_mutex_spec.rb +394 -99
- data/spec/safe_condition_variable_spec.rb +115 -0
- data/spec/safe_sleeper_spec.rb +197 -0
- data/spec/sleeper.rb +197 -0
- data/spec/waiter_spec.rb +181 -0
- metadata +16 -14
- data/lib/quack_concurrency/queue/error.rb +0 -6
- data/lib/quack_concurrency/reentrant_mutex/error.rb +0 -6
- data/lib/quack_concurrency/semaphore.rb +0 -139
- data/lib/quack_concurrency/semaphore/error.rb +0 -6
- data/lib/quack_concurrency/uninterruptible_condition_variable.rb +0 -94
- data/lib/quack_concurrency/uninterruptible_sleeper.rb +0 -81
- data/lib/quack_concurrency/yielder.rb +0 -35
- data/spec/semaphore_spec.rb +0 -244
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 98f80adbcb27205b31915dac49d3577b27f9fe57c1c47e54950d1484c6788b90
|
4
|
+
data.tar.gz: f43c9afe0d281b1a739533db8aa6631808ceada3d5712447cc9d35ec3e25a07e
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 8ccbd4e65d61e1a69785d5a35e14e5cb77357260c15958de82abbd3b79567b2cbd3a19667a5f25a7523e55aefb744447473e95bfc60fa19c009bcc1d98932682
|
7
|
+
data.tar.gz: f45e1759342961393e890d8bcb34d5a244ba70eba8c8a5ae7671886692330579b07c9890764d41ad7c442dbe3d1a3a9141b6a26022914430564c154febc18f7b
|
data/README.md
CHANGED
@@ -1,5 +1,5 @@
|
|
1
1
|
# Quack Concurrency
|
2
|
-
This Ruby Gem offers a few concurrency tools that could also be found in [*Concurrent Ruby*](https://github.com/ruby-concurrency/concurrent-ruby). However, all of *Quack Concurrency's* tools will tolerate duck types of Ruby's core classes to adjust the blocking behaviour of the tools. The tools include: `ConditionVariable`, `Future`, `Mutex`, `Queue`, `ReentrantMutex`, `
|
2
|
+
This Ruby Gem offers a few concurrency tools that could also be found in [*Concurrent Ruby*](https://github.com/ruby-concurrency/concurrent-ruby). However, all of *Quack Concurrency's* tools will tolerate duck types of Ruby's core `::Mutex` and `::ConditionVariable` classes to adjust the blocking behaviour of the tools. The tools available include: `ConditionVariable`, `Future`, `Mutex`, `Queue`, `ReentrantMutex`, `SafeConditionVariable`, `SafeSleeper` and `Sleeper`. *TODO: list some projects using it*.
|
3
3
|
|
4
4
|
# Install
|
5
5
|
`gem install quack_concurrency`
|
data/lib/quack_concurrency.rb
CHANGED
@@ -2,28 +2,21 @@ require 'thread'
|
|
2
2
|
require 'reentrant_mutex'
|
3
3
|
|
4
4
|
require 'quack_concurrency/condition_variable'
|
5
|
+
require 'quack_concurrency/condition_variable/waitable'
|
5
6
|
require 'quack_concurrency/error'
|
6
7
|
require 'quack_concurrency/future'
|
7
8
|
require 'quack_concurrency/future/canceled'
|
8
9
|
require 'quack_concurrency/future/complete'
|
9
10
|
require 'quack_concurrency/mutex'
|
10
11
|
require 'quack_concurrency/queue'
|
11
|
-
require 'quack_concurrency/queue/error'
|
12
12
|
require 'quack_concurrency/reentrant_mutex'
|
13
|
-
require 'quack_concurrency/
|
14
|
-
require 'quack_concurrency/
|
15
|
-
require 'quack_concurrency/
|
13
|
+
require 'quack_concurrency/safe_condition_variable'
|
14
|
+
require 'quack_concurrency/safe_condition_variable/waitable'
|
15
|
+
require 'quack_concurrency/sleeper'
|
16
|
+
require 'quack_concurrency/safe_sleeper'
|
16
17
|
require 'quack_concurrency/waiter'
|
17
18
|
|
18
19
|
|
19
|
-
# if you pass a duck type Hash to any of the concurrency tools it will force you to
|
20
|
-
# supply all the required ducktypes, all or nothing, as it were
|
21
|
-
# this is to protect against forgetting to pass one of the duck types as this
|
22
|
-
# would be a hard bug to solve otherwise
|
23
|
-
|
24
|
-
|
25
20
|
module QuackConcurrency
|
26
|
-
|
27
|
-
ClosedQueueError = ::ClosedQueueError
|
28
|
-
|
21
|
+
|
29
22
|
end
|
@@ -1,128 +1,134 @@
|
|
1
1
|
module QuackConcurrency
|
2
|
-
|
3
|
-
#
|
2
|
+
|
3
|
+
# {ConditionVariable} is similar to +::ConditionVariable+.
|
4
|
+
#
|
5
|
+
# A a few differences include:
|
6
|
+
# * {#wait} supports passing a {ReentrantMutex} and {Mutex}
|
7
|
+
# * methods have been added to get information on waiting threads
|
4
8
|
class ConditionVariable
|
5
|
-
|
9
|
+
|
6
10
|
# Creates a new {ConditionVariable} concurrency tool.
|
7
11
|
# @return [ConditionVariable]
|
8
12
|
def initialize
|
9
|
-
@waiting_threads = []
|
10
13
|
@mutex = ::Mutex.new
|
14
|
+
@waitables = []
|
15
|
+
@waitables_to_resume = []
|
11
16
|
end
|
12
|
-
|
13
|
-
#
|
14
|
-
# @api private
|
17
|
+
|
18
|
+
# Checks if any threads are waiting on it.
|
15
19
|
# @return [Boolean]
|
16
20
|
def any_waiting_threads?
|
17
21
|
waiting_threads_count >= 1
|
18
22
|
end
|
19
|
-
|
20
|
-
#
|
23
|
+
|
24
|
+
# Resumes all threads waiting on it.
|
21
25
|
# @return [self]
|
22
26
|
def broadcast
|
23
27
|
@mutex.synchronize do
|
24
|
-
signal_next until @
|
28
|
+
signal_next until @waitables_to_resume.empty?
|
25
29
|
end
|
26
30
|
self
|
27
31
|
end
|
28
|
-
|
29
|
-
#
|
32
|
+
|
33
|
+
# Returns the {Waitable} representing the next thread to be woken.
|
34
|
+
# It will return the thread that made the earliest call to {#wait}.
|
35
|
+
# @api private
|
36
|
+
# @return [Waitable]
|
37
|
+
def next_waitable_to_wake
|
38
|
+
@mutex.synchronize { @waitables.first }
|
39
|
+
end
|
40
|
+
|
41
|
+
# Resumes next thread waiting on it if one exists.
|
30
42
|
# @return [self]
|
31
43
|
def signal
|
32
44
|
@mutex.synchronize do
|
33
|
-
signal_next if @
|
45
|
+
signal_next if @waitables_to_resume.any?
|
34
46
|
end
|
35
47
|
self
|
36
48
|
end
|
37
|
-
|
38
|
-
#
|
39
|
-
#
|
40
|
-
# @
|
41
|
-
# @
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
Thread.sleep(duration)
|
47
|
-
end
|
48
|
-
nil
|
49
|
-
end
|
50
|
-
|
51
|
-
# Sleeps the current `Thread` until {#signal} or {#broadcast} wake it.
|
52
|
-
# If a {Mutex} is given, the {Mutex} will be unlocked before sleeping and relocked when woken.
|
53
|
-
# @raise [ArgumentError]
|
54
|
-
# @param mutex [nil,Mutex]
|
55
|
-
# @param timeout [nil,Numeric] maximum time to wait, specified in seconds
|
49
|
+
|
50
|
+
# Puts this thread to sleep until another thread resumes it.
|
51
|
+
# Threads will be woken in the chronological order that this was called.
|
52
|
+
# @note Will block until resumed
|
53
|
+
# @param mutex [Mutex] mutex to be unlocked while this thread is sleeping
|
54
|
+
# @param timeout [nil,Numeric] maximum time to sleep in seconds, +nil+ for forever
|
55
|
+
# @raise [TypeError] if +timeout+ is not +nil+ or +Numeric+
|
56
|
+
# @raise [ArgumentError] if +timeout+ is negative
|
57
|
+
# @raise [Exception] any exception raised by +::ConditionVariable#wait+ (eg. interrupts, +ThreadError+)
|
56
58
|
# @return [self]
|
57
|
-
def wait(mutex
|
59
|
+
def wait(mutex, timeout = nil)
|
58
60
|
validate_mutex(mutex)
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
# ideally we would would check if this Thread can sleep (not the last Thread alive)
|
65
|
-
# before we unlock the mutex, however I am not sure is that can be implemented
|
66
|
-
if mutex.respond_to?(:unlock!)
|
67
|
-
mutex.unlock! { sleep(timeout) }
|
68
|
-
else
|
69
|
-
mutex.unlock
|
70
|
-
begin
|
71
|
-
sleep(timeout)
|
72
|
-
ensure # rescue a fatal error (eg. only Thread stopped)
|
73
|
-
if mutex.locked?
|
74
|
-
# another Thread locked this before it died
|
75
|
-
# this is not a correct state to be in but I don't know how to fix it
|
76
|
-
# given that there are no other alive Threads then than the ramifications should be minimal
|
77
|
-
else
|
78
|
-
mutex.lock
|
79
|
-
end
|
80
|
-
end
|
81
|
-
end
|
82
|
-
else
|
83
|
-
sleep(timeout)
|
61
|
+
validate_timeout(timeout)
|
62
|
+
waitable = waitable_for_current_thread
|
63
|
+
@mutex.synchronize do
|
64
|
+
@waitables.push(waitable)
|
65
|
+
@waitables_to_resume.push(waitable)
|
84
66
|
end
|
67
|
+
waitable.wait(mutex, timeout)
|
85
68
|
self
|
86
|
-
ensure
|
87
|
-
@mutex.synchronize { @waiting_threads.delete(caller) }
|
88
69
|
end
|
89
|
-
|
90
|
-
#
|
70
|
+
|
71
|
+
# Remove a {Waitable} whose thread has been woken.
|
91
72
|
# @api private
|
73
|
+
# @return [void]
|
74
|
+
def waitable_woken(waitable)
|
75
|
+
@mutex.synchronize { @waitables.delete(waitable) }
|
76
|
+
end
|
77
|
+
|
78
|
+
# Returns the number of threads currently waiting on it.
|
92
79
|
# @return [Integer]
|
93
80
|
def waiting_threads_count
|
94
|
-
@
|
81
|
+
@waitables.length
|
95
82
|
end
|
96
|
-
|
83
|
+
|
97
84
|
private
|
98
|
-
|
99
|
-
#
|
100
|
-
#
|
101
|
-
# @return [Thread]
|
102
|
-
def caller
|
103
|
-
Thread.current
|
104
|
-
end
|
105
|
-
|
106
|
-
# Wakes up the next waiting `Thread`.
|
107
|
-
# Will try again if the `Thread` has already been woken.
|
85
|
+
|
86
|
+
# Wakes up the next waiting thread.
|
87
|
+
# Will try again if the thread has already been woken.
|
108
88
|
# @api private
|
109
89
|
# @return [void]
|
110
90
|
def signal_next
|
111
|
-
|
112
|
-
|
113
|
-
|
114
|
-
|
115
|
-
|
116
|
-
|
91
|
+
loop do
|
92
|
+
next_waitable = @waitables_to_resume.shift
|
93
|
+
if next_waitable
|
94
|
+
resume_successful = next_waitable.resume
|
95
|
+
break if resume_successful
|
96
|
+
end
|
117
97
|
end
|
118
98
|
nil
|
119
99
|
end
|
120
|
-
|
100
|
+
|
101
|
+
# Validates that an object behaves like a +::Mutex+
|
102
|
+
# Must be able to lock and unlock +mutex+.
|
103
|
+
# @api private
|
104
|
+
# @param mutex [Mutex] mutex to be validated
|
105
|
+
# @raise [TypeError] if +mutex+ does not behave like a +::Mutex+
|
106
|
+
# @return [void]
|
121
107
|
def validate_mutex(mutex)
|
122
|
-
return if mutex
|
123
|
-
return if mutex.respond_to?(:
|
124
|
-
raise
|
108
|
+
return if mutex.respond_to?(:lock) && mutex.respond_to?(:unlock)
|
109
|
+
return if mutex.respond_to?(:unlock!)
|
110
|
+
raise TypeError, "'mutex' must respond to ('lock' and 'unlock') or 'unlock!'"
|
111
|
+
end
|
112
|
+
|
113
|
+
# Validates a timeout value
|
114
|
+
# @api private
|
115
|
+
# @param timeout [nil,Numeric]
|
116
|
+
# @raise [TypeError] if +timeout+ is not +nil+ or +Numeric+
|
117
|
+
# @raise [ArgumentError] if +timeout+ is negative
|
118
|
+
# @return [void]
|
119
|
+
def validate_timeout(timeout)
|
120
|
+
unless timeout == nil
|
121
|
+
raise TypeError, "'timeout' must be nil or a Numeric" unless timeout.is_a?(Numeric)
|
122
|
+
raise ArgumentError, "'timeout' must not be negative" if timeout.negative?
|
123
|
+
end
|
124
|
+
end
|
125
|
+
|
126
|
+
# Returns a waitable to represent the current thread.
|
127
|
+
# @api private
|
128
|
+
# @return [Waitable]
|
129
|
+
def waitable_for_current_thread
|
130
|
+
Waitable.new(self)
|
125
131
|
end
|
126
|
-
|
132
|
+
|
127
133
|
end
|
128
134
|
end
|
@@ -0,0 +1,108 @@
|
|
1
|
+
module QuackConcurrency
|
2
|
+
class ConditionVariable
|
3
|
+
|
4
|
+
# Used to put threads to sleep and wake them back up in order.
|
5
|
+
# A given mutex will be unlocked while the thread sleeps.
|
6
|
+
# When waking a thread it will ensure the mutex is relocked before wakng the next thread.
|
7
|
+
# Threads will be woken in the chronological order that {#wait} was called.
|
8
|
+
class Waitable
|
9
|
+
|
10
|
+
# Creates a new {Waitable}.
|
11
|
+
# @return [ConditionVariable]
|
12
|
+
def initialize(condition_variable)
|
13
|
+
@condition_variable = condition_variable
|
14
|
+
@complete_condition_variable = ::ConditionVariable.new
|
15
|
+
@mutex = ::Mutex.new
|
16
|
+
@sleeper = Sleeper.new
|
17
|
+
@state = :inital
|
18
|
+
end
|
19
|
+
|
20
|
+
# Request the sleeping thread to wake.
|
21
|
+
# It will return +false+ if the thread was already woken,
|
22
|
+
# possibly due to an interrupt or calling +Thread#run+, etc.
|
23
|
+
# @return [Boolean] if the thread was successfully woken during this call
|
24
|
+
def resume
|
25
|
+
@mutex.synchronize do
|
26
|
+
if @state == :complete
|
27
|
+
false
|
28
|
+
else
|
29
|
+
@sleeper.wake
|
30
|
+
true
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
# Puts this thread to sleep until {#resume} is called.
|
36
|
+
# Unlocks +mutex+ while sleeping
|
37
|
+
# It will ensure that previous sleeping threads have resumed before mutex is relocked.
|
38
|
+
# @note Will block until resumed
|
39
|
+
# @param mutex [Mutex] mutex to be unlocked while this thread is sleeping
|
40
|
+
# @param timeout [nil,Numeric] maximum time to sleep in seconds, nil for forever
|
41
|
+
# @raise [TypeError] if +timeout+ is not +nil+ or +Numeric+
|
42
|
+
# @raise [ArgumentError] if +timeout+ is negative
|
43
|
+
# @raise [Exception] any exception raised by +::ConditionVariable#wait+ (eg. interrupts, +ThreadError+)
|
44
|
+
# @return [self]
|
45
|
+
def wait(mutex, timeout)
|
46
|
+
# ideally we would would check if this thread can sleep (ie. is not the last thread alive)
|
47
|
+
# before we unlock the mutex, however I am not sure that it can be implemented
|
48
|
+
if mutex.respond_to?(:unlock!)
|
49
|
+
mutex.unlock! { sleep(timeout) }
|
50
|
+
else
|
51
|
+
mutex_unlock(mutex) { sleep(timeout) }
|
52
|
+
end
|
53
|
+
ensure
|
54
|
+
@mutex.synchronize do
|
55
|
+
@condition_variable.waitable_woken(self)
|
56
|
+
@state = :complete
|
57
|
+
@complete_condition_variable.broadcast
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
# Wait until thread has woken and relocked the mutex.
|
62
|
+
# Will block until thread has resumed.
|
63
|
+
# Will not block if {#resume} has already been called.
|
64
|
+
# @api private
|
65
|
+
# @return [void]
|
66
|
+
def wait_until_resumed
|
67
|
+
@mutex.synchronize do
|
68
|
+
@complete_condition_variable.wait(@mutex) unless @state == :complete
|
69
|
+
end
|
70
|
+
end
|
71
|
+
|
72
|
+
private
|
73
|
+
|
74
|
+
# Temporarily unlocks a mutex while a block is run.
|
75
|
+
# If an error is raised in the block, +mutex+ will try to be immediately relocked
|
76
|
+
# before passing the error up. If unsuccessful, a +ThreadError+ will be raised to
|
77
|
+
# imitate the core's behavior.
|
78
|
+
# @api private
|
79
|
+
# @raise [ThreadError] if relock unsuccessful after an error
|
80
|
+
# @return [void]
|
81
|
+
def mutex_unlock(mutex, &block)
|
82
|
+
mutex.unlock
|
83
|
+
yield
|
84
|
+
mutex.lock
|
85
|
+
rescue Exception
|
86
|
+
unless mutex.try_lock
|
87
|
+
raise ThreadError, "Attempt to lock a mutex which is locked by another thread"
|
88
|
+
end
|
89
|
+
raise
|
90
|
+
end
|
91
|
+
|
92
|
+
# Puts this thread to sleep.
|
93
|
+
# It will ensure that previous sleeping threads have resumed before returning.
|
94
|
+
# @api private
|
95
|
+
# @param timeout [nil, Numeric] time to sleep in seconds, nil for forever
|
96
|
+
# @return [void]
|
97
|
+
def sleep(timeout)
|
98
|
+
@sleeper.sleep(timeout)
|
99
|
+
loop do
|
100
|
+
next_waitable = @condition_variable.next_waitable_to_wake
|
101
|
+
break if next_waitable == self
|
102
|
+
next_waitable.wait_until_resumed
|
103
|
+
end
|
104
|
+
end
|
105
|
+
|
106
|
+
end
|
107
|
+
end
|
108
|
+
end
|
@@ -1,68 +1,69 @@
|
|
1
1
|
module QuackConcurrency
|
2
|
+
|
3
|
+
# Used to send a value or error from one thread to another without the need for coordination.
|
2
4
|
class Future
|
3
|
-
|
5
|
+
|
4
6
|
# Creates a new {Future} concurrency tool.
|
5
7
|
# @return [Future]
|
6
8
|
def initialize
|
7
|
-
@waiter = Waiter.new
|
8
|
-
@mutex = ::Mutex.new
|
9
|
-
@value = nil
|
10
9
|
@complete = false
|
11
10
|
@exception = false
|
11
|
+
@mutex = ::Mutex.new
|
12
|
+
@value = nil
|
13
|
+
@waiter = Waiter.new
|
12
14
|
end
|
13
|
-
|
14
|
-
# Cancels
|
15
|
-
#
|
16
|
-
#
|
17
|
-
# @
|
18
|
-
# @param exception [Exception] custom `Exception` to set
|
15
|
+
|
16
|
+
# Cancels it.
|
17
|
+
# If no +exception+ is specified, a {Canceled} error will be set.
|
18
|
+
# @raise [Complete] if the {Future} has already completed
|
19
|
+
# @param exception [Exception] custom exception to set (see {#raise})
|
19
20
|
# @return [void]
|
20
21
|
def cancel(exception = nil)
|
21
22
|
exception ||= Canceled.new
|
22
23
|
self.raise(exception)
|
23
24
|
nil
|
24
25
|
end
|
25
|
-
|
26
|
-
# Checks if
|
26
|
+
|
27
|
+
# Checks if it has a value or error set.
|
27
28
|
# @return [Boolean]
|
28
29
|
def complete?
|
29
30
|
@complete
|
30
31
|
end
|
31
|
-
|
32
|
-
# Gets
|
33
|
-
# @note This method will block until the future has completed
|
34
|
-
# @raise [Canceled] if
|
35
|
-
# @raise [Exception] if
|
36
|
-
# @return [Object] value
|
32
|
+
|
33
|
+
# Gets it's value.
|
34
|
+
# @note This method will block until the future has completed
|
35
|
+
# @raise [Canceled] if it is canceled
|
36
|
+
# @raise [Exception] if specific error was set
|
37
|
+
# @return [Object] it's value
|
37
38
|
def get
|
38
39
|
@waiter.wait
|
39
40
|
Kernel.raise(@exception) if @exception
|
40
41
|
@value
|
41
42
|
end
|
42
|
-
|
43
|
-
#
|
44
|
-
# @raise [Complete] if the
|
45
|
-
# @param exception [Exception
|
43
|
+
|
44
|
+
# Sets it to an error.
|
45
|
+
# @raise [Complete] if the it has already completed
|
46
|
+
# @param exception [nil,Object] +Exception+ class or instance to set, otherwise a +StandardError+ will be set
|
46
47
|
# @return [void]
|
47
48
|
def raise(exception = nil)
|
48
49
|
exception = case
|
49
50
|
when exception == nil then StandardError.new
|
50
51
|
when exception.is_a?(Exception) then exception
|
51
|
-
when
|
52
|
+
when Exception >= exception then exception.new
|
52
53
|
else
|
53
|
-
Kernel.raise(
|
54
|
+
Kernel.raise(TypeError, "'exception' must be nil or an instance of or descendant of Exception")
|
54
55
|
end
|
55
56
|
@mutex.synchronize do
|
56
57
|
Kernel.raise(Complete) if @complete
|
57
58
|
@complete = true
|
58
59
|
@exception = exception
|
59
|
-
@waiter.
|
60
|
+
@waiter.resume_all_indefinitely
|
60
61
|
end
|
61
62
|
nil
|
62
63
|
end
|
63
|
-
|
64
|
-
# Sets
|
65
|
-
# @raise [Complete] if
|
64
|
+
|
65
|
+
# Sets it to a value.
|
66
|
+
# @raise [Complete] if it has already completed
|
66
67
|
# @param new_value [nil,Object] value to assign to future
|
67
68
|
# @return [void]
|
68
69
|
def set(new_value = nil)
|
@@ -70,10 +71,10 @@ module QuackConcurrency
|
|
70
71
|
Kernel.raise(Complete) if @complete
|
71
72
|
@complete = true
|
72
73
|
@value = new_value
|
73
|
-
@waiter.
|
74
|
+
@waiter.resume_all_indefinitely
|
74
75
|
end
|
75
76
|
nil
|
76
77
|
end
|
77
|
-
|
78
|
+
|
78
79
|
end
|
79
80
|
end
|