quack_concurrency 0.5.4 → 0.6.0
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/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
|