concurrent-ruby 1.1.8 → 1.2.0

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 (99) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +36 -0
  3. data/Gemfile +2 -8
  4. data/README.md +49 -28
  5. data/Rakefile +59 -75
  6. data/ext/concurrent-ruby/com/concurrent_ruby/ext/JavaAtomicFixnumLibrary.java +0 -0
  7. data/ext/concurrent-ruby/com/concurrent_ruby/ext/JavaSemaphoreLibrary.java +52 -22
  8. data/ext/concurrent-ruby/com/concurrent_ruby/ext/SynchronizationLibrary.java +10 -25
  9. data/lib/concurrent-ruby/concurrent/agent.rb +2 -1
  10. data/lib/concurrent-ruby/concurrent/array.rb +0 -10
  11. data/lib/concurrent-ruby/concurrent/async.rb +1 -0
  12. data/lib/concurrent-ruby/concurrent/atom.rb +1 -1
  13. data/lib/concurrent-ruby/concurrent/atomic/atomic_boolean.rb +5 -4
  14. data/lib/concurrent-ruby/concurrent/atomic/atomic_fixnum.rb +5 -4
  15. data/lib/concurrent-ruby/concurrent/atomic/atomic_markable_reference.rb +3 -0
  16. data/lib/concurrent-ruby/concurrent/atomic/atomic_reference.rb +82 -151
  17. data/lib/concurrent-ruby/concurrent/atomic/cyclic_barrier.rb +1 -1
  18. data/lib/concurrent-ruby/concurrent/atomic/event.rb +3 -3
  19. data/lib/concurrent-ruby/concurrent/atomic/fiber_local_var.rb +109 -0
  20. data/lib/concurrent-ruby/concurrent/atomic/java_count_down_latch.rb +1 -0
  21. data/lib/concurrent-ruby/concurrent/atomic/locals.rb +188 -0
  22. data/lib/concurrent-ruby/concurrent/atomic/lock_local_var.rb +28 -0
  23. data/lib/concurrent-ruby/concurrent/atomic/mutex_atomic_boolean.rb +11 -5
  24. data/lib/concurrent-ruby/concurrent/atomic/mutex_atomic_fixnum.rb +11 -5
  25. data/lib/concurrent-ruby/concurrent/atomic/mutex_count_down_latch.rb +1 -1
  26. data/lib/concurrent-ruby/concurrent/atomic/mutex_semaphore.rb +19 -3
  27. data/lib/concurrent-ruby/concurrent/atomic/read_write_lock.rb +2 -1
  28. data/lib/concurrent-ruby/concurrent/atomic/reentrant_read_write_lock.rb +9 -9
  29. data/lib/concurrent-ruby/concurrent/atomic/semaphore.rb +32 -14
  30. data/lib/concurrent-ruby/concurrent/atomic/thread_local_var.rb +96 -89
  31. data/lib/concurrent-ruby/concurrent/atomic_reference/atomic_direct_update.rb +37 -0
  32. data/lib/concurrent-ruby/concurrent/atomic_reference/mutex_atomic.rb +15 -4
  33. data/lib/concurrent-ruby/concurrent/collection/copy_on_notify_observer_set.rb +1 -1
  34. data/lib/concurrent-ruby/concurrent/collection/copy_on_write_observer_set.rb +1 -1
  35. data/lib/concurrent-ruby/concurrent/collection/lock_free_stack.rb +2 -0
  36. data/lib/concurrent-ruby/concurrent/collection/map/truffleruby_map_backend.rb +14 -0
  37. data/lib/concurrent-ruby/concurrent/collection/ruby_non_concurrent_priority_queue.rb +11 -1
  38. data/lib/concurrent-ruby/concurrent/concern/logging.rb +86 -2
  39. data/lib/concurrent-ruby/concurrent/concurrent_ruby.jar +0 -0
  40. data/lib/concurrent-ruby/concurrent/configuration.rb +4 -87
  41. data/lib/concurrent-ruby/concurrent/delay.rb +2 -2
  42. data/lib/concurrent-ruby/concurrent/errors.rb +5 -0
  43. data/lib/concurrent-ruby/concurrent/exchanger.rb +1 -0
  44. data/lib/concurrent-ruby/concurrent/executor/abstract_executor_service.rb +17 -14
  45. data/lib/concurrent-ruby/concurrent/executor/fixed_thread_pool.rb +13 -3
  46. data/lib/concurrent-ruby/concurrent/executor/java_executor_service.rb +3 -3
  47. data/lib/concurrent-ruby/concurrent/executor/java_thread_pool_executor.rb +4 -0
  48. data/lib/concurrent-ruby/concurrent/executor/ruby_executor_service.rb +10 -4
  49. data/lib/concurrent-ruby/concurrent/executor/ruby_thread_pool_executor.rb +26 -37
  50. data/lib/concurrent-ruby/concurrent/executor/safe_task_executor.rb +6 -6
  51. data/lib/concurrent-ruby/concurrent/executor/serialized_execution.rb +1 -1
  52. data/lib/concurrent-ruby/concurrent/executor/simple_executor_service.rb +4 -1
  53. data/lib/concurrent-ruby/concurrent/hash.rb +0 -9
  54. data/lib/concurrent-ruby/concurrent/immutable_struct.rb +1 -1
  55. data/lib/concurrent-ruby/concurrent/ivar.rb +2 -1
  56. data/lib/concurrent-ruby/concurrent/map.rb +18 -8
  57. data/lib/concurrent-ruby/concurrent/maybe.rb +1 -1
  58. data/lib/concurrent-ruby/concurrent/mutable_struct.rb +1 -1
  59. data/lib/concurrent-ruby/concurrent/mvar.rb +1 -1
  60. data/lib/concurrent-ruby/concurrent/promise.rb +2 -1
  61. data/lib/concurrent-ruby/concurrent/promises.rb +7 -6
  62. data/lib/concurrent-ruby/concurrent/re_include.rb +2 -0
  63. data/lib/concurrent-ruby/concurrent/scheduled_task.rb +30 -17
  64. data/lib/concurrent-ruby/concurrent/set.rb +12 -14
  65. data/lib/concurrent-ruby/concurrent/settable_struct.rb +2 -2
  66. data/lib/concurrent-ruby/concurrent/synchronization/abstract_lockable_object.rb +5 -1
  67. data/lib/concurrent-ruby/concurrent/synchronization/abstract_object.rb +1 -3
  68. data/lib/concurrent-ruby/concurrent/synchronization/condition.rb +2 -0
  69. data/lib/concurrent-ruby/concurrent/synchronization/full_memory_barrier.rb +29 -0
  70. data/lib/concurrent-ruby/concurrent/synchronization/jruby_lockable_object.rb +3 -1
  71. data/lib/concurrent-ruby/concurrent/synchronization/lock.rb +2 -0
  72. data/lib/concurrent-ruby/concurrent/synchronization/lockable_object.rb +6 -5
  73. data/lib/concurrent-ruby/concurrent/synchronization/mutex_lockable_object.rb +18 -5
  74. data/lib/concurrent-ruby/concurrent/synchronization/object.rb +12 -44
  75. data/lib/concurrent-ruby/concurrent/synchronization/safe_initialization.rb +36 -0
  76. data/lib/concurrent-ruby/concurrent/synchronization/volatile.rb +77 -12
  77. data/lib/concurrent-ruby/concurrent/synchronization.rb +1 -18
  78. data/lib/concurrent-ruby/concurrent/thread_safe/synchronized_delegator.rb +36 -39
  79. data/lib/concurrent-ruby/concurrent/thread_safe/util/cheap_lockable.rb +2 -39
  80. data/lib/concurrent-ruby/concurrent/thread_safe/util/data_structures.rb +16 -27
  81. data/lib/concurrent-ruby/concurrent/timer_task.rb +11 -33
  82. data/lib/concurrent-ruby/concurrent/tuple.rb +1 -5
  83. data/lib/concurrent-ruby/concurrent/tvar.rb +22 -61
  84. data/lib/concurrent-ruby/concurrent/utility/engine.rb +5 -16
  85. data/lib/concurrent-ruby/concurrent/utility/monotonic_time.rb +7 -46
  86. data/lib/concurrent-ruby/concurrent/utility/native_extension_loader.rb +8 -10
  87. data/lib/concurrent-ruby/concurrent/utility/native_integer.rb +1 -0
  88. data/lib/concurrent-ruby/concurrent/utility/processor_counter.rb +36 -89
  89. data/lib/concurrent-ruby/concurrent/version.rb +1 -1
  90. data/lib/concurrent-ruby/concurrent-ruby.rb +5 -1
  91. metadata +11 -12
  92. data/lib/concurrent-ruby/concurrent/atomic/abstract_thread_local_var.rb +0 -66
  93. data/lib/concurrent-ruby/concurrent/atomic/java_thread_local_var.rb +0 -37
  94. data/lib/concurrent-ruby/concurrent/atomic/ruby_thread_local_var.rb +0 -181
  95. data/lib/concurrent-ruby/concurrent/synchronization/jruby_object.rb +0 -45
  96. data/lib/concurrent-ruby/concurrent/synchronization/mri_object.rb +0 -44
  97. data/lib/concurrent-ruby/concurrent/synchronization/rbx_lockable_object.rb +0 -65
  98. data/lib/concurrent-ruby/concurrent/synchronization/rbx_object.rb +0 -49
  99. data/lib/concurrent-ruby/concurrent/synchronization/truffleruby_object.rb +0 -47
