concurrent-ruby 0.8.0.pre2-java → 0.9.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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +114 -3
- data/README.md +111 -55
- data/lib/concurrent.rb +90 -14
- data/lib/concurrent/async.rb +143 -51
- data/lib/concurrent/atom.rb +131 -0
- data/lib/concurrent/atomic/atomic_boolean.rb +57 -107
- data/lib/concurrent/atomic/atomic_fixnum.rb +73 -101
- data/lib/concurrent/atomic/atomic_reference.rb +49 -0
- data/lib/concurrent/atomic/condition.rb +23 -12
- data/lib/concurrent/atomic/count_down_latch.rb +23 -21
- data/lib/concurrent/atomic/cyclic_barrier.rb +47 -47
- data/lib/concurrent/atomic/event.rb +33 -42
- data/lib/concurrent/atomic/read_write_lock.rb +252 -0
- data/lib/concurrent/atomic/semaphore.rb +64 -89
- data/lib/concurrent/atomic/thread_local_var.rb +130 -58
- data/lib/concurrent/atomic/thread_local_var/weak_key_map.rb +236 -0
- data/lib/concurrent/atomic_reference/direct_update.rb +34 -3
- data/lib/concurrent/atomic_reference/jruby.rb +6 -3
- data/lib/concurrent/atomic_reference/mutex_atomic.rb +17 -39
- data/lib/concurrent/atomic_reference/numeric_cas_wrapper.rb +3 -0
- data/lib/concurrent/atomic_reference/rbx.rb +4 -1
- data/lib/concurrent/atomic_reference/ruby.rb +6 -3
- data/lib/concurrent/atomics.rb +74 -4
- data/lib/concurrent/collection/copy_on_notify_observer_set.rb +115 -0
- data/lib/concurrent/collection/copy_on_write_observer_set.rb +119 -0
- data/lib/concurrent/collection/priority_queue.rb +300 -245
- data/lib/concurrent/concern/deprecation.rb +34 -0
- data/lib/concurrent/concern/dereferenceable.rb +88 -0
- data/lib/concurrent/concern/logging.rb +27 -0
- data/lib/concurrent/concern/obligation.rb +228 -0
- data/lib/concurrent/concern/observable.rb +85 -0
- data/lib/concurrent/configuration.rb +234 -109
- data/lib/concurrent/dataflow.rb +2 -3
- data/lib/concurrent/delay.rb +141 -50
- data/lib/concurrent/edge.rb +30 -0
- data/lib/concurrent/errors.rb +19 -7
- data/lib/concurrent/exchanger.rb +25 -1
- data/lib/concurrent/executor/cached_thread_pool.rb +51 -33
- data/lib/concurrent/executor/executor.rb +46 -299
- data/lib/concurrent/executor/executor_service.rb +521 -0
- data/lib/concurrent/executor/fixed_thread_pool.rb +196 -23
- data/lib/concurrent/executor/immediate_executor.rb +9 -9
- data/lib/concurrent/executor/indirect_immediate_executor.rb +4 -3
- data/lib/concurrent/executor/java_single_thread_executor.rb +17 -16
- data/lib/concurrent/executor/java_thread_pool_executor.rb +55 -102
- data/lib/concurrent/executor/ruby_single_thread_executor.rb +14 -16
- data/lib/concurrent/executor/ruby_thread_pool_executor.rb +250 -166
- data/lib/concurrent/executor/safe_task_executor.rb +5 -4
- data/lib/concurrent/executor/serialized_execution.rb +22 -18
- data/lib/concurrent/executor/{per_thread_executor.rb → simple_executor_service.rb} +29 -20
- data/lib/concurrent/executor/single_thread_executor.rb +32 -21
- data/lib/concurrent/executor/thread_pool_executor.rb +73 -60
- data/lib/concurrent/executor/timer_set.rb +96 -84
- data/lib/concurrent/executors.rb +1 -1
- data/lib/concurrent/future.rb +71 -38
- data/lib/concurrent/immutable_struct.rb +89 -0
- data/lib/concurrent/ivar.rb +152 -60
- data/lib/concurrent/lazy_register.rb +40 -20
- data/lib/concurrent/maybe.rb +226 -0
- data/lib/concurrent/mutable_struct.rb +227 -0
- data/lib/concurrent/mvar.rb +44 -43
- data/lib/concurrent/promise.rb +229 -136
- data/lib/concurrent/scheduled_task.rb +341 -43
- data/lib/concurrent/settable_struct.rb +127 -0
- data/lib/concurrent/synchronization.rb +17 -0
- data/lib/concurrent/synchronization/abstract_object.rb +163 -0
- data/lib/concurrent/synchronization/abstract_struct.rb +158 -0
- data/lib/concurrent/synchronization/condition.rb +53 -0
- data/lib/concurrent/synchronization/java_object.rb +34 -0
- data/lib/concurrent/synchronization/lock.rb +32 -0
- data/lib/concurrent/synchronization/monitor_object.rb +26 -0
- data/lib/concurrent/synchronization/mutex_object.rb +43 -0
- data/lib/concurrent/synchronization/object.rb +78 -0
- data/lib/concurrent/synchronization/rbx_object.rb +75 -0
- data/lib/concurrent/timer_task.rb +92 -103
- data/lib/concurrent/tvar.rb +42 -38
- data/lib/concurrent/utilities.rb +3 -1
- data/lib/concurrent/utility/at_exit.rb +97 -0
- data/lib/concurrent/utility/engine.rb +44 -0
- data/lib/concurrent/utility/monotonic_time.rb +59 -0
- data/lib/concurrent/utility/native_extension_loader.rb +56 -0
- data/lib/concurrent/utility/processor_counter.rb +156 -0
- data/lib/concurrent/utility/timeout.rb +18 -14
- data/lib/concurrent/utility/timer.rb +11 -6
- data/lib/concurrent/version.rb +2 -1
- data/lib/concurrent_ruby.rb +1 -0
- data/lib/concurrent_ruby_ext.jar +0 -0
- metadata +46 -66
- data/lib/concurrent/actor.rb +0 -103
- data/lib/concurrent/actor/behaviour.rb +0 -70
- data/lib/concurrent/actor/behaviour/abstract.rb +0 -48
- data/lib/concurrent/actor/behaviour/awaits.rb +0 -21
- data/lib/concurrent/actor/behaviour/buffer.rb +0 -54
- data/lib/concurrent/actor/behaviour/errors_on_unknown_message.rb +0 -12
- data/lib/concurrent/actor/behaviour/executes_context.rb +0 -18
- data/lib/concurrent/actor/behaviour/linking.rb +0 -45
- data/lib/concurrent/actor/behaviour/pausing.rb +0 -77
- data/lib/concurrent/actor/behaviour/removes_child.rb +0 -16
- data/lib/concurrent/actor/behaviour/sets_results.rb +0 -36
- data/lib/concurrent/actor/behaviour/supervised.rb +0 -59
- data/lib/concurrent/actor/behaviour/supervising.rb +0 -34
- data/lib/concurrent/actor/behaviour/terminates_children.rb +0 -13
- data/lib/concurrent/actor/behaviour/termination.rb +0 -54
- data/lib/concurrent/actor/context.rb +0 -154
- data/lib/concurrent/actor/core.rb +0 -217
- data/lib/concurrent/actor/default_dead_letter_handler.rb +0 -9
- data/lib/concurrent/actor/envelope.rb +0 -41
- data/lib/concurrent/actor/errors.rb +0 -27
- data/lib/concurrent/actor/internal_delegations.rb +0 -49
- data/lib/concurrent/actor/public_delegations.rb +0 -40
- data/lib/concurrent/actor/reference.rb +0 -81
- data/lib/concurrent/actor/root.rb +0 -37
- data/lib/concurrent/actor/type_check.rb +0 -48
- data/lib/concurrent/actor/utils.rb +0 -10
- data/lib/concurrent/actor/utils/ad_hoc.rb +0 -21
- data/lib/concurrent/actor/utils/balancer.rb +0 -42
- data/lib/concurrent/actor/utils/broadcast.rb +0 -52
- data/lib/concurrent/actor/utils/pool.rb +0 -59
- data/lib/concurrent/actress.rb +0 -3
- data/lib/concurrent/agent.rb +0 -209
- data/lib/concurrent/atomic.rb +0 -92
- data/lib/concurrent/atomic/copy_on_notify_observer_set.rb +0 -118
- data/lib/concurrent/atomic/copy_on_write_observer_set.rb +0 -117
- data/lib/concurrent/atomic/synchronization.rb +0 -51
- data/lib/concurrent/channel/buffered_channel.rb +0 -85
- data/lib/concurrent/channel/channel.rb +0 -41
- data/lib/concurrent/channel/unbuffered_channel.rb +0 -35
- data/lib/concurrent/channel/waitable_list.rb +0 -40
- data/lib/concurrent/channels.rb +0 -5
- data/lib/concurrent/collection/blocking_ring_buffer.rb +0 -71
- data/lib/concurrent/collection/ring_buffer.rb +0 -59
- data/lib/concurrent/collections.rb +0 -3
- data/lib/concurrent/dereferenceable.rb +0 -108
- data/lib/concurrent/executor/java_cached_thread_pool.rb +0 -32
- data/lib/concurrent/executor/java_fixed_thread_pool.rb +0 -31
- data/lib/concurrent/executor/ruby_cached_thread_pool.rb +0 -29
- data/lib/concurrent/executor/ruby_fixed_thread_pool.rb +0 -32
- data/lib/concurrent/executor/ruby_thread_pool_worker.rb +0 -73
- data/lib/concurrent/logging.rb +0 -20
- data/lib/concurrent/obligation.rb +0 -171
- data/lib/concurrent/observable.rb +0 -73
- data/lib/concurrent/options_parser.rb +0 -48
- data/lib/concurrent/utility/processor_count.rb +0 -152
- data/lib/extension_helper.rb +0 -37
@@ -1,42 +1,48 @@
|
|
1
|
+
require 'concurrent/synchronization'
|
2
|
+
|
1
3
|
module Concurrent
|
2
4
|
|
3
|
-
class CyclicBarrier
|
5
|
+
class CyclicBarrier < Synchronization::Object
|
4
6
|
|
7
|
+
# @!visibility private
|
5
8
|
Generation = Struct.new(:status)
|
6
9
|
private_constant :Generation
|
7
10
|
|
8
11
|
# Create a new `CyclicBarrier` that waits for `parties` threads
|
9
12
|
#
|
10
13
|
# @param [Fixnum] parties the number of parties
|
11
|
-
# @yield an optional block that will be executed that will be executed after
|
14
|
+
# @yield an optional block that will be executed that will be executed after
|
15
|
+
# the last thread arrives and before the others are released
|
12
16
|
#
|
13
17
|
# @raise [ArgumentError] if `parties` is not an integer or is less than zero
|
14
18
|
def initialize(parties, &block)
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
@action = block
|
21
|
-
@generation = Generation.new(:waiting)
|
19
|
+
if !parties.is_a?(Fixnum) || parties < 1
|
20
|
+
raise ArgumentError.new('count must be in integer greater than or equal zero')
|
21
|
+
end
|
22
|
+
super(&nil)
|
23
|
+
synchronize { ns_initialize parties, &block }
|
22
24
|
end
|
23
25
|
|
24
26
|
# @return [Fixnum] the number of threads needed to pass the barrier
|
25
27
|
def parties
|
26
|
-
@parties
|
28
|
+
synchronize { @parties }
|
27
29
|
end
|
28
30
|
|
29
31
|
# @return [Fixnum] the number of threads currently waiting on the barrier
|
30
32
|
def number_waiting
|
31
|
-
@number_waiting
|
33
|
+
synchronize { @number_waiting }
|
32
34
|
end
|
33
35
|
|
34
|
-
# Blocks on the barrier until the number of waiting threads is equal to
|
35
|
-
#
|
36
|
-
#
|
37
|
-
#
|
36
|
+
# Blocks on the barrier until the number of waiting threads is equal to
|
37
|
+
# `parties` or until `timeout` is reached or `reset` is called
|
38
|
+
# If a block has been passed to the constructor, it will be executed once by
|
39
|
+
# the last arrived thread before releasing the others
|
40
|
+
# @param [Fixnum] timeout the number of seconds to wait for the counter or
|
41
|
+
# `nil` to block indefinitely
|
42
|
+
# @return [Boolean] `true` if the `count` reaches zero else false on
|
43
|
+
# `timeout` or on `reset` or if the barrier is broken
|
38
44
|
def wait(timeout = nil)
|
39
|
-
|
45
|
+
synchronize do
|
40
46
|
|
41
47
|
return false unless @generation.status == :waiting
|
42
48
|
|
@@ -44,25 +50,28 @@ module Concurrent
|
|
44
50
|
|
45
51
|
if @number_waiting == @parties
|
46
52
|
@action.call if @action
|
47
|
-
|
53
|
+
ns_generation_done @generation, :fulfilled
|
48
54
|
true
|
49
55
|
else
|
50
|
-
|
56
|
+
generation = @generation
|
57
|
+
if ns_wait_until(timeout) { generation.status != :waiting }
|
58
|
+
generation.status == :fulfilled
|
59
|
+
else
|
60
|
+
ns_generation_done generation, :broken, false
|
61
|
+
false
|
62
|
+
end
|
51
63
|
end
|
52
64
|
end
|
53
65
|
end
|
54
66
|
|
55
|
-
|
56
|
-
|
57
67
|
# resets the barrier to its initial state
|
58
|
-
# If there is at least one waiting thread, it will be woken up, the `wait`
|
68
|
+
# If there is at least one waiting thread, it will be woken up, the `wait`
|
69
|
+
# method will return false and the barrier will be broken
|
59
70
|
# If the barrier is broken, this method restores it to the original state
|
60
71
|
#
|
61
72
|
# @return [nil]
|
62
73
|
def reset
|
63
|
-
|
64
|
-
set_status_and_restore(:reset)
|
65
|
-
end
|
74
|
+
synchronize { ns_generation_done @generation, :reset }
|
66
75
|
end
|
67
76
|
|
68
77
|
# A barrier can be broken when:
|
@@ -72,35 +81,26 @@ module Concurrent
|
|
72
81
|
# A broken barrier can be restored using `reset` it's safer to create a new one
|
73
82
|
# @return [Boolean] true if the barrier is broken otherwise false
|
74
83
|
def broken?
|
75
|
-
|
84
|
+
synchronize { @generation.status != :waiting }
|
76
85
|
end
|
77
86
|
|
78
|
-
|
87
|
+
protected
|
79
88
|
|
80
|
-
def
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
@number_waiting = 0
|
89
|
+
def ns_generation_done(generation, status, continue = true)
|
90
|
+
generation.status = status
|
91
|
+
ns_next_generation if continue
|
92
|
+
ns_broadcast
|
85
93
|
end
|
86
94
|
|
87
|
-
def
|
88
|
-
|
89
|
-
|
90
|
-
else
|
91
|
-
generation.status = :broken
|
92
|
-
@condition.broadcast
|
93
|
-
false
|
94
|
-
end
|
95
|
+
def ns_next_generation
|
96
|
+
@generation = Generation.new(:waiting)
|
97
|
+
@number_waiting = 0
|
95
98
|
end
|
96
99
|
|
97
|
-
def
|
98
|
-
|
99
|
-
|
100
|
-
|
101
|
-
end
|
102
|
-
remaining.woken_up?
|
100
|
+
def ns_initialize(parties, &block)
|
101
|
+
@parties = parties
|
102
|
+
@action = block
|
103
|
+
ns_next_generation
|
103
104
|
end
|
104
|
-
|
105
105
|
end
|
106
|
-
end
|
106
|
+
end
|
@@ -1,5 +1,5 @@
|
|
1
1
|
require 'thread'
|
2
|
-
require 'concurrent/
|
2
|
+
require 'concurrent/synchronization'
|
3
3
|
|
4
4
|
module Concurrent
|
5
5
|
|
@@ -13,24 +13,20 @@ module Concurrent
|
|
13
13
|
# `#reset` at any time once it has been set.
|
14
14
|
#
|
15
15
|
# @see http://msdn.microsoft.com/en-us/library/windows/desktop/ms682655.aspx
|
16
|
-
class Event
|
16
|
+
class Event < Synchronization::Object
|
17
17
|
|
18
18
|
# Creates a new `Event` in the unset state. Threads calling `#wait` on the
|
19
19
|
# `Event` will block.
|
20
20
|
def initialize
|
21
|
-
|
22
|
-
|
23
|
-
@condition = Condition.new
|
21
|
+
super
|
22
|
+
synchronize { ns_initialize }
|
24
23
|
end
|
25
24
|
|
26
25
|
# Is the object in the set state?
|
27
26
|
#
|
28
27
|
# @return [Boolean] indicating whether or not the `Event` has been set
|
29
28
|
def set?
|
30
|
-
@
|
31
|
-
@set
|
32
|
-
ensure
|
33
|
-
@mutex.unlock
|
29
|
+
synchronize { @set }
|
34
30
|
end
|
35
31
|
|
36
32
|
# Trigger the event, setting the state to `set` and releasing all threads
|
@@ -38,29 +34,11 @@ module Concurrent
|
|
38
34
|
#
|
39
35
|
# @return [Boolean] should always return `true`
|
40
36
|
def set
|
41
|
-
|
42
|
-
unless @set
|
43
|
-
@set = true
|
44
|
-
@condition.broadcast
|
45
|
-
end
|
46
|
-
true
|
47
|
-
ensure
|
48
|
-
@mutex.unlock
|
37
|
+
synchronize { ns_set }
|
49
38
|
end
|
50
39
|
|
51
40
|
def try?
|
52
|
-
@
|
53
|
-
|
54
|
-
if @set
|
55
|
-
false
|
56
|
-
else
|
57
|
-
@set = true
|
58
|
-
@condition.broadcast
|
59
|
-
true
|
60
|
-
end
|
61
|
-
|
62
|
-
ensure
|
63
|
-
@mutex.unlock
|
41
|
+
synchronize { @set ? false : ns_set }
|
64
42
|
end
|
65
43
|
|
66
44
|
# Reset a previously set event back to the `unset` state.
|
@@ -68,11 +46,13 @@ module Concurrent
|
|
68
46
|
#
|
69
47
|
# @return [Boolean] should always return `true`
|
70
48
|
def reset
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
49
|
+
synchronize do
|
50
|
+
if @set
|
51
|
+
@set = false
|
52
|
+
@iteration +=1
|
53
|
+
end
|
54
|
+
true
|
55
|
+
end
|
76
56
|
end
|
77
57
|
|
78
58
|
# Wait a given number of seconds for the `Event` to be set by another
|
@@ -81,18 +61,29 @@ module Concurrent
|
|
81
61
|
#
|
82
62
|
# @return [Boolean] true if the `Event` was set before timeout else false
|
83
63
|
def wait(timeout = nil)
|
84
|
-
|
64
|
+
synchronize do
|
65
|
+
unless @set
|
66
|
+
iteration = @iteration
|
67
|
+
ns_wait_until(timeout) { iteration < @iteration || @set }
|
68
|
+
else
|
69
|
+
true
|
70
|
+
end
|
71
|
+
end
|
72
|
+
end
|
85
73
|
|
74
|
+
protected
|
75
|
+
|
76
|
+
def ns_set
|
86
77
|
unless @set
|
87
|
-
|
88
|
-
|
89
|
-
remaining = @condition.wait(@mutex, remaining.remaining_time)
|
90
|
-
end
|
78
|
+
@set = true
|
79
|
+
ns_broadcast
|
91
80
|
end
|
81
|
+
true
|
82
|
+
end
|
92
83
|
|
93
|
-
|
94
|
-
|
95
|
-
@
|
84
|
+
def ns_initialize
|
85
|
+
@set = false
|
86
|
+
@iteration = 0
|
96
87
|
end
|
97
88
|
end
|
98
89
|
end
|
@@ -0,0 +1,252 @@
|
|
1
|
+
require 'thread'
|
2
|
+
require 'concurrent/atomic/atomic_reference'
|
3
|
+
require 'concurrent/errors'
|
4
|
+
require 'concurrent/synchronization'
|
5
|
+
|
6
|
+
module Concurrent
|
7
|
+
|
8
|
+
# Ruby read-write lock implementation
|
9
|
+
#
|
10
|
+
# Allows any number of concurrent readers, but only one concurrent writer
|
11
|
+
# (And if the "write" lock is taken, any readers who come along will have to wait)
|
12
|
+
#
|
13
|
+
# If readers are already active when a writer comes along, the writer will wait for
|
14
|
+
# all the readers to finish before going ahead.
|
15
|
+
# Any additional readers that come when the writer is already waiting, will also
|
16
|
+
# wait (so writers are not starved).
|
17
|
+
#
|
18
|
+
# This implementation is based on `java.util.concurrent.ReentrantReadWriteLock`.
|
19
|
+
#
|
20
|
+
# @example
|
21
|
+
# lock = Concurrent::ReadWriteLock.new
|
22
|
+
# lock.with_read_lock { data.retrieve }
|
23
|
+
# lock.with_write_lock { data.modify! }
|
24
|
+
#
|
25
|
+
# @note Do **not** try to acquire the write lock while already holding a read lock
|
26
|
+
# **or** try to acquire the write lock while you already have it.
|
27
|
+
# This will lead to deadlock
|
28
|
+
#
|
29
|
+
# @see http://docs.oracle.com/javase/7/docs/api/java/util/concurrent/locks/ReentrantReadWriteLock.html java.util.concurrent.ReentrantReadWriteLock
|
30
|
+
class ReadWriteLock < Synchronization::Object
|
31
|
+
|
32
|
+
# @!visibility private
|
33
|
+
WAITING_WRITER = 1 << 15
|
34
|
+
|
35
|
+
# @!visibility private
|
36
|
+
RUNNING_WRITER = 1 << 30
|
37
|
+
|
38
|
+
# @!visibility private
|
39
|
+
MAX_READERS = WAITING_WRITER - 1
|
40
|
+
|
41
|
+
# @!visibility private
|
42
|
+
MAX_WRITERS = RUNNING_WRITER - MAX_READERS - 1
|
43
|
+
|
44
|
+
# Implementation notes:
|
45
|
+
# A goal is to make the uncontended path for both readers/writers lock-free
|
46
|
+
# Only if there is reader-writer or writer-writer contention, should locks be used
|
47
|
+
# Internal state is represented by a single integer ("counter"), and updated
|
48
|
+
# using atomic compare-and-swap operations
|
49
|
+
# When the counter is 0, the lock is free
|
50
|
+
# Each reader increments the counter by 1 when acquiring a read lock
|
51
|
+
# (and decrements by 1 when releasing the read lock)
|
52
|
+
# The counter is increased by (1 << 15) for each writer waiting to acquire the
|
53
|
+
# write lock, and by (1 << 30) if the write lock is taken
|
54
|
+
|
55
|
+
# Create a new `ReadWriteLock` in the unlocked state.
|
56
|
+
def initialize
|
57
|
+
@Counter = AtomicReference.new(0) # single integer which represents lock state
|
58
|
+
@ReadLock = Synchronization::Lock.new
|
59
|
+
@WriteLock = Synchronization::Lock.new
|
60
|
+
ensure_ivar_visibility!
|
61
|
+
super()
|
62
|
+
end
|
63
|
+
|
64
|
+
# Execute a block operation within a read lock.
|
65
|
+
#
|
66
|
+
# @yield the task to be performed within the lock.
|
67
|
+
#
|
68
|
+
# @return [Object] the result of the block operation.
|
69
|
+
#
|
70
|
+
# @raise [ArgumentError] when no block is given.
|
71
|
+
# @raise [Concurrent::ResourceLimitError] if the maximum number of readers
|
72
|
+
# is exceeded.
|
73
|
+
def with_read_lock
|
74
|
+
raise ArgumentError.new('no block given') unless block_given?
|
75
|
+
acquire_read_lock
|
76
|
+
begin
|
77
|
+
yield
|
78
|
+
ensure
|
79
|
+
release_read_lock
|
80
|
+
end
|
81
|
+
end
|
82
|
+
|
83
|
+
# Execute a block operation within a write lock.
|
84
|
+
#
|
85
|
+
# @yield the task to be performed within the lock.
|
86
|
+
#
|
87
|
+
# @return [Object] the result of the block operation.
|
88
|
+
#
|
89
|
+
# @raise [ArgumentError] when no block is given.
|
90
|
+
# @raise [Concurrent::ResourceLimitError] if the maximum number of readers
|
91
|
+
# is exceeded.
|
92
|
+
def with_write_lock
|
93
|
+
raise ArgumentError.new('no block given') unless block_given?
|
94
|
+
acquire_write_lock
|
95
|
+
begin
|
96
|
+
yield
|
97
|
+
ensure
|
98
|
+
release_write_lock
|
99
|
+
end
|
100
|
+
end
|
101
|
+
|
102
|
+
# Acquire a read lock. If a write lock has been acquired will block until
|
103
|
+
# it is released. Will not block if other read locks have been acquired.
|
104
|
+
#
|
105
|
+
# @return [Boolean] true if the lock is successfully acquired
|
106
|
+
#
|
107
|
+
# @raise [Concurrent::ResourceLimitError] if the maximum number of readers
|
108
|
+
# is exceeded.
|
109
|
+
def acquire_read_lock
|
110
|
+
while true
|
111
|
+
c = @Counter.value
|
112
|
+
raise ResourceLimitError.new('Too many reader threads') if max_readers?(c)
|
113
|
+
|
114
|
+
# If a writer is waiting when we first queue up, we need to wait
|
115
|
+
if waiting_writer?(c)
|
116
|
+
@ReadLock.wait_until { !waiting_writer? }
|
117
|
+
|
118
|
+
# after a reader has waited once, they are allowed to "barge" ahead of waiting writers
|
119
|
+
# but if a writer is *running*, the reader still needs to wait (naturally)
|
120
|
+
while true
|
121
|
+
c = @Counter.value
|
122
|
+
if running_writer?(c)
|
123
|
+
@ReadLock.wait_until { !running_writer? }
|
124
|
+
else
|
125
|
+
return if @Counter.compare_and_swap(c, c+1)
|
126
|
+
end
|
127
|
+
end
|
128
|
+
else
|
129
|
+
break if @Counter.compare_and_swap(c, c+1)
|
130
|
+
end
|
131
|
+
end
|
132
|
+
true
|
133
|
+
end
|
134
|
+
|
135
|
+
# Release a previously acquired read lock.
|
136
|
+
#
|
137
|
+
# @return [Boolean] true if the lock is successfully released
|
138
|
+
def release_read_lock
|
139
|
+
while true
|
140
|
+
c = @Counter.value
|
141
|
+
if @Counter.compare_and_swap(c, c-1)
|
142
|
+
# If one or more writers were waiting, and we were the last reader, wake a writer up
|
143
|
+
if waiting_writer?(c) && running_readers(c) == 1
|
144
|
+
@WriteLock.signal
|
145
|
+
end
|
146
|
+
break
|
147
|
+
end
|
148
|
+
end
|
149
|
+
true
|
150
|
+
end
|
151
|
+
|
152
|
+
# Acquire a write lock. Will block and wait for all active readers and writers.
|
153
|
+
#
|
154
|
+
# @return [Boolean] true if the lock is successfully acquired
|
155
|
+
#
|
156
|
+
# @raise [Concurrent::ResourceLimitError] if the maximum number of writers
|
157
|
+
# is exceeded.
|
158
|
+
def acquire_write_lock
|
159
|
+
while true
|
160
|
+
c = @Counter.value
|
161
|
+
raise ResourceLimitError.new('Too many writer threads') if max_writers?(c)
|
162
|
+
|
163
|
+
if c == 0 # no readers OR writers running
|
164
|
+
# if we successfully swap the RUNNING_WRITER bit on, then we can go ahead
|
165
|
+
break if @Counter.compare_and_swap(0, RUNNING_WRITER)
|
166
|
+
elsif @Counter.compare_and_swap(c, c+WAITING_WRITER)
|
167
|
+
while true
|
168
|
+
# Now we have successfully incremented, so no more readers will be able to increment
|
169
|
+
# (they will wait instead)
|
170
|
+
# However, readers OR writers could decrement right here, OR another writer could increment
|
171
|
+
@WriteLock.wait_until do
|
172
|
+
# So we have to do another check inside the synchronized section
|
173
|
+
# If a writer OR reader is running, then go to sleep
|
174
|
+
c = @Counter.value
|
175
|
+
!running_writer?(c) && !running_readers?(c)
|
176
|
+
end
|
177
|
+
|
178
|
+
# We just came out of a wait
|
179
|
+
# If we successfully turn the RUNNING_WRITER bit on with an atomic swap,
|
180
|
+
# Then we are OK to stop waiting and go ahead
|
181
|
+
# Otherwise go back and wait again
|
182
|
+
c = @Counter.value
|
183
|
+
break if !running_writer?(c) && !running_readers?(c) && @Counter.compare_and_swap(c, c+RUNNING_WRITER-WAITING_WRITER)
|
184
|
+
end
|
185
|
+
break
|
186
|
+
end
|
187
|
+
end
|
188
|
+
true
|
189
|
+
end
|
190
|
+
|
191
|
+
# Release a previously acquired write lock.
|
192
|
+
#
|
193
|
+
# @return [Boolean] true if the lock is successfully released
|
194
|
+
def release_write_lock
|
195
|
+
c = @Counter.update { |c| c-RUNNING_WRITER }
|
196
|
+
@ReadLock.broadcast
|
197
|
+
@WriteLock.signal if waiting_writers(c) > 0
|
198
|
+
true
|
199
|
+
end
|
200
|
+
|
201
|
+
# Queries if the write lock is held by any thread.
|
202
|
+
#
|
203
|
+
# @return [Boolean] true if the write lock is held else false`
|
204
|
+
def write_locked?
|
205
|
+
@Counter.value >= RUNNING_WRITER
|
206
|
+
end
|
207
|
+
|
208
|
+
# Queries whether any threads are waiting to acquire the read or write lock.
|
209
|
+
#
|
210
|
+
# @return [Boolean] true if any threads are waiting for a lock else false
|
211
|
+
def has_waiters?
|
212
|
+
waiting_writer?(@Counter.value)
|
213
|
+
end
|
214
|
+
|
215
|
+
private
|
216
|
+
|
217
|
+
# @!visibility private
|
218
|
+
def running_readers(c = @Counter.value)
|
219
|
+
c & MAX_READERS
|
220
|
+
end
|
221
|
+
|
222
|
+
# @!visibility private
|
223
|
+
def running_readers?(c = @Counter.value)
|
224
|
+
(c & MAX_READERS) > 0
|
225
|
+
end
|
226
|
+
|
227
|
+
# @!visibility private
|
228
|
+
def running_writer?(c = @Counter.value)
|
229
|
+
c >= RUNNING_WRITER
|
230
|
+
end
|
231
|
+
|
232
|
+
# @!visibility private
|
233
|
+
def waiting_writers(c = @Counter.value)
|
234
|
+
(c & MAX_WRITERS) / WAITING_WRITER
|
235
|
+
end
|
236
|
+
|
237
|
+
# @!visibility private
|
238
|
+
def waiting_writer?(c = @Counter.value)
|
239
|
+
c >= WAITING_WRITER
|
240
|
+
end
|
241
|
+
|
242
|
+
# @!visibility private
|
243
|
+
def max_readers?(c = @Counter.value)
|
244
|
+
(c & MAX_READERS) == MAX_READERS
|
245
|
+
end
|
246
|
+
|
247
|
+
# @!visibility private
|
248
|
+
def max_writers?(c = @Counter.value)
|
249
|
+
(c & MAX_WRITERS) == MAX_WRITERS
|
250
|
+
end
|
251
|
+
end
|
252
|
+
end
|