concurrent-ruby 1.1.5

Sign up to get free protection for your applications and to get access to all the features.
Files changed (143) hide show
  1. checksums.yaml +7 -0
  2. data/CHANGELOG.md +478 -0
  3. data/Gemfile +41 -0
  4. data/LICENSE.md +23 -0
  5. data/README.md +381 -0
  6. data/Rakefile +327 -0
  7. data/ext/concurrent-ruby/ConcurrentRubyService.java +17 -0
  8. data/ext/concurrent-ruby/com/concurrent_ruby/ext/AtomicReferenceLibrary.java +175 -0
  9. data/ext/concurrent-ruby/com/concurrent_ruby/ext/JRubyMapBackendLibrary.java +248 -0
  10. data/ext/concurrent-ruby/com/concurrent_ruby/ext/JavaAtomicBooleanLibrary.java +93 -0
  11. data/ext/concurrent-ruby/com/concurrent_ruby/ext/JavaAtomicFixnumLibrary.java +113 -0
  12. data/ext/concurrent-ruby/com/concurrent_ruby/ext/JavaSemaphoreLibrary.java +159 -0
  13. data/ext/concurrent-ruby/com/concurrent_ruby/ext/SynchronizationLibrary.java +307 -0
  14. data/ext/concurrent-ruby/com/concurrent_ruby/ext/jsr166e/ConcurrentHashMap.java +31 -0
  15. data/ext/concurrent-ruby/com/concurrent_ruby/ext/jsr166e/ConcurrentHashMapV8.java +3863 -0
  16. data/ext/concurrent-ruby/com/concurrent_ruby/ext/jsr166e/LongAdder.java +203 -0
  17. data/ext/concurrent-ruby/com/concurrent_ruby/ext/jsr166e/Striped64.java +342 -0
  18. data/ext/concurrent-ruby/com/concurrent_ruby/ext/jsr166e/nounsafe/ConcurrentHashMapV8.java +3800 -0
  19. data/ext/concurrent-ruby/com/concurrent_ruby/ext/jsr166e/nounsafe/LongAdder.java +204 -0
  20. data/ext/concurrent-ruby/com/concurrent_ruby/ext/jsr166e/nounsafe/Striped64.java +291 -0
  21. data/ext/concurrent-ruby/com/concurrent_ruby/ext/jsr166y/ThreadLocalRandom.java +199 -0
  22. data/lib/concurrent-ruby.rb +1 -0
  23. data/lib/concurrent.rb +134 -0
  24. data/lib/concurrent/agent.rb +587 -0
  25. data/lib/concurrent/array.rb +66 -0
  26. data/lib/concurrent/async.rb +459 -0
  27. data/lib/concurrent/atom.rb +222 -0
  28. data/lib/concurrent/atomic/abstract_thread_local_var.rb +66 -0
  29. data/lib/concurrent/atomic/atomic_boolean.rb +126 -0
  30. data/lib/concurrent/atomic/atomic_fixnum.rb +143 -0
  31. data/lib/concurrent/atomic/atomic_markable_reference.rb +164 -0
  32. data/lib/concurrent/atomic/atomic_reference.rb +204 -0
  33. data/lib/concurrent/atomic/count_down_latch.rb +100 -0
  34. data/lib/concurrent/atomic/cyclic_barrier.rb +128 -0
  35. data/lib/concurrent/atomic/event.rb +109 -0
  36. data/lib/concurrent/atomic/java_count_down_latch.rb +42 -0
  37. data/lib/concurrent/atomic/java_thread_local_var.rb +37 -0
  38. data/lib/concurrent/atomic/mutex_atomic_boolean.rb +62 -0
  39. data/lib/concurrent/atomic/mutex_atomic_fixnum.rb +75 -0
  40. data/lib/concurrent/atomic/mutex_count_down_latch.rb +44 -0
  41. data/lib/concurrent/atomic/mutex_semaphore.rb +115 -0
  42. data/lib/concurrent/atomic/read_write_lock.rb +254 -0
  43. data/lib/concurrent/atomic/reentrant_read_write_lock.rb +379 -0
  44. data/lib/concurrent/atomic/ruby_thread_local_var.rb +161 -0
  45. data/lib/concurrent/atomic/semaphore.rb +145 -0
  46. data/lib/concurrent/atomic/thread_local_var.rb +104 -0
  47. data/lib/concurrent/atomic_reference/mutex_atomic.rb +56 -0
  48. data/lib/concurrent/atomic_reference/numeric_cas_wrapper.rb +28 -0
  49. data/lib/concurrent/atomics.rb +10 -0
  50. data/lib/concurrent/collection/copy_on_notify_observer_set.rb +107 -0
  51. data/lib/concurrent/collection/copy_on_write_observer_set.rb +111 -0
  52. data/lib/concurrent/collection/java_non_concurrent_priority_queue.rb +84 -0
  53. data/lib/concurrent/collection/lock_free_stack.rb +158 -0
  54. data/lib/concurrent/collection/map/atomic_reference_map_backend.rb +927 -0
  55. data/lib/concurrent/collection/map/mri_map_backend.rb +66 -0
  56. data/lib/concurrent/collection/map/non_concurrent_map_backend.rb +140 -0
  57. data/lib/concurrent/collection/map/synchronized_map_backend.rb +82 -0
  58. data/lib/concurrent/collection/non_concurrent_priority_queue.rb +143 -0
  59. data/lib/concurrent/collection/ruby_non_concurrent_priority_queue.rb +150 -0
  60. data/lib/concurrent/concern/deprecation.rb +34 -0
  61. data/lib/concurrent/concern/dereferenceable.rb +73 -0
  62. data/lib/concurrent/concern/logging.rb +32 -0
  63. data/lib/concurrent/concern/obligation.rb +220 -0
  64. data/lib/concurrent/concern/observable.rb +110 -0
  65. data/lib/concurrent/concurrent_ruby.jar +0 -0
  66. data/lib/concurrent/configuration.rb +184 -0
  67. data/lib/concurrent/constants.rb +8 -0
  68. data/lib/concurrent/dataflow.rb +81 -0
  69. data/lib/concurrent/delay.rb +199 -0
  70. data/lib/concurrent/errors.rb +69 -0
  71. data/lib/concurrent/exchanger.rb +352 -0
  72. data/lib/concurrent/executor/abstract_executor_service.rb +134 -0
  73. data/lib/concurrent/executor/cached_thread_pool.rb +62 -0
  74. data/lib/concurrent/executor/executor_service.rb +185 -0
  75. data/lib/concurrent/executor/fixed_thread_pool.rb +206 -0
  76. data/lib/concurrent/executor/immediate_executor.rb +66 -0
  77. data/lib/concurrent/executor/indirect_immediate_executor.rb +44 -0
  78. data/lib/concurrent/executor/java_executor_service.rb +91 -0
  79. data/lib/concurrent/executor/java_single_thread_executor.rb +29 -0
  80. data/lib/concurrent/executor/java_thread_pool_executor.rb +123 -0
  81. data/lib/concurrent/executor/ruby_executor_service.rb +78 -0
  82. data/lib/concurrent/executor/ruby_single_thread_executor.rb +22 -0
  83. data/lib/concurrent/executor/ruby_thread_pool_executor.rb +362 -0
  84. data/lib/concurrent/executor/safe_task_executor.rb +35 -0
  85. data/lib/concurrent/executor/serial_executor_service.rb +34 -0
  86. data/lib/concurrent/executor/serialized_execution.rb +107 -0
  87. data/lib/concurrent/executor/serialized_execution_delegator.rb +28 -0
  88. data/lib/concurrent/executor/simple_executor_service.rb +100 -0
  89. data/lib/concurrent/executor/single_thread_executor.rb +56 -0
  90. data/lib/concurrent/executor/thread_pool_executor.rb +87 -0
  91. data/lib/concurrent/executor/timer_set.rb +173 -0
  92. data/lib/concurrent/executors.rb +20 -0
  93. data/lib/concurrent/future.rb +141 -0
  94. data/lib/concurrent/hash.rb +59 -0
  95. data/lib/concurrent/immutable_struct.rb +93 -0
  96. data/lib/concurrent/ivar.rb +207 -0
  97. data/lib/concurrent/map.rb +337 -0
  98. data/lib/concurrent/maybe.rb +229 -0
  99. data/lib/concurrent/mutable_struct.rb +229 -0
  100. data/lib/concurrent/mvar.rb +242 -0
  101. data/lib/concurrent/options.rb +42 -0
  102. data/lib/concurrent/promise.rb +579 -0
  103. data/lib/concurrent/promises.rb +2167 -0
  104. data/lib/concurrent/re_include.rb +58 -0
  105. data/lib/concurrent/scheduled_task.rb +318 -0
  106. data/lib/concurrent/set.rb +66 -0
  107. data/lib/concurrent/settable_struct.rb +129 -0
  108. data/lib/concurrent/synchronization.rb +30 -0
  109. data/lib/concurrent/synchronization/abstract_lockable_object.rb +98 -0
  110. data/lib/concurrent/synchronization/abstract_object.rb +24 -0
  111. data/lib/concurrent/synchronization/abstract_struct.rb +160 -0
  112. data/lib/concurrent/synchronization/condition.rb +60 -0
  113. data/lib/concurrent/synchronization/jruby_lockable_object.rb +13 -0
  114. data/lib/concurrent/synchronization/jruby_object.rb +45 -0
  115. data/lib/concurrent/synchronization/lock.rb +36 -0
  116. data/lib/concurrent/synchronization/lockable_object.rb +74 -0
  117. data/lib/concurrent/synchronization/mri_object.rb +44 -0
  118. data/lib/concurrent/synchronization/mutex_lockable_object.rb +76 -0
  119. data/lib/concurrent/synchronization/object.rb +183 -0
  120. data/lib/concurrent/synchronization/rbx_lockable_object.rb +65 -0
  121. data/lib/concurrent/synchronization/rbx_object.rb +49 -0
  122. data/lib/concurrent/synchronization/truffleruby_object.rb +47 -0
  123. data/lib/concurrent/synchronization/volatile.rb +36 -0
  124. data/lib/concurrent/thread_safe/synchronized_delegator.rb +50 -0
  125. data/lib/concurrent/thread_safe/util.rb +16 -0
  126. data/lib/concurrent/thread_safe/util/adder.rb +74 -0
  127. data/lib/concurrent/thread_safe/util/cheap_lockable.rb +118 -0
  128. data/lib/concurrent/thread_safe/util/data_structures.rb +63 -0
  129. data/lib/concurrent/thread_safe/util/power_of_two_tuple.rb +38 -0
  130. data/lib/concurrent/thread_safe/util/striped64.rb +246 -0
  131. data/lib/concurrent/thread_safe/util/volatile.rb +75 -0
  132. data/lib/concurrent/thread_safe/util/xor_shift_random.rb +50 -0
  133. data/lib/concurrent/timer_task.rb +334 -0
  134. data/lib/concurrent/tuple.rb +86 -0
  135. data/lib/concurrent/tvar.rb +258 -0
  136. data/lib/concurrent/utility/at_exit.rb +97 -0
  137. data/lib/concurrent/utility/engine.rb +56 -0
  138. data/lib/concurrent/utility/monotonic_time.rb +58 -0
  139. data/lib/concurrent/utility/native_extension_loader.rb +79 -0
  140. data/lib/concurrent/utility/native_integer.rb +53 -0
  141. data/lib/concurrent/utility/processor_counter.rb +158 -0
  142. data/lib/concurrent/version.rb +3 -0
  143. metadata +193 -0