@@ -0,0 +1,188 @@
1
+ require 'concurrent/utility/engine'
2
+ require 'concurrent/constants'
3
+
4
+ module Concurrent
5
+ # @!visibility private
6
+ # @!macro internal_implementation_note
7
+ #
8
+ # An abstract implementation of local storage, with sub-classes for
9
+ # per-thread and per-fiber locals.
10
+ #
11
+ # Each execution context (EC, thread or fiber) has a lazily initialized array
12
+ # of local variable values. Each time a new local variable is created, we
13
+ # allocate an "index" for it.
14
+ #
15
+ # For example, if the allocated index is 1, that means slot #1 in EVERY EC's
16
+ # locals array will be used for the value of that variable.
17
+ #
18
+ # The good thing about using a per-EC structure to hold values, rather than
19
+ # a global, is that no synchronization is needed when reading and writing
20
+ # those values (since the structure is only ever accessed by a single
21
+ # thread).
22
+ #
23
+ # Of course, when a local variable is GC'd, 1) we need to recover its index
24
+ # for use by other new local variables (otherwise the locals arrays could
25
+ # get bigger and bigger with time), and 2) we need to null out all the
26
+ # references held in the now-unused slots (both to avoid blocking GC of those
27
+ # objects, and also to prevent "stale" values from being passed on to a new
28
+ # local when the index is reused).
29
+ #
30
+ # Because we need to null out freed slots, we need to keep references to
31
+ # ALL the locals arrays, so we can null out the appropriate slots in all of
32
+ # them. This is why we need to use a finalizer to clean up the locals array
33
+ # when the EC goes out of scope.
34
+ class AbstractLocals
35
+ def initialize
36
+ @free = []
37
+ @lock = Mutex.new
38
+ @all_arrays = {}
39
+ @next = 0
40
+ end
41
+
42
+ def synchronize
43
+ @lock.synchronize { yield }
44
+ end
45
+
46
+ if Concurrent.on_cruby?
47
+ def weak_synchronize
48
+ yield
49
+ end
50
+ else
51
+ alias_method :weak_synchronize, :synchronize
52
+ end
53
+
54
+ def next_index(local)
55
+ index = synchronize do
56
+ if @free.empty?
57
+ @next += 1
58
+ else
59
+ @free.pop
60
+ end
61
+ end
62
+
63
+ # When the local goes out of scope, we should free the associated index
64
+ # and all values stored into it.
65
+ ObjectSpace.define_finalizer(local, local_finalizer(index))
66
+
67
+ index
68
+ end
69
+
70
+ def free_index(index)
71
+ weak_synchronize do
72
+ # The cost of GC'ing a TLV is linear in the number of ECs using local
73
+ # variables. But that is natural! More ECs means more storage is used
74
+ # per local variable. So naturally more CPU time is required to free
75
+ # more storage.
76
+ #
77
+ # DO NOT use each_value which might conflict with new pair assignment
78
+ # into the hash in #set method.
79
+ @all_arrays.values.each do |locals|
80
+ locals[index] = nil
81
+ end
82
+
83
+ # free index has to be published after the arrays are cleared:
84
+ @free << index
85
+ end
86
+ end
87
+
88
+ def fetch(index)
89
+ locals = self.locals
90
+ value = locals ? locals[index] : nil
91
+
92
+ if nil == value
93
+ yield
94
+ elsif NULL.equal?(value)
95
+ nil
96
+ else
97
+ value
98
+ end
99
+ end
100
+
101
+ def set(index, value)
102
+ locals = self.locals!
103
+ locals[index] = (nil == value ? NULL : value)
104
+
105
+ value
106
+ end
107
+
108
+ private
109
+
110
+ # When the local goes out of scope, clean up that slot across all locals currently assigned.
111
+ def local_finalizer(index)
112
+ proc do
113
+ free_index(index)
114
+ end
115
+ end
116
+
117
+ # When a thread/fiber goes out of scope, remove the array from @all_arrays.
118
+ def thread_fiber_finalizer(array_object_id)
119
+ proc do
120
+ weak_synchronize do
121
+ @all_arrays.delete(array_object_id)
122
+ end
123
+ end
124
+ end
125
+
126
+ # Returns the locals for the current scope, or nil if none exist.
127
+ def locals
128
+ raise NotImplementedError
129
+ end
130
+
131
+ # Returns the locals for the current scope, creating them if necessary.
132
+ def locals!
133
+ raise NotImplementedError
134
+ end
135
+ end
136
+
137
+ # @!visibility private
138
+ # @!macro internal_implementation_note
139
+ # An array-backed storage of indexed variables per thread.
140
+ class ThreadLocals < AbstractLocals
141
+ def locals
142
+ Thread.current.thread_variable_get(:concurrent_thread_locals)
143
+ end
144
+
145
+ def locals!
146
+ thread = Thread.current
147
+ locals = thread.thread_variable_get(:concurrent_thread_locals)
148
+
149
+ unless locals
150
+ locals = thread.thread_variable_set(:concurrent_thread_locals, [])
151
+ weak_synchronize do
152
+ @all_arrays[locals.object_id] = locals
153
+ end
154
+ # When the thread goes out of scope, we should delete the associated locals:
155
+ ObjectSpace.define_finalizer(thread, thread_fiber_finalizer(locals.object_id))
156
+ end
157
+
158
+ locals
159
+ end
160
+ end
161
+
162
+ # @!visibility private
163
+ # @!macro internal_implementation_note
164
+ # An array-backed storage of indexed variables per fiber.
165
+ class FiberLocals < AbstractLocals
166
+ def locals
167
+ Thread.current[:concurrent_fiber_locals]
168
+ end
169
+
170
+ def locals!
171
+ thread = Thread.current
172
+ locals = thread[:concurrent_fiber_locals]
173
+
174
+ unless locals
175
+ locals = thread[:concurrent_fiber_locals] = []
176
+ weak_synchronize do
177
+ @all_arrays[locals.object_id] = locals
178
+ end
179
+ # When the fiber goes out of scope, we should delete the associated locals:
180
+ ObjectSpace.define_finalizer(Fiber.current, thread_fiber_finalizer(locals.object_id))
181
+ end
182
+
183
+ locals
184
+ end
185
+ end
186
+
187
+ private_constant :AbstractLocals, :ThreadLocals, :FiberLocals
188
+ end
@@ -0,0 +1,28 @@
1
+ require 'concurrent/utility/engine'
2
+ require_relative 'fiber_local_var'
3
+ require_relative 'thread_local_var'
4
+
5
+ module Concurrent
6
+ # @!visibility private
7
+ def self.mutex_owned_per_thread?
8
+ return false if Concurrent.on_jruby? || Concurrent.on_truffleruby?
9
+
10
+ mutex = Mutex.new
11
+ # Lock the mutex:
12
+ mutex.synchronize do
13
+ # Check if the mutex is still owned in a child fiber:
14
+ Fiber.new { mutex.owned? }.resume
15
+ end
16
+ end
17
+
18
+ if mutex_owned_per_thread?
19
+ LockLocalVar = ThreadLocalVar
20
+ else
21
+ LockLocalVar = FiberLocalVar
22
+ end
23
+
24
+ # Either {FiberLocalVar} or {ThreadLocalVar} depending on whether Mutex (and Monitor)
25
+ # are held, respectively, per Fiber or per Thread.
26
+ class LockLocalVar
27
+ end
28
+ end
@@ -1,16 +1,18 @@
1
- require 'concurrent/synchronization'
1
+ require 'concurrent/synchronization/safe_initialization'
2
2
 
