concurrent-ruby 0.8.0 → 0.9.0.pre2

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 (144) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +97 -2
  3. data/README.md +103 -54
  4. data/lib/concurrent.rb +34 -14
  5. data/lib/concurrent/async.rb +164 -50
  6. data/lib/concurrent/atom.rb +171 -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 +3 -0
  19. data/lib/concurrent/atomic_reference/jruby.rb +6 -3
  20. data/lib/concurrent/atomic_reference/mutex_atomic.rb +10 -32
  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 +27 -0
  29. data/lib/concurrent/concern/dereferenceable.rb +88 -0
  30. data/lib/concurrent/concern/logging.rb +25 -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 +226 -112
  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 +10 -0
  38. data/lib/concurrent/exchanger.rb +25 -1
  39. data/lib/concurrent/executor/cached_thread_pool.rb +46 -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 +206 -26
  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_cached_thread_pool.rb +18 -16
  46. data/lib/concurrent/executor/java_fixed_thread_pool.rb +11 -18
  47. data/lib/concurrent/executor/java_single_thread_executor.rb +17 -16
  48. data/lib/concurrent/executor/java_thread_pool_executor.rb +55 -102
  49. data/lib/concurrent/executor/ruby_cached_thread_pool.rb +9 -18
  50. data/lib/concurrent/executor/ruby_fixed_thread_pool.rb +10 -21
  51. data/lib/concurrent/executor/ruby_single_thread_executor.rb +14 -16
  52. data/lib/concurrent/executor/ruby_thread_pool_executor.rb +250 -166
  53. data/lib/concurrent/executor/safe_task_executor.rb +5 -4
  54. data/lib/concurrent/executor/serialized_execution.rb +22 -18
  55. data/lib/concurrent/executor/{per_thread_executor.rb → simple_executor_service.rb} +29 -20
  56. data/lib/concurrent/executor/single_thread_executor.rb +32 -21
  57. data/lib/concurrent/executor/thread_pool_executor.rb +72 -60
  58. data/lib/concurrent/executor/timer_set.rb +96 -84
  59. data/lib/concurrent/executors.rb +1 -1
  60. data/lib/concurrent/future.rb +70 -38
  61. data/lib/concurrent/immutable_struct.rb +89 -0
  62. data/lib/concurrent/ivar.rb +152 -60
  63. data/lib/concurrent/lazy_register.rb +40 -20
  64. data/lib/concurrent/maybe.rb +226 -0
  65. data/lib/concurrent/mutable_struct.rb +227 -0
  66. data/lib/concurrent/mvar.rb +44 -43
  67. data/lib/concurrent/promise.rb +208 -134
  68. data/lib/concurrent/scheduled_task.rb +339 -43
  69. data/lib/concurrent/settable_struct.rb +127 -0
  70. data/lib/concurrent/synchronization.rb +17 -0
  71. data/lib/concurrent/synchronization/abstract_object.rb +163 -0
  72. data/lib/concurrent/synchronization/abstract_struct.rb +158 -0
  73. data/lib/concurrent/synchronization/condition.rb +53 -0
  74. data/lib/concurrent/synchronization/java_object.rb +35 -0
  75. data/lib/concurrent/synchronization/lock.rb +32 -0
  76. data/lib/concurrent/synchronization/monitor_object.rb +24 -0
  77. data/lib/concurrent/synchronization/mutex_object.rb +43 -0
  78. data/lib/concurrent/synchronization/object.rb +78 -0
  79. data/lib/concurrent/synchronization/rbx_object.rb +75 -0
  80. data/lib/concurrent/timer_task.rb +87 -100
  81. data/lib/concurrent/tvar.rb +42 -38
  82. data/lib/concurrent/utilities.rb +3 -1
  83. data/lib/concurrent/utility/at_exit.rb +97 -0
  84. data/lib/concurrent/utility/engine.rb +40 -0
  85. data/lib/concurrent/utility/monotonic_time.rb +59 -0
  86. data/lib/concurrent/utility/native_extension_loader.rb +56 -0
  87. data/lib/concurrent/utility/processor_counter.rb +156 -0
  88. data/lib/concurrent/utility/timeout.rb +18 -14
  89. data/lib/concurrent/utility/timer.rb +11 -6
  90. data/lib/concurrent/version.rb +2 -1
  91. data/lib/concurrent_ruby.rb +1 -0
  92. metadata +47 -83
  93. data/lib/concurrent/actor.rb +0 -103
  94. data/lib/concurrent/actor/behaviour.rb +0 -70
  95. data/lib/concurrent/actor/behaviour/abstract.rb +0 -48
  96. data/lib/concurrent/actor/behaviour/awaits.rb +0 -21
  97. data/lib/concurrent/actor/behaviour/buffer.rb +0 -54
  98. data/lib/concurrent/actor/behaviour/errors_on_unknown_message.rb +0 -12
  99. data/lib/concurrent/actor/behaviour/executes_context.rb +0 -18
  100. data/lib/concurrent/actor/behaviour/linking.rb +0 -45
  101. data/lib/concurrent/actor/behaviour/pausing.rb +0 -77
  102. data/lib/concurrent/actor/behaviour/removes_child.rb +0 -16
  103. data/lib/concurrent/actor/behaviour/sets_results.rb +0 -36
  104. data/lib/concurrent/actor/behaviour/supervised.rb +0 -59
  105. data/lib/concurrent/actor/behaviour/supervising.rb +0 -34
  106. data/lib/concurrent/actor/behaviour/terminates_children.rb +0 -13
  107. data/lib/concurrent/actor/behaviour/termination.rb +0 -54
  108. data/lib/concurrent/actor/context.rb +0 -154
  109. data/lib/concurrent/actor/core.rb +0 -217
  110. data/lib/concurrent/actor/default_dead_letter_handler.rb +0 -9
  111. data/lib/concurrent/actor/envelope.rb +0 -41
  112. data/lib/concurrent/actor/errors.rb +0 -27
  113. data/lib/concurrent/actor/internal_delegations.rb +0 -49
  114. data/lib/concurrent/actor/public_delegations.rb +0 -40
  115. data/lib/concurrent/actor/reference.rb +0 -81
  116. data/lib/concurrent/actor/root.rb +0 -37
  117. data/lib/concurrent/actor/type_check.rb +0 -48
  118. data/lib/concurrent/actor/utils.rb +0 -10
  119. data/lib/concurrent/actor/utils/ad_hoc.rb +0 -21
  120. data/lib/concurrent/actor/utils/balancer.rb +0 -42
  121. data/lib/concurrent/actor/utils/broadcast.rb +0 -52
  122. data/lib/concurrent/actor/utils/pool.rb +0 -59
  123. data/lib/concurrent/actress.rb +0 -3
  124. data/lib/concurrent/agent.rb +0 -209
  125. data/lib/concurrent/atomic.rb +0 -92
  126. data/lib/concurrent/atomic/copy_on_notify_observer_set.rb +0 -118
  127. data/lib/concurrent/atomic/copy_on_write_observer_set.rb +0 -117
  128. data/lib/concurrent/atomic/synchronization.rb +0 -51
  129. data/lib/concurrent/channel/buffered_channel.rb +0 -85
  130. data/lib/concurrent/channel/channel.rb +0 -41
  131. data/lib/concurrent/channel/unbuffered_channel.rb +0 -35
  132. data/lib/concurrent/channel/waitable_list.rb +0 -40
  133. data/lib/concurrent/channels.rb +0 -5
  134. data/lib/concurrent/collection/blocking_ring_buffer.rb +0 -71
  135. data/lib/concurrent/collection/ring_buffer.rb +0 -59
  136. data/lib/concurrent/collections.rb +0 -3
  137. data/lib/concurrent/dereferenceable.rb +0 -108
  138. data/lib/concurrent/executor/ruby_thread_pool_worker.rb +0 -73
  139. data/lib/concurrent/logging.rb +0 -20
  140. data/lib/concurrent/obligation.rb +0 -171
  141. data/lib/concurrent/observable.rb +0 -73
  142. data/lib/concurrent/options_parser.rb +0 -52
  143. data/lib/concurrent/utility/processor_count.rb +0 -152
  144. data/lib/extension_helper.rb +0 -37