@@ -0,0 +1,44 @@
1
+ require 'concurrent/synchronization'
2
+ require 'concurrent/utility/native_integer'
3
+
4
+ module Concurrent
5
+
6
+ # @!macro count_down_latch
7
+ # @!visibility private
8
+ # @!macro internal_implementation_note
9
+ class MutexCountDownLatch < Synchronization::LockableObject
10
+
11
+ # @!macro count_down_latch_method_initialize
12
+ def initialize(count = 1)
13
+ Utility::NativeInteger.ensure_integer_and_bounds count
14
+ Utility::NativeInteger.ensure_positive count
15
+
16
+ super()
17
+ synchronize { ns_initialize count }
18
+ end
19
+
20
+ # @!macro count_down_latch_method_wait
21
+ def wait(timeout = nil)
22
+ synchronize { ns_wait_until(timeout) { @count == 0 } }
23
+ end
24
+
25
+ # @!macro count_down_latch_method_count_down
26
+ def count_down
27
+ synchronize do
28
+ @count -= 1 if @count > 0
29
+ ns_broadcast if @count == 0
30
+ end
31
+ end
32
+
33
+ # @!macro count_down_latch_method_count
34
+ def count
35
+ synchronize { @count }
36
+ end
37
+
38
+ protected
39
+
40
+ def ns_initialize(count)
41
+ @count = count
42
+ end
43
+ end
44
+ end
@@ -0,0 +1,115 @@
1
+ require 'concurrent/synchronization'
2
+ require 'concurrent/utility/native_integer'
3
+
4
+ module Concurrent
5
+
6
+ # @!macro semaphore
7
+ # @!visibility private
8
+ # @!macro internal_implementation_note
9
+ class MutexSemaphore < Synchronization::LockableObject
10
+
11
+ # @!macro semaphore_method_initialize
12
+ def initialize(count)
13
+ Utility::NativeInteger.ensure_integer_and_bounds count
14
+
15
+ super()
16
+ synchronize { ns_initialize count }
17
+ end
18
+
19
+ # @!macro semaphore_method_acquire
20
+ def acquire(permits = 1)
21
+ Utility::NativeInteger.ensure_integer_and_bounds permits
22
+ Utility::NativeInteger.ensure_positive permits
23
+
24
+ synchronize do
25
+ try_acquire_timed(permits, nil)
26
+ nil
27
+ end
28
+ end
29
+
30
+ # @!macro semaphore_method_available_permits
31
+ def available_permits
32
+ synchronize { @free }
33
+ end
34
+
35
+ # @!macro semaphore_method_drain_permits
36
+ #
37
+ # Acquires and returns all permits that are immediately available.
38
+ #
39
+ # @return [Integer]
40
+ def drain_permits
41
+ synchronize do
42
+ @free.tap { |_| @free = 0 }
43
+ end
44
+ end
45
+
46
+ # @!macro semaphore_method_try_acquire
47
+ def try_acquire(permits = 1, timeout = nil)
48
+ Utility::NativeInteger.ensure_integer_and_bounds permits
49
+ Utility::NativeInteger.ensure_positive permits
50
+
51
+ synchronize do
52
+ if timeout.nil?
53
+ try_acquire_now(permits)
54
+ else
55
+ try_acquire_timed(permits, timeout)
56
+ end
57
+ end
58
+ end
59
+
60
+ # @!macro semaphore_method_release
61
+ def release(permits = 1)
62
+ Utility::NativeInteger.ensure_integer_and_bounds permits
63
+ Utility::NativeInteger.ensure_positive permits
64
+
65
+ synchronize do
66
+ @free += permits
67
+ permits.times { ns_signal }
68
+ end
69
+ nil
70
+ end
71
+
72
+ # Shrinks the number of available permits by the indicated reduction.
73
+ #
74
+ # @param [Fixnum] reduction Number of permits to remove.
75
+ #
76
+ # @raise [ArgumentError] if `reduction` is not an integer or is negative
77
+ #
78
+ # @raise [ArgumentError] if `@free` - `@reduction` is less than zero
79
+ #
80
+ # @return [nil]
81
+ #
82
+ # @!visibility private
83
+ def reduce_permits(reduction)
84
+ Utility::NativeInteger.ensure_integer_and_bounds reduction
85
+ Utility::NativeInteger.ensure_positive reduction
86
+
87
+ synchronize { @free -= reduction }
88
+ nil
89
+ end
90
+
91
+ protected
92
+
93
+ # @!visibility private
94
+ def ns_initialize(count)
95
+ @free = count
96
+ end
97
+
98
+ private
99
+
100
+ # @!visibility private
101
+ def try_acquire_now(permits)
102
+ if @free >= permits
103
+ @free -= permits
104
+ true
105
+ else
106
+ false
107
+ end
108
+ end
109
+
110
+ # @!visibility private
111
+ def try_acquire_timed(permits, timeout)
112
+ ns_wait_until(timeout) { try_acquire_now(permits) }
113
+ end
114
+ end
115
+ end
@@ -0,0 +1,254 @@
1
+ require 'thread'
2
+ require 'concurrent/atomic/atomic_fixnum'
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 << 29
37
+
38
+ # @!visibility private
39
+ MAX_READERS = WAITING_WRITER - 1
40
+
41
+ # @!visibility private
42
+ MAX_WRITERS = RUNNING_WRITER - MAX_READERS - 1
43
+
44
+ safe_initialization!
45
+
46
+ # Implementation notes:
47
+ # A goal is to make the uncontended path for both readers/writers lock-free
48
+ # Only if there is reader-writer or writer-writer contention, should locks be used
49
+ # Internal state is represented by a single integer ("counter"), and updated
50
+ # using atomic compare-and-swap operations
51
+ # When the counter is 0, the lock is free
52
+ # Each reader increments the counter by 1 when acquiring a read lock
53
+ # (and decrements by 1 when releasing the read lock)
54
+ # The counter is increased by (1 << 15) for each writer waiting to acquire the
55
+ # write lock, and by (1 << 29) if the write lock is taken
56
+
57
+ # Create a new `ReadWriteLock` in the unlocked state.
58
+ def initialize
59
+ super()
60
+ @Counter = AtomicFixnum.new(0) # single integer which represents lock state
61
+ @ReadLock = Synchronization::Lock.new
62
+ @WriteLock = Synchronization::Lock.new
63
+ end
64
+
65
+ # Execute a block operation within a read lock.
66
+ #
67
+ # @yield the task to be performed within the lock.
68
+ #
69
+ # @return [Object] the result of the block operation.
70
+ #
71
+ # @raise [ArgumentError] when no block is given.
72
+ # @raise [Concurrent::ResourceLimitError] if the maximum number of readers
73
+ # is exceeded.
74
+ def with_read_lock
75
+ raise ArgumentError.new('no block given') unless block_given?
76
+ acquire_read_lock
77
+ begin
78
+ yield
79
+ ensure
80
+ release_read_lock
81
+ end
82
+ end
83
+
84
+ # Execute a block operation within a write lock.
85
+ #
86
+ # @yield the task to be performed within the lock.
87
+ #
88
+ # @return [Object] the result of the block operation.
89
+ #
90
+ # @raise [ArgumentError] when no block is given.
91
+ # @raise [Concurrent::ResourceLimitError] if the maximum number of readers
92
+ # is exceeded.
93
+ def with_write_lock
94
+ raise ArgumentError.new('no block given') unless block_given?
95
+ acquire_write_lock
96
+ begin
97
+ yield
98
+ ensure
99
+ release_write_lock
100
+ end
101
+ end
102
+
103
+ # Acquire a read lock. If a write lock has been acquired will block until
104
+ # it is released. Will not block if other read locks have been acquired.
105
+ #
106
+ # @return [Boolean] true if the lock is successfully acquired
107
+ #
108
+ # @raise [Concurrent::ResourceLimitError] if the maximum number of readers
109
+ # is exceeded.
110
+ def acquire_read_lock
111
+ while true
112
+ c = @Counter.value
113
+ raise ResourceLimitError.new('Too many reader threads') if max_readers?(c)
114
+
115
+ # If a writer is waiting when we first queue up, we need to wait
116
+ if waiting_writer?(c)
117
+ @ReadLock.wait_until { !waiting_writer? }
118
+
119
+ # after a reader has waited once, they are allowed to "barge" ahead of waiting writers
120
+ # but if a writer is *running*, the reader still needs to wait (naturally)
121
+ while true
122
+ c = @Counter.value
123
+ if running_writer?(c)
124
+ @ReadLock.wait_until { !running_writer? }
125
+ else
126
+ return if @Counter.compare_and_set(c, c+1)
127
+ end
128
+ end
129
+ else
130
+ break if @Counter.compare_and_set(c, c+1)
131
+ end
132
+ end
133
+ true
134
+ end
135
+
136
+ # Release a previously acquired read lock.
137
+ #
138
+ # @return [Boolean] true if the lock is successfully released
139
+ def release_read_lock
140
+ while true
141
+ c = @Counter.value
142
+ if @Counter.compare_and_set(c, c-1)
143
+ # If one or more writers were waiting, and we were the last reader, wake a writer up
144
+ if waiting_writer?(c) && running_readers(c) == 1
145
+ @WriteLock.signal
146
+ end
147
+ break
148
+ end
149
+ end
150
+ true
151
+ end
152
+
153
+ # Acquire a write lock. Will block and wait for all active readers and writers.
154
+ #
155
+ # @return [Boolean] true if the lock is successfully acquired
156
+ #
157
+ # @raise [Concurrent::ResourceLimitError] if the maximum number of writers
158
+ # is exceeded.
159
+ def acquire_write_lock
160
+ while true
161
+ c = @Counter.value
162
+ raise ResourceLimitError.new('Too many writer threads') if max_writers?(c)
163
+
164
+ if c == 0 # no readers OR writers running
165
+ # if we successfully swap the RUNNING_WRITER bit on, then we can go ahead
166
+ break if @Counter.compare_and_set(0, RUNNING_WRITER)
167
+ elsif @Counter.compare_and_set(c, c+WAITING_WRITER)
168
+ while true
169
+ # Now we have successfully incremented, so no more readers will be able to increment
170
+ # (they will wait instead)
171
+ # However, readers OR writers could decrement right here, OR another writer could increment
172
+ @WriteLock.wait_until do
173
+ # So we have to do another check inside the synchronized section
174
+ # If a writer OR reader is running, then go to sleep
175
+ c = @Counter.value
176
+ !running_writer?(c) && !running_readers?(c)
177
+ end
178
+
179
+ # We just came out of a wait
180
+ # If we successfully turn the RUNNING_WRITER bit on with an atomic swap,
181
+ # Then we are OK to stop waiting and go ahead
182
+ # Otherwise go back and wait again
183
+ c = @Counter.value
184
+ break if !running_writer?(c) && !running_readers?(c) && @Counter.compare_and_set(c, c+RUNNING_WRITER-WAITING_WRITER)
185
+ end
186
+ break
187
+ end
188
+ end
189
+ true
190
+ end
191
+
192
+ # Release a previously acquired write lock.
193
+ #
194
+ # @return [Boolean] true if the lock is successfully released
195
+ def release_write_lock
196
+ return true unless running_writer?
197
+ c = @Counter.update { |counter| counter - RUNNING_WRITER }
198
+ @ReadLock.broadcast
199
+ @WriteLock.signal if waiting_writers(c) > 0
200
+ true
201
+ end
202
+
203
+ # Queries if the write lock is held by any thread.
204
+ #
205
+ # @return [Boolean] true if the write lock is held else false`
206
+ def write_locked?
207
+ @Counter.value >= RUNNING_WRITER
208
+ end
209
+
210
+ # Queries whether any threads are waiting to acquire the read or write lock.
211
+ #
212
+ # @return [Boolean] true if any threads are waiting for a lock else false
213
+ def has_waiters?
214
+ waiting_writer?(@Counter.value)
215
+ end
216
+
217
+ private
218
+
219
+ # @!visibility private
220
+ def running_readers(c = @Counter.value)
221
+ c & MAX_READERS
222
+ end
223
+
224
+ # @!visibility private
225
+ def running_readers?(c = @Counter.value)
226
+ (c & MAX_READERS) > 0
227
+ end
228
+
229
+ # @!visibility private
230
+ def running_writer?(c = @Counter.value)
231
+ c >= RUNNING_WRITER
232
+ end
233
+
234
+ # @!visibility private
235
+ def waiting_writers(c = @Counter.value)
236
+ (c & MAX_WRITERS) / WAITING_WRITER
237
+ end
238
+
239
+ # @!visibility private
240
+ def waiting_writer?(c = @Counter.value)
241
+ c >= WAITING_WRITER
242
+ end
243
+
244
+ # @!visibility private
245
+ def max_readers?(c = @Counter.value)
246
+ (c & MAX_READERS) == MAX_READERS
247
+ end
248
+
249
+ # @!visibility private
250
+ def max_writers?(c = @Counter.value)
251
+ (c & MAX_WRITERS) == MAX_WRITERS
252
+ end
253
+ end
254
+ end
@@ -0,0 +1,379 @@
1
+ require 'thread'
2
+ require 'concurrent/atomic/atomic_reference'
3
+ require 'concurrent/errors'
4
+ require 'concurrent/synchronization'
5
+ require 'concurrent/atomic/thread_local_var'
6
+
7
+ module Concurrent
8
+
9
+ # Re-entrant read-write lock implementation
10
+ #
11
+ # Allows any number of concurrent readers, but only one concurrent writer
12
+ # (And while the "write" lock is taken, no read locks can be obtained either.
13
+ # Hence, the write lock can also be called an "exclusive" lock.)
14
+ #
15
+ # If another thread has taken a read lock, any thread which wants a write lock
16
+ # will block until all the readers release their locks. However, once a thread
17
+ # starts waiting to obtain a write lock, any additional readers that come along
18
+ # will also wait (so writers are not starved).
19
+ #
20
+ # A thread can acquire both a read and write lock at the same time. A thread can
21
+ # also acquire a read lock OR a write lock more than once. Only when the read (or
22
+ # write) lock is released as many times as it was acquired, will the thread
23
+ # actually let it go, allowing other threads which might have been waiting
24
+ # to proceed. Therefore the lock can be upgraded by first acquiring
25
+ # read lock and then write lock and that the lock can be downgraded by first
26
+ # having both read and write lock a releasing just the write lock.
27
+ #
28
+ # If both read and write locks are acquired by the same thread, it is not strictly
29
+ # necessary to release them in the same order they were acquired. In other words,
30
+ # the following code is legal:
31
+ #
32
+ # @example
33
+ # lock = Concurrent::ReentrantReadWriteLock.new
34
+ # lock.acquire_write_lock
35
+ # lock.acquire_read_lock
36
+ # lock.release_write_lock
37
+ # # At this point, the current thread is holding only a read lock, not a write
38
+ # # lock. So other threads can take read locks, but not a write lock.
39
+ # lock.release_read_lock
40
+ # # Now the current thread is not holding either a read or write lock, so
41
+ # # another thread could potentially acquire a write lock.
42
+ #
43
+ # This implementation was inspired by `java.util.concurrent.ReentrantReadWriteLock`.
44
+ #
45
+ # @example
46
+ # lock = Concurrent::ReentrantReadWriteLock.new
47
+ # lock.with_read_lock { data.retrieve }
48
+ # lock.with_write_lock { data.modify! }
49
+ #
50
+ # @see http://docs.oracle.com/javase/7/docs/api/java/util/concurrent/locks/ReentrantReadWriteLock.html java.util.concurrent.ReentrantReadWriteLock
51
+ class ReentrantReadWriteLock < Synchronization::Object
52
+
53
+ # Implementation notes:
54
+ #
55
+ # A goal is to make the uncontended path for both readers/writers mutex-free
56
+ # Only if there is reader-writer or writer-writer contention, should mutexes be used
57
+ # Otherwise, a single CAS operation is all we need to acquire/release a lock
58
+ #
59
+ # Internal state is represented by a single integer ("counter"), and updated
60
+ # using atomic compare-and-swap operations
61
+ # When the counter is 0, the lock is free
62
+ # Each thread which has one OR MORE read locks increments the counter by 1
63
+ # (and decrements by 1 when releasing the read lock)
64
+ # The counter is increased by (1 << 15) for each writer waiting to acquire the
65
+ # write lock, and by (1 << 29) if the write lock is taken
66
+ #
67
+ # Additionally, each thread uses a thread-local variable to count how many times
68
+ # it has acquired a read lock, AND how many times it has acquired a write lock.
69
+ # It uses a similar trick; an increment of 1 means a read lock was taken, and
70
+ # an increment of (1 << 15) means a write lock was taken
71
+ # This is what makes re-entrancy possible
72
+ #
73
+ # 2 rules are followed to ensure good liveness properties:
74
+ # 1) Once a writer has queued up and is waiting for a write lock, no other thread
75
+ # can take a lock without waiting
76
+ # 2) When a write lock is released, readers are given the "first chance" to wake
77
+ # up and acquire a read lock
78
+ # Following these rules means readers and writers tend to "take turns", so neither
79
+ # can starve the other, even under heavy contention
80
+
81
+ # @!visibility private
82
+ READER_BITS = 15
83
+ # @!visibility private
84
+ WRITER_BITS = 14
85
+
86
+ # Used with @Counter:
87
+ # @!visibility private
88
+ WAITING_WRITER = 1 << READER_BITS
89
+ # @!visibility private
90
+ RUNNING_WRITER = 1 << (READER_BITS + WRITER_BITS)
91
+ # @!visibility private
92
+ MAX_READERS = WAITING_WRITER - 1
93
+ # @!visibility private
94
+ MAX_WRITERS = RUNNING_WRITER - MAX_READERS - 1
95
+
96
+ # Used with @HeldCount:
97
+ # @!visibility private
98
+ WRITE_LOCK_HELD = 1 << READER_BITS
99
+ # @!visibility private
100
+ READ_LOCK_MASK = WRITE_LOCK_HELD - 1
101
+ # @!visibility private
102
+ WRITE_LOCK_MASK = MAX_WRITERS
103
+
104
+ safe_initialization!
105
+
106
+ # Create a new `ReentrantReadWriteLock` in the unlocked state.
107
+ def initialize
108
+ super()
109
+ @Counter = AtomicFixnum.new(0) # single integer which represents lock state
110
+ @ReadQueue = Synchronization::Lock.new # used to queue waiting readers
111
+ @WriteQueue = Synchronization::Lock.new # used to queue waiting writers
112
+ @HeldCount = ThreadLocalVar.new(0) # indicates # of R & W locks held by this thread
113
+ end
114
+
115
+ # Execute a block operation within a read lock.
116
+ #
117
+ # @yield the task to be performed within the lock.
118
+ #
119
+ # @return [Object] the result of the block operation.
120
+ #
121
+ # @raise [ArgumentError] when no block is given.
122
+ # @raise [Concurrent::ResourceLimitError] if the maximum number of readers
123
+ # is exceeded.
124
+ def with_read_lock
125
+ raise ArgumentError.new('no block given') unless block_given?
126
+ acquire_read_lock
127
+ begin
128
+ yield
129
+ ensure
130
+ release_read_lock
131
+ end
132
+ end
133
+
134
+ # Execute a block operation within a write lock.
135
+ #
136
+ # @yield the task to be performed within the lock.
137
+ #
138
+ # @return [Object] the result of the block operation.
139
+ #
140
+ # @raise [ArgumentError] when no block is given.
141
+ # @raise [Concurrent::ResourceLimitError] if the maximum number of readers
142
+ # is exceeded.
143
+ def with_write_lock
144
+ raise ArgumentError.new('no block given') unless block_given?
145
+ acquire_write_lock
146
+ begin
147
+ yield
148
+ ensure
149
+ release_write_lock
150
+ end
151
+ end
152
+
153
+ # Acquire a read lock. If a write lock is held by another thread, will block
154
+ # until it is released.
155
+ #
156
+ # @return [Boolean] true if the lock is successfully acquired
157
+ #
158
+ # @raise [Concurrent::ResourceLimitError] if the maximum number of readers
159
+ # is exceeded.
160
+ def acquire_read_lock
161
+ if (held = @HeldCount.value) > 0
162
+ # If we already have a lock, there's no need to wait
163
+ if held & READ_LOCK_MASK == 0
164
+ # But we do need to update the counter, if we were holding a write
165
+ # lock but not a read lock
166
+ @Counter.update { |c| c + 1 }
167
+ end
168
+ @HeldCount.value = held + 1
169
+ return true
170
+ end
171
+
172
+ while true
173
+ c = @Counter.value
174
+ raise ResourceLimitError.new('Too many reader threads') if max_readers?(c)
175
+
176
+ # If a writer is waiting OR running when we first queue up, we need to wait
177
+ if waiting_or_running_writer?(c)
178
+ # Before going to sleep, check again with the ReadQueue mutex held
179
+ @ReadQueue.synchronize do
180
+ @ReadQueue.ns_wait if waiting_or_running_writer?
181
+ end
182
+ # Note: the above 'synchronize' block could have used #wait_until,
183
+ # but that waits repeatedly in a loop, checking the wait condition
184
+ # each time it wakes up (to protect against spurious wakeups)
185
+ # But we are already in a loop, which is only broken when we successfully
186
+ # acquire the lock! So we don't care about spurious wakeups, and would
187
+ # rather not pay the extra overhead of using #wait_until
188
+
189
+ # After a reader has waited once, they are allowed to "barge" ahead of waiting writers
190
+ # But if a writer is *running*, the reader still needs to wait (naturally)
191
+ while true
192
+ c = @Counter.value
193
+ if running_writer?(c)
194
+ @ReadQueue.synchronize do
195
+ @ReadQueue.ns_wait if running_writer?
196
+ end
197
+ elsif @Counter.compare_and_set(c, c+1)
198
+ @HeldCount.value = held + 1
199
+ return true
200
+ end
201
+ end
202
+ elsif @Counter.compare_and_set(c, c+1)
203
+ @HeldCount.value = held + 1
204
+ return true
205
+ end
206
+ end
207
+ end
208
+
209
+ # Try to acquire a read lock and return true if we succeed. If it cannot be
210
+ # acquired immediately, return false.
211
+ #
212
+ # @return [Boolean] true if the lock is successfully acquired
213
+ def try_read_lock
214
+ if (held = @HeldCount.value) > 0
215
+ if held & READ_LOCK_MASK == 0
216
+ # If we hold a write lock, but not a read lock...
217
+ @Counter.update { |c| c + 1 }
218
+ end
219
+ @HeldCount.value = held + 1
220
+ return true
221
+ else
222
+ c = @Counter.value
223
+ if !waiting_or_running_writer?(c) && @Counter.compare_and_set(c, c+1)
224
+ @HeldCount.value = held + 1
225
+ return true
226
+ end
227
+ end
228
+ false
229
+ end
230
+
231
+ # Release a previously acquired read lock.
232
+ #
233
+ # @return [Boolean] true if the lock is successfully released
234
+ def release_read_lock
235
+ held = @HeldCount.value = @HeldCount.value - 1
236
+ rlocks_held = held & READ_LOCK_MASK
237
+ if rlocks_held == 0
238
+ c = @Counter.update { |counter| counter - 1 }
239
+ # If one or more writers were waiting, and we were the last reader, wake a writer up
240
+ if waiting_or_running_writer?(c) && running_readers(c) == 0
241
+ @WriteQueue.signal
242
+ end
243
+ elsif rlocks_held == READ_LOCK_MASK
244
+ raise IllegalOperationError, "Cannot release a read lock which is not held"
245
+ end
246
+ true
247
+ end
248
+
249
+ # Acquire a write lock. Will block and wait for all active readers and writers.
250
+ #
251
+ # @return [Boolean] true if the lock is successfully acquired
252
+ #
253
+ # @raise [Concurrent::ResourceLimitError] if the maximum number of writers
254
+ # is exceeded.
255
+ def acquire_write_lock
256
+ if (held = @HeldCount.value) >= WRITE_LOCK_HELD
257
+ # if we already have a write (exclusive) lock, there's no need to wait
258
+ @HeldCount.value = held + WRITE_LOCK_HELD
259
+ return true
260
+ end
261
+
262
+ while true
263
+ c = @Counter.value
264
+ raise ResourceLimitError.new('Too many writer threads') if max_writers?(c)
265
+
266
+ # To go ahead and take the lock without waiting, there must be no writer
267
+ # running right now, AND no writers who came before us still waiting to
268
+ # acquire the lock
269
+ # Additionally, if any read locks have been taken, we must hold all of them
270
+ if c == held
271
+ # If we successfully swap the RUNNING_WRITER bit on, then we can go ahead
272
+ if @Counter.compare_and_set(c, c+RUNNING_WRITER)
273
+ @HeldCount.value = held + WRITE_LOCK_HELD
274
+ return true
275
+ end
276
+ elsif @Counter.compare_and_set(c, c+WAITING_WRITER)
277
+ while true
278
+ # Now we have successfully incremented, so no more readers will be able to increment
279
+ # (they will wait instead)
280
+ # However, readers OR writers could decrement right here
281
+ @WriteQueue.synchronize do
282
+ # So we have to do another check inside the synchronized section
283
+ # If a writer OR another reader is running, then go to sleep
284
+ c = @Counter.value
285
+ @WriteQueue.ns_wait if running_writer?(c) || running_readers(c) != held
286
+ end
287
+ # Note: if you are thinking of replacing the above 'synchronize' block
288
+ # with #wait_until, read the comment in #acquire_read_lock first!
289
+
290
+ # We just came out of a wait
291
+ # If we successfully turn the RUNNING_WRITER bit on with an atomic swap,
292
+ # then we are OK to stop waiting and go ahead
293
+ # Otherwise go back and wait again
294
+ c = @Counter.value
295
+ if !running_writer?(c) &&
296
+ running_readers(c) == held &&
297
+ @Counter.compare_and_set(c, c+RUNNING_WRITER-WAITING_WRITER)
298
+ @HeldCount.value = held + WRITE_LOCK_HELD
299
+ return true
300
+ end
301
+ end
302
+ end
303
+ end
304
+ end
305
+
306
+ # Try to acquire a write lock and return true if we succeed. If it cannot be
307
+ # acquired immediately, return false.
308
+ #
309
+ # @return [Boolean] true if the lock is successfully acquired
310
+ def try_write_lock
311
+ if (held = @HeldCount.value) >= WRITE_LOCK_HELD
312
+ @HeldCount.value = held + WRITE_LOCK_HELD
313
+ return true
314
+ else
315
+ c = @Counter.value
316
+ if !waiting_or_running_writer?(c) &&
317
+ running_readers(c) == held &&
318
+ @Counter.compare_and_set(c, c+RUNNING_WRITER)
319
+ @HeldCount.value = held + WRITE_LOCK_HELD
320
+ return true
321
+ end
322
+ end
323
+ false
324
+ end
325
+
326
+ # Release a previously acquired write lock.
327
+ #
328
+ # @return [Boolean] true if the lock is successfully released
329
+ def release_write_lock
330
+ held = @HeldCount.value = @HeldCount.value - WRITE_LOCK_HELD
331
+ wlocks_held = held & WRITE_LOCK_MASK
332
+ if wlocks_held == 0
333
+ c = @Counter.update { |counter| counter - RUNNING_WRITER }
334
+ @ReadQueue.broadcast
335
+ @WriteQueue.signal if waiting_writers(c) > 0
336
+ elsif wlocks_held == WRITE_LOCK_MASK
337
+ raise IllegalOperationError, "Cannot release a write lock which is not held"
338
+ end
339
+ true
340
+ end
341
+
342
+ private
343
+
344
+ # @!visibility private
345
+ def running_readers(c = @Counter.value)
346
+ c & MAX_READERS
347
+ end
348
+
349
+ # @!visibility private
350
+ def running_readers?(c = @Counter.value)
351
+ (c & MAX_READERS) > 0
352
+ end
353
+
354
+ # @!visibility private
355
+ def running_writer?(c = @Counter.value)
356
+ c >= RUNNING_WRITER
357
+ end
358
+
359
+ # @!visibility private
360
+ def waiting_writers(c = @Counter.value)
361
+ (c & MAX_WRITERS) >> READER_BITS
362
+ end
363
+
364
+ # @!visibility private
365
+ def waiting_or_running_writer?(c = @Counter.value)
366
+ c >= WAITING_WRITER
367
+ end
368
+
369
+ # @!visibility private
370
+ def max_readers?(c = @Counter.value)
371
+ (c & MAX_READERS) == MAX_READERS
372
+ end
373
+
374
+ # @!visibility private
375
+ def max_writers?(c = @Counter.value)
376
+ (c & MAX_WRITERS) == MAX_WRITERS
377
+ end
378
+ end
379
+ end