3
3
  module Concurrent
4
4
 
5
5
  # @!macro atomic_boolean
6
6
  # @!visibility private
7
7
  # @!macro internal_implementation_note
8
- class MutexAtomicBoolean < Synchronization::LockableObject
8
+ class MutexAtomicBoolean
9
+ extend Concurrent::Synchronization::SafeInitialization
9
10
 
10
11
  # @!macro atomic_boolean_method_initialize
11
12
  def initialize(initial = false)
12
13
  super()
13
- synchronize { ns_initialize(initial) }
14
+ @Lock = ::Mutex.new
15
+ @value = !!initial
14
16
  end
15
17
 
16
18
  # @!macro atomic_boolean_method_value_get
@@ -46,8 +48,12 @@ module Concurrent
46
48
  protected
47
49
 
48
50
  # @!visibility private
49
- def ns_initialize(initial)
50
- @value = !!initial
51
+ def synchronize
52
+ if @Lock.owned?
53
+ yield
54
+ else
55
+ @Lock.synchronize { yield }
56
+ end
51
57
  end
52
58
 
53
59
  private
@@ -1,4 +1,4 @@
1
- require 'concurrent/synchronization'
1
+ require 'concurrent/synchronization/safe_initialization'
2
2
  require 'concurrent/utility/native_integer'