@@ -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
@@ -1,7 +1,21 @@
1
- require 'concurrent/atomic/condition'
1
+ require 'concurrent/synchronization'
2
2
 
3
3
  module Concurrent
4
- class MutexSemaphore
4
+
5
+ # @!macro [attach] semaphore
6
+ #
7
+ # A counting semaphore. Conceptually, a semaphore maintains a set of
8
+ # permits. Each {#acquire} blocks if necessary until a permit is
9
+ # available, and then takes it. Each {#release} adds a permit, potentially
10
+ # releasing a blocking acquirer.
11
+ # However, no actual permit objects are used; the Semaphore just keeps a
12
+ # count of the number available and acts accordingly.
13
+ #
14
+ # @!visibility private
15
+ #
16
+ # @!macro internal_implementation_note
17
+ class MutexSemaphore < Synchronization::Object
18
+
5
19
  # @!macro [attach] semaphore_method_initialize
6
20
  #
7
21
  # Create a new `Semaphore` with the initial `count`.
@@ -13,9 +27,8 @@ module Concurrent
13
27
  unless count.is_a?(Fixnum) && count >= 0
14
28
  fail ArgumentError, 'count must be an non-negative integer'
15
29
  end
16
- @mutex = Mutex.new
17
- @condition = Condition.new
18
- @free = count
30
+ super()
31
+ synchronize { ns_initialize count }
19
32
  end
