concurrent-ruby 0.7.0-java
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.
- data/LICENSE.txt +21 -0
- data/README.md +217 -0
- data/lib/concurrent.rb +45 -0
- data/lib/concurrent/actor.rb +104 -0
- data/lib/concurrent/actor/behaviour.rb +70 -0
- data/lib/concurrent/actor/behaviour/abstract.rb +48 -0
- data/lib/concurrent/actor/behaviour/awaits.rb +21 -0
- data/lib/concurrent/actor/behaviour/buffer.rb +54 -0
- data/lib/concurrent/actor/behaviour/errors_on_unknown_message.rb +12 -0
- data/lib/concurrent/actor/behaviour/executes_context.rb +18 -0
- data/lib/concurrent/actor/behaviour/linking.rb +42 -0
- data/lib/concurrent/actor/behaviour/pausing.rb +77 -0
- data/lib/concurrent/actor/behaviour/removes_child.rb +16 -0
- data/lib/concurrent/actor/behaviour/sets_results.rb +36 -0
- data/lib/concurrent/actor/behaviour/supervised.rb +58 -0
- data/lib/concurrent/actor/behaviour/supervising.rb +34 -0
- data/lib/concurrent/actor/behaviour/terminates_children.rb +13 -0
- data/lib/concurrent/actor/behaviour/termination.rb +54 -0
- data/lib/concurrent/actor/context.rb +153 -0
- data/lib/concurrent/actor/core.rb +213 -0
- data/lib/concurrent/actor/default_dead_letter_handler.rb +9 -0
- data/lib/concurrent/actor/envelope.rb +41 -0
- data/lib/concurrent/actor/errors.rb +27 -0
- data/lib/concurrent/actor/internal_delegations.rb +49 -0
- data/lib/concurrent/actor/public_delegations.rb +40 -0
- data/lib/concurrent/actor/reference.rb +81 -0
- data/lib/concurrent/actor/root.rb +37 -0
- data/lib/concurrent/actor/type_check.rb +48 -0
- data/lib/concurrent/actor/utils.rb +10 -0
- data/lib/concurrent/actor/utils/ad_hoc.rb +21 -0
- data/lib/concurrent/actor/utils/balancer.rb +40 -0
- data/lib/concurrent/actor/utils/broadcast.rb +52 -0
- data/lib/concurrent/actor/utils/pool.rb +59 -0
- data/lib/concurrent/actress.rb +3 -0
- data/lib/concurrent/agent.rb +230 -0
- data/lib/concurrent/async.rb +284 -0
- data/lib/concurrent/atomic.rb +91 -0
- data/lib/concurrent/atomic/atomic_boolean.rb +202 -0
- data/lib/concurrent/atomic/atomic_fixnum.rb +203 -0
- data/lib/concurrent/atomic/condition.rb +67 -0
- data/lib/concurrent/atomic/copy_on_notify_observer_set.rb +118 -0
- data/lib/concurrent/atomic/copy_on_write_observer_set.rb +117 -0
- data/lib/concurrent/atomic/count_down_latch.rb +116 -0
- data/lib/concurrent/atomic/cyclic_barrier.rb +106 -0
- data/lib/concurrent/atomic/event.rb +98 -0
- data/lib/concurrent/atomic/synchronization.rb +51 -0
- data/lib/concurrent/atomic/thread_local_var.rb +82 -0
- data/lib/concurrent/atomic_reference/concurrent_update_error.rb +8 -0
- data/lib/concurrent/atomic_reference/direct_update.rb +50 -0
- data/lib/concurrent/atomic_reference/jruby.rb +14 -0
- data/lib/concurrent/atomic_reference/mutex_atomic.rb +77 -0
- data/lib/concurrent/atomic_reference/numeric_cas_wrapper.rb +25 -0
- data/lib/concurrent/atomic_reference/rbx.rb +19 -0
- data/lib/concurrent/atomic_reference/ruby.rb +37 -0
- data/lib/concurrent/atomics.rb +11 -0
- data/lib/concurrent/channel/buffered_channel.rb +85 -0
- data/lib/concurrent/channel/channel.rb +41 -0
- data/lib/concurrent/channel/unbuffered_channel.rb +35 -0
- data/lib/concurrent/channel/waitable_list.rb +40 -0
- data/lib/concurrent/channels.rb +5 -0
- data/lib/concurrent/collection/blocking_ring_buffer.rb +71 -0
- data/lib/concurrent/collection/priority_queue.rb +305 -0
- data/lib/concurrent/collection/ring_buffer.rb +59 -0
- data/lib/concurrent/collections.rb +3 -0
- data/lib/concurrent/configuration.rb +161 -0
- data/lib/concurrent/dataflow.rb +108 -0
- data/lib/concurrent/delay.rb +104 -0
- data/lib/concurrent/dereferenceable.rb +101 -0
- data/lib/concurrent/errors.rb +30 -0
- data/lib/concurrent/exchanger.rb +34 -0
- data/lib/concurrent/executor/cached_thread_pool.rb +44 -0
- data/lib/concurrent/executor/executor.rb +282 -0
- data/lib/concurrent/executor/fixed_thread_pool.rb +33 -0
- data/lib/concurrent/executor/immediate_executor.rb +65 -0
- data/lib/concurrent/executor/java_cached_thread_pool.rb +31 -0
- data/lib/concurrent/executor/java_fixed_thread_pool.rb +41 -0
- data/lib/concurrent/executor/java_single_thread_executor.rb +22 -0
- data/lib/concurrent/executor/java_thread_pool_executor.rb +180 -0
- data/lib/concurrent/executor/per_thread_executor.rb +100 -0
- data/lib/concurrent/executor/ruby_cached_thread_pool.rb +29 -0
- data/lib/concurrent/executor/ruby_fixed_thread_pool.rb +32 -0
- data/lib/concurrent/executor/ruby_single_thread_executor.rb +74 -0
- data/lib/concurrent/executor/ruby_thread_pool_executor.rb +288 -0
- data/lib/concurrent/executor/ruby_thread_pool_worker.rb +72 -0
- data/lib/concurrent/executor/safe_task_executor.rb +35 -0
- data/lib/concurrent/executor/serialized_execution.rb +126 -0
- data/lib/concurrent/executor/single_thread_executor.rb +35 -0
- data/lib/concurrent/executor/thread_pool_executor.rb +68 -0
- data/lib/concurrent/executor/timer_set.rb +143 -0
- data/lib/concurrent/executors.rb +9 -0
- data/lib/concurrent/future.rb +125 -0
- data/lib/concurrent/ivar.rb +111 -0
- data/lib/concurrent/lazy_register.rb +58 -0
- data/lib/concurrent/logging.rb +17 -0
- data/lib/concurrent/mvar.rb +200 -0
- data/lib/concurrent/obligation.rb +171 -0
- data/lib/concurrent/observable.rb +40 -0
- data/lib/concurrent/options_parser.rb +48 -0
- data/lib/concurrent/promise.rb +170 -0
- data/lib/concurrent/scheduled_task.rb +79 -0
- data/lib/concurrent/timer_task.rb +341 -0
- data/lib/concurrent/tvar.rb +248 -0
- data/lib/concurrent/utilities.rb +3 -0
- data/lib/concurrent/utility/processor_count.rb +152 -0
- data/lib/concurrent/utility/timeout.rb +35 -0
- data/lib/concurrent/utility/timer.rb +21 -0
- data/lib/concurrent/version.rb +3 -0
- data/lib/concurrent_ruby.rb +1 -0
- data/lib/concurrent_ruby_ext.jar +0 -0
- data/lib/concurrent_ruby_ext.so +0 -0
- data/lib/extension_helper.rb +28 -0
- metadata +163 -0
@@ -0,0 +1,67 @@
|
|
1
|
+
module Concurrent
|
2
|
+
|
3
|
+
# Condition is a better implementation of standard Ruby ConditionVariable.
|
4
|
+
# The biggest difference is the wait return value: Condition#wait returns
|
5
|
+
# Condition::Result which make possible to know if waiting thread has been woken up
|
6
|
+
# by an another thread (using #signal or #broadcast) or due to timeout.
|
7
|
+
#
|
8
|
+
# Every #wait must be guarded by a locked Mutex or a ThreadError will be risen.
|
9
|
+
# Although it's not mandatory, it's recommended to call also #signal and #broadcast within
|
10
|
+
# the same mutex
|
11
|
+
class Condition
|
12
|
+
|
13
|
+
class Result
|
14
|
+
def initialize(remaining_time)
|
15
|
+
@remaining_time = remaining_time
|
16
|
+
end
|
17
|
+
|
18
|
+
attr_reader :remaining_time
|
19
|
+
|
20
|
+
# @return [Boolean] true if current thread has been waken up by a #signal or a #broadcast call, otherwise false
|
21
|
+
def woken_up?
|
22
|
+
@remaining_time.nil? || @remaining_time > 0
|
23
|
+
end
|
24
|
+
|
25
|
+
# @return [Boolean] true if current thread has been waken up due to a timeout, otherwise false
|
26
|
+
def timed_out?
|
27
|
+
@remaining_time != nil && @remaining_time <= 0
|
28
|
+
end
|
29
|
+
|
30
|
+
alias_method :can_wait?, :woken_up?
|
31
|
+
|
32
|
+
end
|
33
|
+
|
34
|
+
def initialize
|
35
|
+
@condition = ConditionVariable.new
|
36
|
+
end
|
37
|
+
|
38
|
+
# @param [Mutex] mutex the locked mutex guarding the wait
|
39
|
+
# @param [Object] timeout nil means no timeout
|
40
|
+
# @return [Result]
|
41
|
+
def wait(mutex, timeout = nil)
|
42
|
+
start_time = Time.now.to_f
|
43
|
+
@condition.wait(mutex, timeout)
|
44
|
+
|
45
|
+
if timeout.nil?
|
46
|
+
Result.new(nil)
|
47
|
+
else
|
48
|
+
Result.new(start_time + timeout - Time.now.to_f)
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
# Wakes up a waiting thread
|
53
|
+
# @return [true]
|
54
|
+
def signal
|
55
|
+
@condition.signal
|
56
|
+
true
|
57
|
+
end
|
58
|
+
|
59
|
+
# Wakes up all waiting threads
|
60
|
+
# @return [true]
|
61
|
+
def broadcast
|
62
|
+
@condition.broadcast
|
63
|
+
true
|
64
|
+
end
|
65
|
+
|
66
|
+
end
|
67
|
+
end
|
@@ -0,0 +1,118 @@
|
|
1
|
+
module Concurrent
|
2
|
+
|
3
|
+
# A thread safe observer set implemented using copy-on-read approach:
|
4
|
+
# observers are added and removed from a thread safe collection; every time
|
5
|
+
# a notification is required the internal data structure is copied to
|
6
|
+
# prevent concurrency issues
|
7
|
+
class CopyOnNotifyObserverSet
|
8
|
+
|
9
|
+
def initialize
|
10
|
+
@mutex = Mutex.new
|
11
|
+
@observers = {}
|
12
|
+
end
|
13
|
+
|
14
|
+
# Adds an observer to this set
|
15
|
+
# If a block is passed, the observer will be created by this method and no other params should be passed
|
16
|
+
# @param [Object] observer the observer to add
|
17
|
+
# @param [Symbol] func the function to call on the observer during notification. Default is :update
|
18
|
+
# @return [Object] the added observer
|
19
|
+
def add_observer(observer=nil, func=:update, &block)
|
20
|
+
if observer.nil? && block.nil?
|
21
|
+
raise ArgumentError, 'should pass observer as a first argument or block'
|
22
|
+
elsif observer && block
|
23
|
+
raise ArgumentError.new('cannot provide both an observer and a block')
|
24
|
+
end
|
25
|
+
|
26
|
+
if block
|
27
|
+
observer = block
|
28
|
+
func = :call
|
29
|
+
end
|
30
|
+
|
31
|
+
begin
|
32
|
+
@mutex.lock
|
33
|
+
@observers[observer] = func
|
34
|
+
ensure
|
35
|
+
@mutex.unlock
|
36
|
+
end
|
37
|
+
|
38
|
+
observer
|
39
|
+
end
|
40
|
+
|
41
|
+
# @param [Object] observer the observer to remove
|
42
|
+
# @return [Object] the deleted observer
|
43
|
+
def delete_observer(observer)
|
44
|
+
@mutex.lock
|
45
|
+
@observers.delete(observer)
|
46
|
+
@mutex.unlock
|
47
|
+
|
48
|
+
observer
|
49
|
+
end
|
50
|
+
|
51
|
+
# Deletes all observers
|
52
|
+
# @return [CopyOnWriteObserverSet] self
|
53
|
+
def delete_observers
|
54
|
+
@mutex.lock
|
55
|
+
@observers.clear
|
56
|
+
@mutex.unlock
|
57
|
+
|
58
|
+
self
|
59
|
+
end
|
60
|
+
|
61
|
+
# @return [Integer] the observers count
|
62
|
+
def count_observers
|
63
|
+
@mutex.lock
|
64
|
+
result = @observers.count
|
65
|
+
@mutex.unlock
|
66
|
+
|
67
|
+
result
|
68
|
+
end
|
69
|
+
|
70
|
+
# Notifies all registered observers with optional args
|
71
|
+
# @param [Object] args arguments to be passed to each observer
|
72
|
+
# @return [CopyOnWriteObserverSet] self
|
73
|
+
def notify_observers(*args, &block)
|
74
|
+
observers = duplicate_observers
|
75
|
+
notify_to(observers, *args, &block)
|
76
|
+
|
77
|
+
self
|
78
|
+
end
|
79
|
+
|
80
|
+
# Notifies all registered observers with optional args and deletes them.
|
81
|
+
#
|
82
|
+
# @param [Object] args arguments to be passed to each observer
|
83
|
+
# @return [CopyOnWriteObserverSet] self
|
84
|
+
def notify_and_delete_observers(*args, &block)
|
85
|
+
observers = duplicate_and_clear_observers
|
86
|
+
notify_to(observers, *args, &block)
|
87
|
+
|
88
|
+
self
|
89
|
+
end
|
90
|
+
|
91
|
+
private
|
92
|
+
|
93
|
+
def duplicate_and_clear_observers
|
94
|
+
@mutex.lock
|
95
|
+
observers = @observers.dup
|
96
|
+
@observers.clear
|
97
|
+
@mutex.unlock
|
98
|
+
|
99
|
+
observers
|
100
|
+
end
|
101
|
+
|
102
|
+
def duplicate_observers
|
103
|
+
@mutex.lock
|
104
|
+
observers = @observers.dup
|
105
|
+
@mutex.unlock
|
106
|
+
|
107
|
+
observers
|
108
|
+
end
|
109
|
+
|
110
|
+
def notify_to(observers, *args)
|
111
|
+
raise ArgumentError.new('cannot give arguments and a block') if block_given? && !args.empty?
|
112
|
+
observers.each do |observer, function|
|
113
|
+
args = yield if block_given?
|
114
|
+
observer.send(function, *args)
|
115
|
+
end
|
116
|
+
end
|
117
|
+
end
|
118
|
+
end
|
@@ -0,0 +1,117 @@
|
|
1
|
+
module Concurrent
|
2
|
+
|
3
|
+
# A thread safe observer set implemented using copy-on-write approach:
|
4
|
+
# every time an observer is added or removed the whole internal data structure is
|
5
|
+
# duplicated and replaced with a new one.
|
6
|
+
class CopyOnWriteObserverSet
|
7
|
+
|
8
|
+
def initialize
|
9
|
+
@mutex = Mutex.new
|
10
|
+
@observers = {}
|
11
|
+
end
|
12
|
+
|
13
|
+
# Adds an observer to this set
|
14
|
+
# If a block is passed, the observer will be created by this method and no other params should be passed
|
15
|
+
# @param [Object] observer the observer to add
|
16
|
+
# @param [Symbol] func the function to call on the observer during notification. Default is :update
|
17
|
+
# @return [Object] the added observer
|
18
|
+
def add_observer(observer=nil, func=:update, &block)
|
19
|
+
if observer.nil? && block.nil?
|
20
|
+
raise ArgumentError, 'should pass observer as a first argument or block'
|
21
|
+
elsif observer && block
|
22
|
+
raise ArgumentError.new('cannot provide both an observer and a block')
|
23
|
+
end
|
24
|
+
|
25
|
+
if block
|
26
|
+
observer = block
|
27
|
+
func = :call
|
28
|
+
end
|
29
|
+
|
30
|
+
begin
|
31
|
+
@mutex.lock
|
32
|
+
new_observers = @observers.dup
|
33
|
+
new_observers[observer] = func
|
34
|
+
@observers = new_observers
|
35
|
+
observer
|
36
|
+
ensure
|
37
|
+
@mutex.unlock
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
# @param [Object] observer the observer to remove
|
42
|
+
# @return [Object] the deleted observer
|
43
|
+
def delete_observer(observer)
|
44
|
+
@mutex.lock
|
45
|
+
new_observers = @observers.dup
|
46
|
+
new_observers.delete(observer)
|
47
|
+
@observers = new_observers
|
48
|
+
observer
|
49
|
+
ensure
|
50
|
+
@mutex.unlock
|
51
|
+
end
|
52
|
+
|
53
|
+
# Deletes all observers
|
54
|
+
# @return [CopyOnWriteObserverSet] self
|
55
|
+
def delete_observers
|
56
|
+
self.observers = {}
|
57
|
+
self
|
58
|
+
end
|
59
|
+
|
60
|
+
|
61
|
+
# @return [Integer] the observers count
|
62
|
+
def count_observers
|
63
|
+
observers.count
|
64
|
+
end
|
65
|
+
|
66
|
+
# Notifies all registered observers with optional args
|
67
|
+
# @param [Object] args arguments to be passed to each observer
|
68
|
+
# @return [CopyOnWriteObserverSet] self
|
69
|
+
def notify_observers(*args, &block)
|
70
|
+
notify_to(observers, *args, &block)
|
71
|
+
self
|
72
|
+
end
|
73
|
+
|
74
|
+
# Notifies all registered observers with optional args and deletes them.
|
75
|
+
#
|
76
|
+
# @param [Object] args arguments to be passed to each observer
|
77
|
+
# @return [CopyOnWriteObserverSet] self
|
78
|
+
def notify_and_delete_observers(*args, &block)
|
79
|
+
old = clear_observers_and_return_old
|
80
|
+
notify_to(old, *args, &block)
|
81
|
+
self
|
82
|
+
end
|
83
|
+
|
84
|
+
private
|
85
|
+
|
86
|
+
def notify_to(observers, *args)
|
87
|
+
raise ArgumentError.new('cannot give arguments and a block') if block_given? && !args.empty?
|
88
|
+
observers.each do |observer, function|
|
89
|
+
args = yield if block_given?
|
90
|
+
observer.send(function, *args)
|
91
|
+
end
|
92
|
+
end
|
93
|
+
|
94
|
+
def observers
|
95
|
+
@mutex.lock
|
96
|
+
@observers
|
97
|
+
ensure
|
98
|
+
@mutex.unlock
|
99
|
+
end
|
100
|
+
|
101
|
+
def observers=(new_set)
|
102
|
+
@mutex.lock
|
103
|
+
@observers = new_set
|
104
|
+
ensure
|
105
|
+
@mutex.unlock
|
106
|
+
end
|
107
|
+
|
108
|
+
def clear_observers_and_return_old
|
109
|
+
@mutex.lock
|
110
|
+
old_observers = @observers
|
111
|
+
@observers = {}
|
112
|
+
old_observers
|
113
|
+
ensure
|
114
|
+
@mutex.unlock
|
115
|
+
end
|
116
|
+
end
|
117
|
+
end
|
@@ -0,0 +1,116 @@
|
|
1
|
+
require 'concurrent/atomic/condition'
|
2
|
+
|
3
|
+
module Concurrent
|
4
|
+
|
5
|
+
# @!macro [attach] count_down_latch
|
6
|
+
#
|
7
|
+
# A synchronization object that allows one thread to wait on multiple other threads.
|
8
|
+
# The thread that will wait creates a `CountDownLatch` and sets the initial value
|
9
|
+
# (normally equal to the number of other threads). The initiating thread passes the
|
10
|
+
# latch to the other threads then waits for the other threads by calling the `#wait`
|
11
|
+
# method. Each of the other threads calls `#count_down` when done with its work.
|
12
|
+
# When the latch counter reaches zero the waiting thread is unblocked and continues
|
13
|
+
# with its work. A `CountDownLatch` can be used only once. Its value cannot be reset.
|
14
|
+
class MutexCountDownLatch
|
15
|
+
|
16
|
+
# @!macro [attach] count_down_latch_method_initialize
|
17
|
+
#
|
18
|
+
# Create a new `CountDownLatch` with the initial `count`.
|
19
|
+
#
|
20
|
+
# @param [Fixnum] count the initial count
|
21
|
+
#
|
22
|
+
# @raise [ArgumentError] if `count` is not an integer or is less than zero
|
23
|
+
def initialize(count)
|
24
|
+
unless count.is_a?(Fixnum) && count >= 0
|
25
|
+
raise ArgumentError.new('count must be in integer greater than or equal zero')
|
26
|
+
end
|
27
|
+
@mutex = Mutex.new
|
28
|
+
@condition = Condition.new
|
29
|
+
@count = count
|
30
|
+
end
|
31
|
+
|
32
|
+
# @!macro [attach] count_down_latch_method_wait
|
33
|
+
#
|
34
|
+
# Block on the latch until the counter reaches zero or until `timeout` is reached.
|
35
|
+
#
|
36
|
+
# @param [Fixnum] timeout the number of seconds to wait for the counter or `nil`
|
37
|
+
# to block indefinitely
|
38
|
+
# @return [Boolean] `true` if the `count` reaches zero else false on `timeout`
|
39
|
+
def wait(timeout = nil)
|
40
|
+
@mutex.synchronize do
|
41
|
+
|
42
|
+
remaining = Condition::Result.new(timeout)
|
43
|
+
while @count > 0 && remaining.can_wait?
|
44
|
+
remaining = @condition.wait(@mutex, remaining.remaining_time)
|
45
|
+
end
|
46
|
+
|
47
|
+
@count == 0
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
# @!macro [attach] count_down_latch_method_count_down
|
52
|
+
#
|
53
|
+
# Signal the latch to decrement the counter. Will signal all blocked threads when
|
54
|
+
# the `count` reaches zero.
|
55
|
+
def count_down
|
56
|
+
@mutex.synchronize do
|
57
|
+
@count -= 1 if @count > 0
|
58
|
+
@condition.broadcast if @count == 0
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
62
|
+
# @!macro [attach] count_down_latch_method_count
|
63
|
+
#
|
64
|
+
# The current value of the counter.
|
65
|
+
#
|
66
|
+
# @return [Fixnum] the current value of the counter
|
67
|
+
def count
|
68
|
+
@mutex.synchronize { @count }
|
69
|
+
end
|
70
|
+
end
|
71
|
+
|
72
|
+
if RUBY_PLATFORM == 'java'
|
73
|
+
|
74
|
+
# @!macro count_down_latch
|
75
|
+
class JavaCountDownLatch
|
76
|
+
|
77
|
+
# @!macro count_down_latch_method_initialize
|
78
|
+
def initialize(count)
|
79
|
+
unless count.is_a?(Fixnum) && count >= 0
|
80
|
+
raise ArgumentError.new('count must be in integer greater than or equal zero')
|
81
|
+
end
|
82
|
+
@latch = java.util.concurrent.CountDownLatch.new(count)
|
83
|
+
end
|
84
|
+
|
85
|
+
# @!macro count_down_latch_method_wait
|
86
|
+
def wait(timeout = nil)
|
87
|
+
if timeout.nil?
|
88
|
+
@latch.await
|
89
|
+
true
|
90
|
+
else
|
91
|
+
@latch.await(1000 * timeout, java.util.concurrent.TimeUnit::MILLISECONDS)
|
92
|
+
end
|
93
|
+
end
|
94
|
+
|
95
|
+
# @!macro count_down_latch_method_count_down
|
96
|
+
def count_down
|
97
|
+
@latch.countDown
|
98
|
+
end
|
99
|
+
|
100
|
+
# @!macro count_down_latch_method_count
|
101
|
+
def count
|
102
|
+
@latch.getCount
|
103
|
+
end
|
104
|
+
end
|
105
|
+
|
106
|
+
# @!macro count_down_latch
|
107
|
+
class CountDownLatch < JavaCountDownLatch
|
108
|
+
end
|
109
|
+
|
110
|
+
else
|
111
|
+
|
112
|
+
# @!macro count_down_latch
|
113
|
+
class CountDownLatch < MutexCountDownLatch
|
114
|
+
end
|
115
|
+
end
|
116
|
+
end
|
@@ -0,0 +1,106 @@
|
|
1
|
+
module Concurrent
|
2
|
+
|
3
|
+
class CyclicBarrier
|
4
|
+
|
5
|
+
Generation = Struct.new(:status)
|
6
|
+
private_constant :Generation
|
7
|
+
|
8
|
+
# Create a new `CyclicBarrier` that waits for `parties` threads
|
9
|
+
#
|
10
|
+
# @param [Fixnum] parties the number of parties
|
11
|
+
# @yield an optional block that will be executed that will be executed after the last thread arrives and before the others are released
|
12
|
+
#
|
13
|
+
# @raise [ArgumentError] if `parties` is not an integer or is less than zero
|
14
|
+
def initialize(parties, &block)
|
15
|
+
raise ArgumentError.new('count must be in integer greater than or equal zero') if !parties.is_a?(Fixnum) || parties < 1
|
16
|
+
@parties = parties
|
17
|
+
@mutex = Mutex.new
|
18
|
+
@condition = Condition.new
|
19
|
+
@number_waiting = 0
|
20
|
+
@action = block
|
21
|
+
@generation = Generation.new(:waiting)
|
22
|
+
end
|
23
|
+
|
24
|
+
# @return [Fixnum] the number of threads needed to pass the barrier
|
25
|
+
def parties
|
26
|
+
@parties
|
27
|
+
end
|
28
|
+
|
29
|
+
# @return [Fixnum] the number of threads currently waiting on the barrier
|
30
|
+
def number_waiting
|
31
|
+
@number_waiting
|
32
|
+
end
|
33
|
+
|
34
|
+
# Blocks on the barrier until the number of waiting threads is equal to `parties` or until `timeout` is reached or `reset` is called
|
35
|
+
# If a block has been passed to the constructor, it will be executed once by the last arrived thread before releasing the others
|
36
|
+
# @param [Fixnum] timeout the number of seconds to wait for the counter or `nil` to block indefinitely
|
37
|
+
# @return [Boolean] `true` if the `count` reaches zero else false on `timeout` or on `reset` or if the barrier is broken
|
38
|
+
def wait(timeout = nil)
|
39
|
+
@mutex.synchronize do
|
40
|
+
|
41
|
+
return false unless @generation.status == :waiting
|
42
|
+
|
43
|
+
@number_waiting += 1
|
44
|
+
|
45
|
+
if @number_waiting == @parties
|
46
|
+
@action.call if @action
|
47
|
+
set_status_and_restore(:fulfilled)
|
48
|
+
true
|
49
|
+
else
|
50
|
+
wait_for_wake_up(@generation, timeout)
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
|
56
|
+
|
57
|
+
# resets the barrier to its initial state
|
58
|
+
# If there is at least one waiting thread, it will be woken up, the `wait` method will return false and the barrier will be broken
|
59
|
+
# If the barrier is broken, this method restores it to the original state
|
60
|
+
#
|
61
|
+
# @return [nil]
|
62
|
+
def reset
|
63
|
+
@mutex.synchronize do
|
64
|
+
set_status_and_restore(:reset)
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
68
|
+
# A barrier can be broken when:
|
69
|
+
# - a thread called the `reset` method while at least one other thread was waiting
|
70
|
+
# - at least one thread timed out on `wait` method
|
71
|
+
#
|
72
|
+
# A broken barrier can be restored using `reset` it's safer to create a new one
|
73
|
+
# @return [Boolean] true if the barrier is broken otherwise false
|
74
|
+
def broken?
|
75
|
+
@mutex.synchronize { @generation.status != :waiting }
|
76
|
+
end
|
77
|
+
|
78
|
+
private
|
79
|
+
|
80
|
+
def set_status_and_restore(new_status)
|
81
|
+
@generation.status = new_status
|
82
|
+
@condition.broadcast
|
83
|
+
@generation = Generation.new(:waiting)
|
84
|
+
@number_waiting = 0
|
85
|
+
end
|
86
|
+
|
87
|
+
def wait_for_wake_up(generation, timeout)
|
88
|
+
if wait_while_waiting(generation, timeout)
|
89
|
+
generation.status == :fulfilled
|
90
|
+
else
|
91
|
+
generation.status = :broken
|
92
|
+
@condition.broadcast
|
93
|
+
false
|
94
|
+
end
|
95
|
+
end
|
96
|
+
|
97
|
+
def wait_while_waiting(generation, timeout)
|
98
|
+
remaining = Condition::Result.new(timeout)
|
99
|
+
while generation.status == :waiting && remaining.can_wait?
|
100
|
+
remaining = @condition.wait(@mutex, remaining.remaining_time)
|
101
|
+
end
|
102
|
+
remaining.woken_up?
|
103
|
+
end
|
104
|
+
|
105
|
+
end
|
106
|
+
end
|