3
3
 
4
4
  module Concurrent
@@ -6,12 +6,14 @@ module Concurrent
6
6
  # @!macro atomic_fixnum
7
7
  # @!visibility private
8
8
  # @!macro internal_implementation_note
9
- class MutexAtomicFixnum < Synchronization::LockableObject
9
+ class MutexAtomicFixnum
10
+ extend Concurrent::Synchronization::SafeInitialization
10
11
 
11
12
  # @!macro atomic_fixnum_method_initialize
12
13
  def initialize(initial = 0)
13
14
  super()
14
- synchronize { ns_initialize(initial) }
15
+ @Lock = ::Mutex.new
16
+ ns_set(initial)
15
17
  end
16
18
 
17
19
  # @!macro atomic_fixnum_method_value_get
@@ -60,8 +62,12 @@ module Concurrent
60
62
  protected
61
63
 
62
64
  # @!visibility private
63
- def ns_initialize(initial)
64
- ns_set(initial)
65
+ def synchronize
66
+ if @Lock.owned?
67
+ yield
68
+ else
69
+ @Lock.synchronize { yield }
70
+ end
65
71
  end
66
72
 
67
73
  private
@@ -1,4 +1,4 @@
1
- require 'concurrent/synchronization'
1
+ require 'concurrent/synchronization/lockable_object'
2
2
  require 'concurrent/utility/native_integer'