20
33
 
21
34
  # @!macro [attach] semaphore_method_acquire
@@ -33,7 +46,7 @@ module Concurrent
33
46
  unless permits.is_a?(Fixnum) && permits > 0
34
47
  fail ArgumentError, 'permits must be an integer greater than zero'
35
48
  end
36
- @mutex.synchronize do
49
+ synchronize do
37
50
  try_acquire_timed(permits, nil)
38
51
  nil
39
52
  end
@@ -45,7 +58,7 @@ module Concurrent
45
58
  #
46
59
  # @return [Integer]
47
60
  def available_permits
48
- @mutex.synchronize { @free }
61
+ synchronize { @free }
49
62
  end
50
63
 
51
64
  # @!macro [attach] semaphore_method_drain_permits
@@ -54,7 +67,7 @@ module Concurrent
54
67
  #
55
68
  # @return [Integer]
56
69
  def drain_permits
57
- @mutex.synchronize do
70
+ synchronize do
58
71
  @free.tap { |_| @free = 0 }
59
72
  end
60
73
  end
@@ -79,7 +92,7 @@ module Concurrent
79
92
  unless permits.is_a?(Fixnum) && permits > 0
80
93
  fail ArgumentError, 'permits must be an integer greater than zero'
81
94
  end
82
- @mutex.synchronize do
95
+ synchronize do
83
96
  if timeout.nil?
84
97
  try_acquire_now(permits)
85
98
  else
@@ -101,15 +114,15 @@ module Concurrent
101
114
  unless permits.is_a?(Fixnum) && permits > 0
102
115
  fail ArgumentError, 'permits must be an integer greater than zero'
103
116
  end
104
- @mutex.synchronize do
117
+ synchronize do
105
118
  @free += permits
106
- permits.times { @condition.signal }
119
+ permits.times { ns_signal }
107
120
  end
108
121
  nil
109
122
  end
110
123
 
111
124
  # @!macro [attach] semaphore_method_reduce_permits
112
- #
125
+ #
113
126
  # @api private
114
127
  #
115
128
  # Shrinks the number of available permits by the indicated reduction.
@@ -125,12 +138,20 @@ module Concurrent
125
138
  unless reduction.is_a?(Fixnum) && reduction >= 0
126
139
  fail ArgumentError, 'reduction must be an non-negative integer'
127
140
  end
128
- @mutex.synchronize { @free -= reduction }
129
- nil
141
+ synchronize { @free -= reduction }
142
+ nil
143
+ end
144
+
145
+ protected
146
+
147
+ # @!visibility private
148
+ def ns_initialize(count)
149
+ @free = count
130
150
  end
131
151
 
132
152
  private
133
153
 
154
+ # @!visibility private
134
155
  def try_acquire_now(permits)
135
156
  if @free >= permits
136
157
  @free -= permits
@@ -140,93 +161,47 @@ module Concurrent
140
161
  end
141
162
  end
142
163
 
164
+ # @!visibility private
143
165
  def try_acquire_timed(permits, timeout)
