concurrent-ruby 1.1.8 → 1.2.2

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 (101) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +45 -0
  3. data/Gemfile +2 -8
  4. data/README.md +49 -28
  5. data/Rakefile +66 -81
  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 +189 -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/mri_map_backend.rb +2 -2
  37. data/lib/concurrent-ruby/concurrent/collection/map/non_concurrent_map_backend.rb +16 -8
  38. data/lib/concurrent-ruby/concurrent/collection/map/truffleruby_map_backend.rb +14 -0
  39. data/lib/concurrent-ruby/concurrent/collection/ruby_non_concurrent_priority_queue.rb +11 -1
  40. data/lib/concurrent-ruby/concurrent/concern/logging.rb +86 -2
  41. data/lib/concurrent-ruby/concurrent/concurrent_ruby.jar +0 -0
  42. data/lib/concurrent-ruby/concurrent/configuration.rb +4 -87
  43. data/lib/concurrent-ruby/concurrent/delay.rb +2 -2
  44. data/lib/concurrent-ruby/concurrent/errors.rb +5 -0
  45. data/lib/concurrent-ruby/concurrent/exchanger.rb +1 -0
  46. data/lib/concurrent-ruby/concurrent/executor/abstract_executor_service.rb +17 -14
  47. data/lib/concurrent-ruby/concurrent/executor/fixed_thread_pool.rb +13 -3
  48. data/lib/concurrent-ruby/concurrent/executor/java_executor_service.rb +3 -3
  49. data/lib/concurrent-ruby/concurrent/executor/java_thread_pool_executor.rb +4 -0
  50. data/lib/concurrent-ruby/concurrent/executor/ruby_executor_service.rb +10 -4
  51. data/lib/concurrent-ruby/concurrent/executor/ruby_thread_pool_executor.rb +26 -37
  52. data/lib/concurrent-ruby/concurrent/executor/safe_task_executor.rb +6 -6
  53. data/lib/concurrent-ruby/concurrent/executor/serialized_execution.rb +1 -1
  54. data/lib/concurrent-ruby/concurrent/executor/simple_executor_service.rb +4 -1
  55. data/lib/concurrent-ruby/concurrent/hash.rb +0 -9
  56. data/lib/concurrent-ruby/concurrent/immutable_struct.rb +1 -1
  57. data/lib/concurrent-ruby/concurrent/ivar.rb +2 -1
  58. data/lib/concurrent-ruby/concurrent/map.rb +43 -30
  59. data/lib/concurrent-ruby/concurrent/maybe.rb +1 -1
  60. data/lib/concurrent-ruby/concurrent/mutable_struct.rb +1 -1
  61. data/lib/concurrent-ruby/concurrent/mvar.rb +1 -1
  62. data/lib/concurrent-ruby/concurrent/promise.rb +2 -1
  63. data/lib/concurrent-ruby/concurrent/promises.rb +7 -6
  64. data/lib/concurrent-ruby/concurrent/re_include.rb +2 -0
  65. data/lib/concurrent-ruby/concurrent/scheduled_task.rb +30 -17
  66. data/lib/concurrent-ruby/concurrent/set.rb +12 -14
  67. data/lib/concurrent-ruby/concurrent/settable_struct.rb +2 -2
  68. data/lib/concurrent-ruby/concurrent/synchronization/abstract_lockable_object.rb +5 -1
  69. data/lib/concurrent-ruby/concurrent/synchronization/abstract_object.rb +1 -3
  70. data/lib/concurrent-ruby/concurrent/synchronization/condition.rb +2 -0
  71. data/lib/concurrent-ruby/concurrent/synchronization/full_memory_barrier.rb +29 -0
  72. data/lib/concurrent-ruby/concurrent/synchronization/jruby_lockable_object.rb +3 -1
  73. data/lib/concurrent-ruby/concurrent/synchronization/lock.rb +2 -0
  74. data/lib/concurrent-ruby/concurrent/synchronization/lockable_object.rb +6 -5
  75. data/lib/concurrent-ruby/concurrent/synchronization/mutex_lockable_object.rb +18 -5
  76. data/lib/concurrent-ruby/concurrent/synchronization/object.rb +12 -44
  77. data/lib/concurrent-ruby/concurrent/synchronization/safe_initialization.rb +36 -0
  78. data/lib/concurrent-ruby/concurrent/synchronization/volatile.rb +77 -12
  79. data/lib/concurrent-ruby/concurrent/synchronization.rb +1 -18
  80. data/lib/concurrent-ruby/concurrent/thread_safe/synchronized_delegator.rb +36 -39
  81. data/lib/concurrent-ruby/concurrent/thread_safe/util/cheap_lockable.rb +2 -39
  82. data/lib/concurrent-ruby/concurrent/thread_safe/util/data_structures.rb +16 -27
  83. data/lib/concurrent-ruby/concurrent/timer_task.rb +11 -33
  84. data/lib/concurrent-ruby/concurrent/tuple.rb +1 -5
  85. data/lib/concurrent-ruby/concurrent/tvar.rb +22 -61
  86. data/lib/concurrent-ruby/concurrent/utility/engine.rb +5 -16
  87. data/lib/concurrent-ruby/concurrent/utility/monotonic_time.rb +7 -46
  88. data/lib/concurrent-ruby/concurrent/utility/native_extension_loader.rb +8 -10
  89. data/lib/concurrent-ruby/concurrent/utility/native_integer.rb +1 -0
  90. data/lib/concurrent-ruby/concurrent/utility/processor_counter.rb +36 -89
  91. data/lib/concurrent-ruby/concurrent/version.rb +1 -1
  92. data/lib/concurrent-ruby/concurrent-ruby.rb +5 -1
  93. metadata +11 -12
  94. data/lib/concurrent-ruby/concurrent/atomic/abstract_thread_local_var.rb +0 -66
  95. data/lib/concurrent-ruby/concurrent/atomic/java_thread_local_var.rb +0 -37
  96. data/lib/concurrent-ruby/concurrent/atomic/ruby_thread_local_var.rb +0 -181
  97. data/lib/concurrent-ruby/concurrent/synchronization/jruby_object.rb +0 -45
  98. data/lib/concurrent-ruby/concurrent/synchronization/mri_object.rb +0 -44
  99. data/lib/concurrent-ruby/concurrent/synchronization/rbx_lockable_object.rb +0 -65
  100. data/lib/concurrent-ruby/concurrent/synchronization/rbx_object.rb +0 -49
  101. data/lib/concurrent-ruby/concurrent/synchronization/truffleruby_object.rb +0 -47