3
3
 
4
4
  module Concurrent
@@ -1,4 +1,4 @@
1
- require 'concurrent/synchronization'
1
+ require 'concurrent/synchronization/lockable_object'
2
2
  require 'concurrent/utility/native_integer'
3
3
 
4
4
  module Concurrent
@@ -23,7 +23,14 @@ module Concurrent
23
23
 
24
24
  synchronize do
25
25
  try_acquire_timed(permits, nil)
26
- nil
26
+ end
27
+
28
+ return unless block_given?
29
+
30
+ begin
31
+ yield
32
+ ensure
33
+ release(permits)
27
34
  end
28
35
  end
29
36
 
@@ -48,13 +55,22 @@ module Concurrent
48
55
  Utility::NativeInteger.ensure_integer_and_bounds permits
49
56
  Utility::NativeInteger.ensure_positive permits
50
57
 
51
- synchronize do
58
+ acquired = synchronize do
52
59
  if timeout.nil?
53
60
  try_acquire_now(permits)
54
61
  else
55
62
  try_acquire_timed(permits, timeout)
56
63
  end
57
64
  end
65
+
66
+ return acquired unless block_given?
67
+ return unless acquired
68
+
69
+ begin
70
+ yield
71
+ ensure
72
+ release(permits)
73
+ end
58
74
  end