144
- remaining = Condition::Result.new(timeout)
145
- while !try_acquire_now(permits) && remaining.can_wait?
146
- @condition.signal
147
- remaining = @condition.wait(@mutex, remaining.remaining_time)
148
- end
149
- remaining.can_wait? ? true : false
166
+ ns_wait_until(timeout) { try_acquire_now(permits) }
150
167
  end
151
168
  end
152
169
 
153
- if RUBY_PLATFORM == 'java'
154
-
155
- # @!macro semaphore
156
- #
157
- # A counting semaphore. Conceptually, a semaphore maintains a set of permits. Each {#acquire} blocks if necessary
158
- # until a permit is available, and then takes it. Each {#release} adds a permit,
159
- # potentially releasing a blocking acquirer.
160
- # However, no actual permit objects are used; the Semaphore just keeps a count of the number available and
161
- # acts accordingly.
162
- class JavaSemaphore
163
- # @!macro semaphore_method_initialize
164
- def initialize(count)
165
- unless count.is_a?(Fixnum) && count >= 0
166
- fail(ArgumentError,
167
- 'count must be in integer greater than or equal zero')
168
- end
169
- @semaphore = java.util.concurrent.Semaphore.new(count)
170
- end
170
+ # @!visibility private
171
+ # @!macro internal_implementation_note
172
+ SemaphoreImplementation = case
173
+ when Concurrent.on_jruby?
174
+ JavaSemaphore
175
+ else
176
+ MutexSemaphore
177
+ end
178
+ private_constant :SemaphoreImplementation
171
179
 
172
- # @!macro semaphore_method_acquire
173
- def acquire(permits = 1)
174
- unless permits.is_a?(Fixnum) && permits > 0
175
- fail ArgumentError, 'permits must be an integer greater than zero'
176
- end
177
- @semaphore.acquire(permits)
178
- end
180
+ # @!macro semaphore
181
+ #
182
+ # @see Concurrent::MutexSemaphore
183
+ class Semaphore < SemaphoreImplementation
179
184
 
180
- # @!macro semaphore_method_available_permits
181
- def available_permits
182
- @semaphore.availablePermits
183
- end
185
+ # @!method initialize(count)
186
+ # @!macro semaphore_method_initialize
184
187
 
185
- # @!macro semaphore_method_drain_permits
186
- def drain_permits
187
- @semaphore.drainPermits
188
- end
188
+ # @!method acquire(permits = 1)
189
+ # @!macro semaphore_method_acquire
189
190
 
190
- # @!macro semaphore_method_try_acquire
191
- def try_acquire(permits = 1, timeout = nil)
192
- unless permits.is_a?(Fixnum) && permits > 0
193
- fail ArgumentError, 'permits must be an integer greater than zero'
194
- end
195
- if timeout.nil?
196
- @semaphore.tryAcquire(permits)
197
- else
198
- @semaphore.tryAcquire(permits,
199
- timeout,
200
- java.util.concurrent.TimeUnit::SECONDS)
201
- end
202
- end
191
+ # @!method available_permits
192
+ # @!macro semaphore_method_available_permits
203
193
 
204
- # @!macro semaphore_method_release
205
- def release(permits = 1)
206
- unless permits.is_a?(Fixnum) && permits > 0
207
- fail ArgumentError, 'permits must be an integer greater than zero'
208
- end
209
- @semaphore.release(permits)
210
- true
211
- end
194
+ # @!method drain_permits
195
+ # @!macro semaphore_method_drain_permits
212
196
 
213
- # @!macro semaphore_method_reduce_permits
214
- def reduce_permits(reduction)
215
- unless reduction.is_a?(Fixnum) && reduction >= 0
216
- fail ArgumentError, 'reduction must be an non-negative integer'
217
- end
218
- @semaphore.reducePermits(reduction)
219
- end
220
- end
197
+ # @!method try_acquire(permits = 1, timeout = nil)
198
+ # @!macro semaphore_method_try_acquire
221
199
 
222
- # @!macro semaphore
223
- class Semaphore < JavaSemaphore
224
- end
200
+ # @!method release(permits = 1)
201
+ # @!macro semaphore_method_release
225
202
 
226
- else
203
+ # @!method reduce_permits(reduction)
204
+ # @!macro semaphore_method_reduce_permits
227
205
 
228
- # @!macro semaphore
229
- class Semaphore < MutexSemaphore
230
- end
231
206
  end
232
207
  end