@@ -0,0 +1,189 @@
1
+ require 'fiber'
2
+ require 'concurrent/utility/engine'
3
+ require 'concurrent/constants'
4
+
5
+ module Concurrent
6
+ # @!visibility private
7
+ # @!macro internal_implementation_note
8
+ #
9
+ # An abstract implementation of local storage, with sub-classes for
10
+ # per-thread and per-fiber locals.
11
+ #
12
+ # Each execution context (EC, thread or fiber) has a lazily initialized array
13
+ # of local variable values. Each time a new local variable is created, we
14
+ # allocate an "index" for it.
15
+ #
16
+ # For example, if the allocated index is 1, that means slot #1 in EVERY EC's
17
+ # locals array will be used for the value of that variable.
18
+ #
19
+ # The good thing about using a per-EC structure to hold values, rather than
20
+ # a global, is that no synchronization is needed when reading and writing
21
+ # those values (since the structure is only ever accessed by a single
22
+ # thread).
23
+ #
24
+ # Of course, when a local variable is GC'd, 1) we need to recover its index
25
+ # for use by other new local variables (otherwise the locals arrays could
26
+ # get bigger and bigger with time), and 2) we need to null out all the
27
+ # references held in the now-unused slots (both to avoid blocking GC of those
28
+ # objects, and also to prevent "stale" values from being passed on to a new
29
+ # local when the index is reused).
30
+ #
31
+ # Because we need to null out freed slots, we need to keep references to
32
+ # ALL the locals arrays, so we can null out the appropriate slots in all of
33
+ # them. This is why we need to use a finalizer to clean up the locals array
34
+ # when the EC goes out of scope.
35
+ class AbstractLocals
36
+ def initialize
37
+ @free = []
38
+ @lock = Mutex.new
39
+ @all_arrays = {}
40
+ @next = 0
41
+ end
42
+
43
+ def synchronize
44
+ @lock.synchronize { yield }
45
+ end
46
+
47
+ if Concurrent.on_cruby?
48
+ def weak_synchronize
49
+ yield
50
+ end
51
+ else
52
+ alias_method :weak_synchronize, :synchronize
53
+ end
54
+
55
+ def next_index(local)
56
+ index = synchronize do
57
+ if @free.empty?
58
+ @next += 1
59
+ else
60
+ @free.pop
61
+ end
62
+ end
63
+
64
+ # When the local goes out of scope, we should free the associated index
65
+ # and all values stored into it.
66
+ ObjectSpace.define_finalizer(local, local_finalizer(index))
67
+
68
+ index
69
+ end
70
+
71
+ def free_index(index)
72
+ weak_synchronize do
73
+ # The cost of GC'ing a TLV is linear in the number of ECs using local
74
+ # variables. But that is natural! More ECs means more storage is used
75
+ # per local variable. So naturally more CPU time is required to free
76
+ # more storage.
77
+ #
78
+ # DO NOT use each_value which might conflict with new pair assignment
79
+ # into the hash in #set method.
80
+ @all_arrays.values.each do |locals|
81
+ locals[index] = nil
82
+ end
83
+
84
+ # free index has to be published after the arrays are cleared:
85
+ @free << index
86
+ end
87
+ end
88
+
89
+ def fetch(index)
90
+ locals = self.locals
91
+ value = locals ? locals[index] : nil
92
+
93
+ if nil == value
94
+ yield
95
+ elsif NULL.equal?(value)
96
+ nil
97
+ else
98
+ value
99
+ end
100
+ end
101
+
102
+ def set(index, value)
103
+ locals = self.locals!
104
+ locals[index] = (nil == value ? NULL : value)
105
+
106
+ value
107
+ end
108
+
109
+ private
110
+
111
+ # When the local goes out of scope, clean up that slot across all locals currently assigned.
112
+ def local_finalizer(index)
113
+ proc do
114
+ free_index(index)
115
+ end
116
+ end
117
+
118
+ # When a thread/fiber goes out of scope, remove the array from @all_arrays.
119
+ def thread_fiber_finalizer(array_object_id)
120
+ proc do
121
+ weak_synchronize do
122
+ @all_arrays.delete(array_object_id)
123
+ end
124
+ end
125
+ end
126
+
127
+ # Returns the locals for the current scope, or nil if none exist.
128
+ def locals
129
+ raise NotImplementedError
130
+ end
131
+
132
+ # Returns the locals for the current scope, creating them if necessary.
133
+ def locals!
134
+ raise NotImplementedError
135
+ end
136
+ end
137
+
138
+ # @!visibility private
139
+ # @!macro internal_implementation_note
140
+ # An array-backed storage of indexed variables per thread.
141
+ class ThreadLocals < AbstractLocals
142
+ def locals
143
+ Thread.current.thread_variable_get(:concurrent_thread_locals)
144
+ end
145
+
146
+ def locals!
147
+ thread = Thread.current
148
+ locals = thread.thread_variable_get(:concurrent_thread_locals)
149
+
150
+ unless locals
151
+ locals = thread.thread_variable_set(:concurrent_thread_locals, [])
152
+ weak_synchronize do
153
+ @all_arrays[locals.object_id] = locals
154
+ end
155
+ # When the thread goes out of scope, we should delete the associated locals:
156
+ ObjectSpace.define_finalizer(thread, thread_fiber_finalizer(locals.object_id))
157
+ end
158
+
159
+ locals
160
+ end
161
+ end
162
+
163
+ # @!visibility private
164
+ # @!macro internal_implementation_note
165
+ # An array-backed storage of indexed variables per fiber.
166
+ class FiberLocals < AbstractLocals
167
+ def locals
168
+ Thread.current[:concurrent_fiber_locals]
169
+ end
170
+
171
+ def locals!
172
+ thread = Thread.current
173
+ locals = thread[:concurrent_fiber_locals]
174
+
175
+ unless locals
176
+ locals = thread[:concurrent_fiber_locals] = []
177
+ weak_synchronize do
178
+ @all_arrays[locals.object_id] = locals
179
+ end
180
+ # When the fiber goes out of scope, we should delete the associated locals:
181
+ ObjectSpace.define_finalizer(Fiber.current, thread_fiber_finalizer(locals.object_id))
182
+ end
183
+
184
+ locals
185
+ end
186
+ end
187
+
188
+ private_constant :AbstractLocals, :ThreadLocals, :FiberLocals
189
+ 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