59
75
 
60
76
  # @!macro semaphore_method_release
@@ -1,7 +1,8 @@
1
1
  require 'thread'
2
2
  require 'concurrent/atomic/atomic_fixnum'
3
3
  require 'concurrent/errors'
4
- require 'concurrent/synchronization'
4
+ require 'concurrent/synchronization/object'
5
+ require 'concurrent/synchronization/lock'
5
6
 
6
7
  module Concurrent
7
8
 
@@ -1,8 +1,10 @@
1
1
  require 'thread'
2
2
  require 'concurrent/atomic/atomic_reference'
3
+ require 'concurrent/atomic/atomic_fixnum'
3
4
  require 'concurrent/errors'
4
- require 'concurrent/synchronization'
5
- require 'concurrent/atomic/thread_local_var'
5
+ require 'concurrent/synchronization/object'
6
+ require 'concurrent/synchronization/lock'
7
+ require 'concurrent/atomic/lock_local_var'
6
8
 
7
9
  module Concurrent
8
10
 
@@ -109,7 +111,7 @@ module Concurrent
109
111
  @Counter = AtomicFixnum.new(0) # single integer which represents lock state
110
112
  @ReadQueue = Synchronization::Lock.new # used to queue waiting readers
111
113
  @WriteQueue = Synchronization::Lock.new # used to queue waiting writers
112
- @HeldCount = ThreadLocalVar.new(0) # indicates # of R & W locks held by this thread
114
+ @HeldCount = LockLocalVar.new(0) # indicates # of R & W locks held by this thread
113
115
  end
114
116
 
115
117
  # Execute a block operation within a read lock.
@@ -267,12 +269,10 @@ module Concurrent
267
269
  # running right now, AND no writers who came before us still waiting to
268
270
  # acquire the lock
269
271
  # 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
272
+ if held > 0 && @Counter.compare_and_set(1, c+RUNNING_WRITER)
273
+ # If we are the only one reader and successfully swap the RUNNING_WRITER bit on, then we can go ahead
274
+ @HeldCount.value = held + WRITE_LOCK_HELD
275
+ return true
276
276
  elsif @Counter.compare_and_set(c, c+WAITING_WRITER)
277
277
  while true
278
278
  # Now we have successfully incremented, so no more readers will be able to increment
@@ -1,5 +1,4 @@
1
1
  require 'concurrent/atomic/mutex_semaphore'
2
- require 'concurrent/synchronization'
3
2
 
4
3
  module Concurrent
5
4
 
@@ -11,19 +10,20 @@ module Concurrent
11
10
  #
12
11
  # @param [Fixnum] count the initial count
13
12
  #
14
- # @raise [ArgumentError] if `count` is not an integer or is less than zero
13
+ # @raise [ArgumentError] if `count` is not an integer
15
14
 
