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.
Files changed (145) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +114 -3
  3. data/README.md +111 -55
  4. data/lib/concurrent.rb +90 -14
  5. data/lib/concurrent/async.rb +143 -51
  6. data/lib/concurrent/atom.rb +131 -0
  7. data/lib/concurrent/atomic/atomic_boolean.rb +57 -107
  8. data/lib/concurrent/atomic/atomic_fixnum.rb +73 -101
  9. data/lib/concurrent/atomic/atomic_reference.rb +49 -0
  10. data/lib/concurrent/atomic/condition.rb +23 -12
  11. data/lib/concurrent/atomic/count_down_latch.rb +23 -21
  12. data/lib/concurrent/atomic/cyclic_barrier.rb +47 -47
  13. data/lib/concurrent/atomic/event.rb +33 -42
  14. data/lib/concurrent/atomic/read_write_lock.rb +252 -0
  15. data/lib/concurrent/atomic/semaphore.rb +64 -89
  16. data/lib/concurrent/atomic/thread_local_var.rb +130 -58
  17. data/lib/concurrent/atomic/thread_local_var/weak_key_map.rb +236 -0
  18. data/lib/concurrent/atomic_reference/direct_update.rb +34 -3
  19. data/lib/concurrent/atomic_reference/jruby.rb +6 -3
  20. data/lib/concurrent/atomic_reference/mutex_atomic.rb +17 -39
  21. data/lib/concurrent/atomic_reference/numeric_cas_wrapper.rb +3 -0
  22. data/lib/concurrent/atomic_reference/rbx.rb +4 -1
  23. data/lib/concurrent/atomic_reference/ruby.rb +6 -3
  24. data/lib/concurrent/atomics.rb +74 -4
  25. data/lib/concurrent/collection/copy_on_notify_observer_set.rb +115 -0
  26. data/lib/concurrent/collection/copy_on_write_observer_set.rb +119 -0
  27. data/lib/concurrent/collection/priority_queue.rb +300 -245
  28. data/lib/concurrent/concern/deprecation.rb +34 -0
  29. data/lib/concurrent/concern/dereferenceable.rb +88 -0
  30. data/lib/concurrent/concern/logging.rb +27 -0
  31. data/lib/concurrent/concern/obligation.rb +228 -0
  32. data/lib/concurrent/concern/observable.rb +85 -0
  33. data/lib/concurrent/configuration.rb +234 -109
  34. data/lib/concurrent/dataflow.rb +2 -3
  35. data/lib/concurrent/delay.rb +141 -50
  36. data/lib/concurrent/edge.rb +30 -0
  37. data/lib/concurrent/errors.rb +19 -7
  38. data/lib/concurrent/exchanger.rb +25 -1
  39. data/lib/concurrent/executor/cached_thread_pool.rb +51 -33
  40. data/lib/concurrent/executor/executor.rb +46 -299
  41. data/lib/concurrent/executor/executor_service.rb +521 -0
  42. data/lib/concurrent/executor/fixed_thread_pool.rb +196 -23
  43. data/lib/concurrent/executor/immediate_executor.rb +9 -9
  44. data/lib/concurrent/executor/indirect_immediate_executor.rb +4 -3
  45. data/lib/concurrent/executor/java_single_thread_executor.rb +17 -16
  46. data/lib/concurrent/executor/java_thread_pool_executor.rb +55 -102
  47. data/lib/concurrent/executor/ruby_single_thread_executor.rb +14 -16
  48. data/lib/concurrent/executor/ruby_thread_pool_executor.rb +250 -166
  49. data/lib/concurrent/executor/safe_task_executor.rb +5 -4
  50. data/lib/concurrent/executor/serialized_execution.rb +22 -18
  51. data/lib/concurrent/executor/{per_thread_executor.rb → simple_executor_service.rb} +29 -20
  52. data/lib/concurrent/executor/single_thread_executor.rb +32 -21
  53. data/lib/concurrent/executor/thread_pool_executor.rb +73 -60
  54. data/lib/concurrent/executor/timer_set.rb +96 -84
  55. data/lib/concurrent/executors.rb +1 -1
  56. data/lib/concurrent/future.rb +71 -38
  57. data/lib/concurrent/immutable_struct.rb +89 -0
  58. data/lib/concurrent/ivar.rb +152 -60
  59. data/lib/concurrent/lazy_register.rb +40 -20
  60. data/lib/concurrent/maybe.rb +226 -0
  61. data/lib/concurrent/mutable_struct.rb +227 -0
  62. data/lib/concurrent/mvar.rb +44 -43
  63. data/lib/concurrent/promise.rb +229 -136
  64. data/lib/concurrent/scheduled_task.rb +341 -43
  65. data/lib/concurrent/settable_struct.rb +127 -0
  66. data/lib/concurrent/synchronization.rb +17 -0
  67. data/lib/concurrent/synchronization/abstract_object.rb +163 -0
  68. data/lib/concurrent/synchronization/abstract_struct.rb +158 -0
  69. data/lib/concurrent/synchronization/condition.rb +53 -0
  70. data/lib/concurrent/synchronization/java_object.rb +34 -0
  71. data/lib/concurrent/synchronization/lock.rb +32 -0
  72. data/lib/concurrent/synchronization/monitor_object.rb +26 -0
  73. data/lib/concurrent/synchronization/mutex_object.rb +43 -0
  74. data/lib/concurrent/synchronization/object.rb +78 -0
  75. data/lib/concurrent/synchronization/rbx_object.rb +75 -0
  76. data/lib/concurrent/timer_task.rb +92 -103
  77. data/lib/concurrent/tvar.rb +42 -38
  78. data/lib/concurrent/utilities.rb +3 -1
  79. data/lib/concurrent/utility/at_exit.rb +97 -0
  80. data/lib/concurrent/utility/engine.rb +44 -0
  81. data/lib/concurrent/utility/monotonic_time.rb +59 -0
  82. data/lib/concurrent/utility/native_extension_loader.rb +56 -0
  83. data/lib/concurrent/utility/processor_counter.rb +156 -0
  84. data/lib/concurrent/utility/timeout.rb +18 -14
  85. data/lib/concurrent/utility/timer.rb +11 -6
  86. data/lib/concurrent/version.rb +2 -1
  87. data/lib/concurrent_ruby.rb +1 -0
  88. data/lib/concurrent_ruby_ext.jar +0 -0
  89. metadata +46 -66
  90. data/lib/concurrent/actor.rb +0 -103
  91. data/lib/concurrent/actor/behaviour.rb +0 -70
  92. data/lib/concurrent/actor/behaviour/abstract.rb +0 -48
  93. data/lib/concurrent/actor/behaviour/awaits.rb +0 -21
  94. data/lib/concurrent/actor/behaviour/buffer.rb +0 -54
  95. data/lib/concurrent/actor/behaviour/errors_on_unknown_message.rb +0 -12
  96. data/lib/concurrent/actor/behaviour/executes_context.rb +0 -18
  97. data/lib/concurrent/actor/behaviour/linking.rb +0 -45
  98. data/lib/concurrent/actor/behaviour/pausing.rb +0 -77
  99. data/lib/concurrent/actor/behaviour/removes_child.rb +0 -16
  100. data/lib/concurrent/actor/behaviour/sets_results.rb +0 -36
  101. data/lib/concurrent/actor/behaviour/supervised.rb +0 -59
  102. data/lib/concurrent/actor/behaviour/supervising.rb +0 -34
  103. data/lib/concurrent/actor/behaviour/terminates_children.rb +0 -13
  104. data/lib/concurrent/actor/behaviour/termination.rb +0 -54
  105. data/lib/concurrent/actor/context.rb +0 -154
  106. data/lib/concurrent/actor/core.rb +0 -217
  107. data/lib/concurrent/actor/default_dead_letter_handler.rb +0 -9
  108. data/lib/concurrent/actor/envelope.rb +0 -41
  109. data/lib/concurrent/actor/errors.rb +0 -27
  110. data/lib/concurrent/actor/internal_delegations.rb +0 -49
  111. data/lib/concurrent/actor/public_delegations.rb +0 -40
  112. data/lib/concurrent/actor/reference.rb +0 -81
  113. data/lib/concurrent/actor/root.rb +0 -37
  114. data/lib/concurrent/actor/type_check.rb +0 -48
  115. data/lib/concurrent/actor/utils.rb +0 -10
  116. data/lib/concurrent/actor/utils/ad_hoc.rb +0 -21
  117. data/lib/concurrent/actor/utils/balancer.rb +0 -42
  118. data/lib/concurrent/actor/utils/broadcast.rb +0 -52
  119. data/lib/concurrent/actor/utils/pool.rb +0 -59
  120. data/lib/concurrent/actress.rb +0 -3
  121. data/lib/concurrent/agent.rb +0 -209
  122. data/lib/concurrent/atomic.rb +0 -92
  123. data/lib/concurrent/atomic/copy_on_notify_observer_set.rb +0 -118
  124. data/lib/concurrent/atomic/copy_on_write_observer_set.rb +0 -117
  125. data/lib/concurrent/atomic/synchronization.rb +0 -51
  126. data/lib/concurrent/channel/buffered_channel.rb +0 -85
  127. data/lib/concurrent/channel/channel.rb +0 -41
  128. data/lib/concurrent/channel/unbuffered_channel.rb +0 -35
  129. data/lib/concurrent/channel/waitable_list.rb +0 -40
  130. data/lib/concurrent/channels.rb +0 -5
  131. data/lib/concurrent/collection/blocking_ring_buffer.rb +0 -71
  132. data/lib/concurrent/collection/ring_buffer.rb +0 -59
  133. data/lib/concurrent/collections.rb +0 -3
  134. data/lib/concurrent/dereferenceable.rb +0 -108
  135. data/lib/concurrent/executor/java_cached_thread_pool.rb +0 -32
  136. data/lib/concurrent/executor/java_fixed_thread_pool.rb +0 -31
  137. data/lib/concurrent/executor/ruby_cached_thread_pool.rb +0 -29
  138. data/lib/concurrent/executor/ruby_fixed_thread_pool.rb +0 -32
  139. data/lib/concurrent/executor/ruby_thread_pool_worker.rb +0 -73
  140. data/lib/concurrent/logging.rb +0 -20
  141. data/lib/concurrent/obligation.rb +0 -171
  142. data/lib/concurrent/observable.rb +0 -73
  143. data/lib/concurrent/options_parser.rb +0 -48
  144. data/lib/concurrent/utility/processor_count.rb +0 -152
  145. 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 the last thread arrives and before the others are released
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
- 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)
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 `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
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
- @mutex.synchronize do
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
- set_status_and_restore(:fulfilled)
53
+ ns_generation_done @generation, :fulfilled
48
54
  true
49
55
  else
50
- wait_for_wake_up(@generation, timeout)
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` method will return false and the barrier will be broken
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
- @mutex.synchronize do
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
- @mutex.synchronize { @generation.status != :waiting }
84
+ synchronize { @generation.status != :waiting }
76
85
  end
77
86
 
78
- private
87
+ protected
79
88
 
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
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 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
+ def ns_next_generation
96
+ @generation = Generation.new(:waiting)
97
+ @number_waiting = 0
95
98
  end
96
99
 
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?
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/atomic/condition'
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
- @set = false
22
- @mutex = Mutex.new
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
- @mutex.lock
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
- @mutex.lock
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
- @mutex.lock
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
- @mutex.lock
72
- @set = false
73
- true
74
- ensure
75
- @mutex.unlock
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
- @mutex.lock
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
- remaining = Condition::Result.new(timeout)
88
- while !@set && remaining.can_wait?
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
- @set
94
- ensure
95
- @mutex.unlock
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