16
15
  # @!macro semaphore_method_acquire
17
16
  #
18
17
  # Acquires the given number of permits from this semaphore,
19
- # blocking until all are available.
18
+ # blocking until all are available. If a block is given,
19
+ # yields to it and releases the permits afterwards.
20
20
  #
21
21
  # @param [Fixnum] permits Number of permits to acquire
22
22
  #
23
- # @raise [ArgumentError] if `permits` is not an integer or is less than
24
- # one
23
+ # @raise [ArgumentError] if `permits` is not an integer or is less than zero
25
24
  #
26
- # @return [nil]
25
+ # @return [nil, BasicObject] Without a block, `nil` is returned. If a block
26
+ # is given, its return value is returned.
27
27
 
28
28
  # @!macro semaphore_method_available_permits
29
29
  #
@@ -41,18 +41,21 @@ module Concurrent
41
41
  #
42
42
  # Acquires the given number of permits from this semaphore,
43
43
  # only if all are available at the time of invocation or within
44
- # `timeout` interval
44
+ # `timeout` interval. If a block is given, yields to it if the permits
45
+ # were successfully acquired, and releases them afterward, returning the
46
+ # block's return value.
45
47
  #
46
48
  # @param [Fixnum] permits the number of permits to acquire
47
49
  #
48
50
  # @param [Fixnum] timeout the number of seconds to wait for the counter
49
51
  # or `nil` to return immediately
50
52
  #
51
- # @raise [ArgumentError] if `permits` is not an integer or is less than
52
- # one
53
+ # @raise [ArgumentError] if `permits` is not an integer or is less than zero
53
54
  #
54
- # @return [Boolean] `false` if no permits are available, `true` when
55
- # acquired a permit
55
+ # @return [true, false, nil, BasicObject] `false` if no permits are
56
+ # available, `true` when acquired a permit. If a block is given, the
57
+ # block's return value is returned if the permits were acquired; if not,
58
+ # `nil` is returned.
56
59
 
57
60
  # @!macro semaphore_method_release
58
61
  #
@@ -60,7 +63,7 @@ module Concurrent
60
63
  #
61
64
  # @param [Fixnum] permits Number of permits to return to the semaphore.
62
65
  #
63
- # @raise [ArgumentError] if `permits` is not a number or is less than one
66
+ # @raise [ArgumentError] if `permits` is not a number or is less than zero
64
67
  #
65
68
  # @return [nil]
66
69
 
@@ -90,8 +93,8 @@ module Concurrent
90
93
 
91
94
  # @!visibility private
92
95
  # @!macro internal_implementation_note
93
- SemaphoreImplementation = case
94
- when defined?(JavaSemaphore)
96
+ SemaphoreImplementation = if Concurrent.on_jruby?
97
+ require 'concurrent/utility/native_extension_loader'
95
98
  JavaSemaphore
96
99
  else
97
100
  MutexSemaphore
@@ -106,6 +109,8 @@ module Concurrent
106
109
  # releasing a blocking acquirer.
107
110
  # However, no actual permit objects are used; the Semaphore just keeps a
108
111
  # count of the number available and acts accordingly.
112
+ # Alternatively, permits may be acquired within a block, and automatically
113
+ # released after the block finishes executing.
109
114
  #
110
115
  # @!macro semaphore_public_api
111
116
  # @example
@@ -140,6 +145,19 @@ module Concurrent
140
145
  # # Thread 4 releasing semaphore
141
146
  # # Thread 1 acquired semaphore
142
147
  #
148
+ # @example
149
+ # semaphore = Concurrent::Semaphore.new(1)
150
+ #
151
+ # puts semaphore.available_permits
152
+ # semaphore.acquire do
153
+ # puts semaphore.available_permits
154
+ # end
155
+ # puts semaphore.available_permits
156
+ #
157
+ # # prints:
158
+ # # 1
159
+ # # 0
160
+ # # 1
143
161
  class Semaphore < SemaphoreImplementation
144
162
  end
145
163
  end