concurrent-ruby 1.1.10 → 1.3.7

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 (110) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +76 -3
  3. data/Gemfile +6 -7
  4. data/README.md +28 -23
  5. data/Rakefile +90 -76
  6. data/ext/concurrent-ruby/com/concurrent_ruby/ext/AtomicReferenceLibrary.java +3 -38
  7. data/ext/concurrent-ruby/com/concurrent_ruby/ext/SynchronizationLibrary.java +10 -25
  8. data/ext/concurrent-ruby/com/concurrent_ruby/ext/jsr166e/ConcurrentHashMapV8.java +1 -1
  9. data/ext/concurrent-ruby/com/concurrent_ruby/ext/jsr166e/nounsafe/ConcurrentHashMapV8.java +1 -1
  10. data/lib/concurrent-ruby/concurrent/agent.rb +4 -3
  11. data/lib/concurrent-ruby/concurrent/array.rb +3 -13
  12. data/lib/concurrent-ruby/concurrent/async.rb +1 -1
  13. data/lib/concurrent-ruby/concurrent/atom.rb +2 -2
  14. data/lib/concurrent-ruby/concurrent/atomic/atomic_boolean.rb +5 -4
  15. data/lib/concurrent-ruby/concurrent/atomic/atomic_fixnum.rb +5 -4
  16. data/lib/concurrent-ruby/concurrent/atomic/atomic_markable_reference.rb +3 -0
  17. data/lib/concurrent-ruby/concurrent/atomic/atomic_reference.rb +90 -153
  18. data/lib/concurrent-ruby/concurrent/atomic/cyclic_barrier.rb +1 -1
  19. data/lib/concurrent-ruby/concurrent/atomic/event.rb +1 -1
  20. data/lib/concurrent-ruby/concurrent/atomic/fiber_local_var.rb +109 -0
  21. data/lib/concurrent-ruby/concurrent/atomic/java_count_down_latch.rb +1 -0
  22. data/lib/concurrent-ruby/concurrent/atomic/locals.rb +189 -0
  23. data/lib/concurrent-ruby/concurrent/atomic/lock_local_var.rb +29 -0
  24. data/lib/concurrent-ruby/concurrent/atomic/mutex_atomic_boolean.rb +11 -5
  25. data/lib/concurrent-ruby/concurrent/atomic/mutex_atomic_fixnum.rb +11 -5
  26. data/lib/concurrent-ruby/concurrent/atomic/mutex_count_down_latch.rb +1 -1
  27. data/lib/concurrent-ruby/concurrent/atomic/mutex_semaphore.rb +1 -1
  28. data/lib/concurrent-ruby/concurrent/atomic/read_write_lock.rb +17 -3
  29. data/lib/concurrent-ruby/concurrent/atomic/reentrant_read_write_lock.rb +13 -4
  30. data/lib/concurrent-ruby/concurrent/atomic/semaphore.rb +6 -9
  31. data/lib/concurrent-ruby/concurrent/atomic/thread_local_var.rb +96 -89
  32. data/lib/concurrent-ruby/concurrent/atomic_reference/atomic_direct_update.rb +37 -0
  33. data/lib/concurrent-ruby/concurrent/atomic_reference/mutex_atomic.rb +16 -6
  34. data/lib/concurrent-ruby/concurrent/atomic_reference/numeric_cas_wrapper.rb +9 -1
  35. data/lib/concurrent-ruby/concurrent/collection/copy_on_notify_observer_set.rb +1 -1
  36. data/lib/concurrent-ruby/concurrent/collection/copy_on_write_observer_set.rb +1 -1
  37. data/lib/concurrent-ruby/concurrent/collection/lock_free_stack.rb +2 -0
  38. data/lib/concurrent-ruby/concurrent/collection/map/mri_map_backend.rb +2 -2
  39. data/lib/concurrent-ruby/concurrent/collection/map/non_concurrent_map_backend.rb +16 -8
  40. data/lib/concurrent-ruby/concurrent/collection/map/synchronized_map_backend.rb +23 -20
  41. data/lib/concurrent-ruby/concurrent/collection/ruby_timeout_queue.rb +55 -0
  42. data/lib/concurrent-ruby/concurrent/collection/timeout_queue.rb +18 -0
  43. data/lib/concurrent-ruby/concurrent/concern/logging.rb +95 -6
  44. data/lib/concurrent-ruby/concurrent/concurrent_ruby.jar +0 -0
  45. data/lib/concurrent-ruby/concurrent/configuration.rb +4 -87
  46. data/lib/concurrent-ruby/concurrent/delay.rb +3 -3
  47. data/lib/concurrent-ruby/concurrent/errors.rb +5 -0
  48. data/lib/concurrent-ruby/concurrent/exchanger.rb +1 -0
  49. data/lib/concurrent-ruby/concurrent/executor/abstract_executor_service.rb +1 -1
  50. data/lib/concurrent-ruby/concurrent/executor/fixed_thread_pool.rb +6 -4
  51. data/lib/concurrent-ruby/concurrent/executor/java_executor_service.rb +7 -9
  52. data/lib/concurrent-ruby/concurrent/executor/java_thread_pool_executor.rb +7 -0
  53. data/lib/concurrent-ruby/concurrent/executor/ruby_single_thread_executor.rb +2 -0
  54. data/lib/concurrent-ruby/concurrent/executor/ruby_thread_pool_executor.rb +61 -31
  55. data/lib/concurrent-ruby/concurrent/executor/safe_task_executor.rb +1 -1
  56. data/lib/concurrent-ruby/concurrent/executor/serialized_execution.rb +1 -1
  57. data/lib/concurrent-ruby/concurrent/executor/simple_executor_service.rb +4 -1
  58. data/lib/concurrent-ruby/concurrent/executor/single_thread_executor.rb +1 -1
  59. data/lib/concurrent-ruby/concurrent/executor/timer_set.rb +10 -3
  60. data/lib/concurrent-ruby/concurrent/executors.rb +0 -1
  61. data/lib/concurrent-ruby/concurrent/hash.rb +5 -12
  62. data/lib/concurrent-ruby/concurrent/immutable_struct.rb +1 -1
  63. data/lib/concurrent-ruby/concurrent/ivar.rb +2 -1
  64. data/lib/concurrent-ruby/concurrent/map.rb +43 -39
  65. data/lib/concurrent-ruby/concurrent/maybe.rb +1 -1
  66. data/lib/concurrent-ruby/concurrent/mutable_struct.rb +1 -1
  67. data/lib/concurrent-ruby/concurrent/mvar.rb +5 -5
  68. data/lib/concurrent-ruby/concurrent/promise.rb +3 -3
  69. data/lib/concurrent-ruby/concurrent/promises.rb +40 -29
  70. data/lib/concurrent-ruby/concurrent/re_include.rb +2 -0
  71. data/lib/concurrent-ruby/concurrent/scheduled_task.rb +2 -2
  72. data/lib/concurrent-ruby/concurrent/set.rb +0 -10
  73. data/lib/concurrent-ruby/concurrent/settable_struct.rb +2 -2
  74. data/lib/concurrent-ruby/concurrent/synchronization/abstract_lockable_object.rb +5 -1
  75. data/lib/concurrent-ruby/concurrent/synchronization/abstract_object.rb +1 -3
  76. data/lib/concurrent-ruby/concurrent/synchronization/abstract_struct.rb +1 -1
  77. data/lib/concurrent-ruby/concurrent/synchronization/condition.rb +2 -0
  78. data/lib/concurrent-ruby/concurrent/synchronization/full_memory_barrier.rb +29 -0
  79. data/lib/concurrent-ruby/concurrent/synchronization/jruby_lockable_object.rb +3 -1
  80. data/lib/concurrent-ruby/concurrent/synchronization/lock.rb +2 -0
  81. data/lib/concurrent-ruby/concurrent/synchronization/lockable_object.rb +5 -2
  82. data/lib/concurrent-ruby/concurrent/synchronization/mutex_lockable_object.rb +6 -5
  83. data/lib/concurrent-ruby/concurrent/synchronization/object.rb +13 -45
  84. data/lib/concurrent-ruby/concurrent/synchronization/safe_initialization.rb +36 -0
  85. data/lib/concurrent-ruby/concurrent/synchronization/volatile.rb +77 -12
  86. data/lib/concurrent-ruby/concurrent/synchronization.rb +1 -18
  87. data/lib/concurrent-ruby/concurrent/thread_safe/synchronized_delegator.rb +36 -39
  88. data/lib/concurrent-ruby/concurrent/thread_safe/util/adder.rb +1 -1
  89. data/lib/concurrent-ruby/concurrent/thread_safe/util/data_structures.rb +1 -37
  90. data/lib/concurrent-ruby/concurrent/thread_safe/util/xor_shift_random.rb +1 -1
  91. data/lib/concurrent-ruby/concurrent/timer_task.rb +65 -10
  92. data/lib/concurrent-ruby/concurrent/tuple.rb +1 -5
  93. data/lib/concurrent-ruby/concurrent/tvar.rb +2 -1
  94. data/lib/concurrent-ruby/concurrent/utility/engine.rb +5 -16
  95. data/lib/concurrent-ruby/concurrent/utility/monotonic_time.rb +3 -74
  96. data/lib/concurrent-ruby/concurrent/utility/native_extension_loader.rb +8 -10
  97. data/lib/concurrent-ruby/concurrent/utility/native_integer.rb +1 -0
  98. data/lib/concurrent-ruby/concurrent/utility/processor_counter.rb +148 -58
  99. data/lib/concurrent-ruby/concurrent/version.rb +1 -1
  100. metadata +14 -19
  101. data/lib/concurrent-ruby/concurrent/atomic/abstract_thread_local_var.rb +0 -66
  102. data/lib/concurrent-ruby/concurrent/atomic/java_thread_local_var.rb +0 -37
  103. data/lib/concurrent-ruby/concurrent/atomic/ruby_thread_local_var.rb +0 -181
  104. data/lib/concurrent-ruby/concurrent/collection/map/atomic_reference_map_backend.rb +0 -927
  105. data/lib/concurrent-ruby/concurrent/synchronization/jruby_object.rb +0 -45
  106. data/lib/concurrent-ruby/concurrent/synchronization/mri_object.rb +0 -44
  107. data/lib/concurrent-ruby/concurrent/synchronization/rbx_lockable_object.rb +0 -71
  108. data/lib/concurrent-ruby/concurrent/synchronization/rbx_object.rb +0 -49
  109. data/lib/concurrent-ruby/concurrent/synchronization/truffleruby_object.rb +0 -47
  110. data/lib/concurrent-ruby/concurrent/thread_safe/util/cheap_lockable.rb +0 -118
@@ -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,29 @@
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
+ return RUBY_VERSION < "3.0" if Concurrent.on_cruby?
10
+
11
+ mutex = Mutex.new
12
+ # Lock the mutex:
13
+ mutex.synchronize do
14
+ # Check if the mutex is still owned in a child fiber:
15
+ Fiber.new { mutex.owned? }.resume
16
+ end
17
+ end
18
+
19
+ if mutex_owned_per_thread?
20
+ LockLocalVar = ThreadLocalVar
21
+ else
22
+ LockLocalVar = FiberLocalVar
23
+ end
24
+
25
+ # Either {FiberLocalVar} or {ThreadLocalVar} depending on whether Mutex (and Monitor)
26
+ # are held, respectively, per Fiber or per Thread.
27
+ class LockLocalVar
28
+ end
29
+ 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
@@ -1,7 +1,9 @@
1
1
  require 'thread'
2
2
  require 'concurrent/atomic/atomic_fixnum'
3
+ require 'concurrent/atomic/atomic_reference'
3
4
  require 'concurrent/errors'
4
- require 'concurrent/synchronization'
5
+ require 'concurrent/synchronization/object'
6
+ require 'concurrent/synchronization/lock'
5
7
 
6
8
  module Concurrent
7
9
 
@@ -57,7 +59,8 @@ module Concurrent
57
59
  # Create a new `ReadWriteLock` in the unlocked state.
58
60
  def initialize
59
61
  super()
60
- @Counter = AtomicFixnum.new(0) # single integer which represents lock state
62
+ @Counter = AtomicFixnum.new(0) # single integer which represents lock state
63
+ @Writer = AtomicReference.new(nil) # the thread currently holding the write lock
61
64
  @ReadLock = Synchronization::Lock.new
62
65
  @WriteLock = Synchronization::Lock.new
63
66
  end
@@ -136,9 +139,13 @@ module Concurrent
136
139
  # Release a previously acquired read lock.
137
140
  #
138
141
  # @return [Boolean] true if the lock is successfully released
142
+ #
143
+ # @raise [Concurrent::IllegalOperationError] if no read lock is currently held.
139
144
  def release_read_lock
140
145
  while true
141
146
  c = @Counter.value
147
+ raise IllegalOperationError, 'Cannot release a read lock which is not held' if running_readers(c) == 0
148
+
142
149
  if @Counter.compare_and_set(c, c-1)
143
150
  # If one or more writers were waiting, and we were the last reader, wake a writer up
144
151
  if waiting_writer?(c) && running_readers(c) == 1
@@ -186,14 +193,21 @@ module Concurrent
186
193
  break
187
194
  end
188
195
  end
196
+ @Writer.set(Thread.current)
189
197
  true
190
198
  end
191
199
 
192
200
  # Release a previously acquired write lock.
193
201
  #
194
202
  # @return [Boolean] true if the lock is successfully released
203
+ #
204
+ # @raise [Concurrent::IllegalOperationError] if the write lock is not held
205
+ # by the current thread.
195
206
  def release_write_lock
196
- return true unless running_writer?
207
+ unless @Writer.compare_and_set(Thread.current, nil)
208
+ raise IllegalOperationError, 'Cannot release a write lock which is not held by the current thread'
209
+ end
210
+
197
211
  c = @Counter.update { |counter| counter - RUNNING_WRITER }
198
212
  @ReadLock.broadcast
199
213
  @WriteLock.signal if waiting_writers(c) > 0
@@ -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.
@@ -156,9 +158,11 @@ module Concurrent
156
158
  # @return [Boolean] true if the lock is successfully acquired
157
159
  #
158
160
  # @raise [Concurrent::ResourceLimitError] if the maximum number of readers
159
- # is exceeded.
161
+ # or per-thread reentrant acquires is exceeded.
160
162
  def acquire_read_lock
161
163
  if (held = @HeldCount.value) > 0
164
+ raise ResourceLimitError.new('Too many reader holds on this thread') if (held & READ_LOCK_MASK) == READ_LOCK_MASK
165
+
162
166
  # If we already have a lock, there's no need to wait
163
167
  if held & READ_LOCK_MASK == 0
164
168
  # But we do need to update the counter, if we were holding a write
@@ -210,8 +214,13 @@ module Concurrent
210
214
  # acquired immediately, return false.
211
215
  #
212
216
  # @return [Boolean] true if the lock is successfully acquired
217
+ #
218
+ # @raise [Concurrent::ResourceLimitError] if the maximum number of per-thread
219
+ # reentrant acquires is exceeded.
213
220
  def try_read_lock
214
221
  if (held = @HeldCount.value) > 0
222
+ raise ResourceLimitError.new('Too many reader holds on this thread') if (held & READ_LOCK_MASK) == READ_LOCK_MASK
223
+
215
224
  if held & READ_LOCK_MASK == 0
216
225
  # If we hold a write lock, but not a read lock...
217
226
  @Counter.update { |c| c + 1 }
@@ -1,5 +1,4 @@
1
1
  require 'concurrent/atomic/mutex_semaphore'
2
- require 'concurrent/synchronization'
3
2
 
4
3
  module Concurrent
5
4
 
@@ -11,7 +10,7 @@ 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
  #
@@ -21,8 +20,7 @@ module Concurrent
21
20
  #
22
21
  # @param [Fixnum] permits Number of permits to acquire
23
22
  #
24
- # @raise [ArgumentError] if `permits` is not an integer or is less than
25
- # one
23
+ # @raise [ArgumentError] if `permits` is not an integer or is less than zero
26
24
  #
27
25
  # @return [nil, BasicObject] Without a block, `nil` is returned. If a block
28
26
  # is given, its return value is returned.
@@ -52,8 +50,7 @@ module Concurrent
52
50
  # @param [Fixnum] timeout the number of seconds to wait for the counter
53
51
  # or `nil` to return immediately
54
52
  #
55
- # @raise [ArgumentError] if `permits` is not an integer or is less than
56
- # one
53
+ # @raise [ArgumentError] if `permits` is not an integer or is less than zero
57
54
  #
58
55
  # @return [true, false, nil, BasicObject] `false` if no permits are
59
56
  # available, `true` when acquired a permit. If a block is given, the
@@ -66,7 +63,7 @@ module Concurrent
66
63
  #
67
64
  # @param [Fixnum] permits Number of permits to return to the semaphore.
68
65
  #
69
- # @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
70
67
  #
71
68
  # @return [nil]
72
69
 
@@ -96,8 +93,8 @@ module Concurrent
96
93
 
97
94
  # @!visibility private
98
95
  # @!macro internal_implementation_note
99
- SemaphoreImplementation = case
100
- when defined?(JavaSemaphore)
96
+ SemaphoreImplementation = if Concurrent.on_jruby?
97
+ require 'concurrent/utility/native_extension_loader'
101
98
  JavaSemaphore
102
99
  else
103
100
  MutexSemaphore
@@ -1,104 +1,111 @@
1
- require 'concurrent/utility/engine'
2
- require 'concurrent/atomic/ruby_thread_local_var'
3
- require 'concurrent/atomic/java_thread_local_var'
1
+ require 'concurrent/constants'
2
+ require_relative 'locals'
4
3
 
5
4
  module Concurrent
6
5
 
7
- ###################################################################
8
-
9
- # @!macro thread_local_var_method_initialize
6
+ # A `ThreadLocalVar` is a variable where the value is different for each thread.
7
+ # Each variable may have a default value, but when you modify the variable only
8
+ # the current thread will ever see that change.
9
+ #
10
+ # This is similar to Ruby's built-in thread-local variables (`Thread#thread_variable_get`),
11
+ # but with these major advantages:
12
+ # * `ThreadLocalVar` has its own identity, it doesn't need a Symbol.
13
+ # * Each Ruby's built-in thread-local variable leaks some memory forever (it's a Symbol held forever on the thread),
14
+ # so it's only OK to create a small amount of them.
15
+ # `ThreadLocalVar` has no such issue and it is fine to create many of them.
16
+ # * Ruby's built-in thread-local variables leak forever the value set on each thread (unless set to nil explicitly).
17
+ # `ThreadLocalVar` automatically removes the mapping for each thread once the `ThreadLocalVar` instance is GC'd.
18
+ #
19
+ # @!macro thread_safe_variable_comparison
20
+ #
21
+ # @example
22
+ # v = ThreadLocalVar.new(14)
23
+ # v.value #=> 14
24
+ # v.value = 2
25
+ # v.value #=> 2
26
+ #
27
+ # @example
28
+ # v = ThreadLocalVar.new(14)
29
+ #
30
+ # t1 = Thread.new do
31
+ # v.value #=> 14
32
+ # v.value = 1
33
+ # v.value #=> 1
34
+ # end
10
35
  #
11
- # Creates a thread local variable.
36
+ # t2 = Thread.new do
37
+ # v.value #=> 14
38
+ # v.value = 2
39
+ # v.value #=> 2
40
+ # end
12
41
  #
13
- # @param [Object] default the default value when otherwise unset
14
- # @param [Proc] default_block Optional block that gets called to obtain the
15
- # default value for each thread
42
+ # v.value #=> 14
43
+ class ThreadLocalVar
44
+ LOCALS = ThreadLocals.new
16
45
 
17
- # @!macro thread_local_var_method_get
18
- #
19
- # Returns the value in the current thread's copy of this thread-local variable.
20
- #
21
- # @return [Object] the current value
46
+ # Creates a thread local variable.
47
+ #
48
+ # @param [Object] default the default value when otherwise unset
49
+ # @param [Proc] default_block Optional block that gets called to obtain the
50
+ # default value for each thread
51
+ def initialize(default = nil, &default_block)
52
+ if default && block_given?
53
+ raise ArgumentError, "Cannot use both value and block as default value"
54
+ end
22
55
 
23
- # @!macro thread_local_var_method_set
24
- #
25
- # Sets the current thread's copy of this thread-local variable to the specified value.
26
- #
27
- # @param [Object] value the value to set
28
- # @return [Object] the new value
56
+ if block_given?
57
+ @default_block = default_block
58
+ @default = nil
59
+ else
60
+ @default_block = nil
61
+ @default = default
62
+ end
29
63
 
30
- # @!macro thread_local_var_method_bind
31
- #
32
- # Bind the given value to thread local storage during
33
- # execution of the given block.
34
- #
35
- # @param [Object] value the value to bind
36
- # @yield the operation to be performed with the bound variable
37
- # @return [Object] the value
64
+ @index = LOCALS.next_index(self)
65
+ end
38
66
 
67
+ # Returns the value in the current thread's copy of this thread-local variable.
68
+ #
69
+ # @return [Object] the current value
70
+ def value
71
+ LOCALS.fetch(@index) { default }
72
+ end
39
73
 
40
- ###################################################################
74
+ # Sets the current thread's copy of this thread-local variable to the specified value.
75
+ #
76
+ # @param [Object] value the value to set
77
+ # @return [Object] the new value
78
+ def value=(value)
79
+ LOCALS.set(@index, value)
80
+ end
41
81
 
42
- # @!macro thread_local_var_public_api
43
- #
44
- # @!method initialize(default = nil, &default_block)
45
- # @!macro thread_local_var_method_initialize
46
- #
47
- # @!method value
48
- # @!macro thread_local_var_method_get
49
- #
50
- # @!method value=(value)
51
- # @!macro thread_local_var_method_set
52
- #
53
- # @!method bind(value, &block)
54
- # @!macro thread_local_var_method_bind
82
+ # Bind the given value to thread local storage during
83
+ # execution of the given block.
84
+ #
85
+ # @param [Object] value the value to bind
86
+ # @yield the operation to be performed with the bound variable
87
+ # @return [Object] the value
88
+ def bind(value)
89
+ if block_given?
90
+ old_value = self.value
91
+ self.value = value
92
+ begin
93
+ yield
94
+ ensure
95
+ self.value = old_value
96
+ end
97
+ end
98
+ end
55
99
 
56
- ###################################################################
100
+ protected
57
101
 
58
- # @!visibility private
59
- # @!macro internal_implementation_note
60
- ThreadLocalVarImplementation = case
61
- when Concurrent.on_jruby?
62
- JavaThreadLocalVar
63
- else
64
- RubyThreadLocalVar
65
- end
66
- private_constant :ThreadLocalVarImplementation
67
-
68
- # @!macro thread_local_var
69
- #
70
- # A `ThreadLocalVar` is a variable where the value is different for each thread.
71
- # Each variable may have a default value, but when you modify the variable only
72
- # the current thread will ever see that change.
73
- #
74
- # @!macro thread_safe_variable_comparison
75
- #
76
- # @example
77
- # v = ThreadLocalVar.new(14)
78
- # v.value #=> 14
79
- # v.value = 2
80
- # v.value #=> 2
81
- #
82
- # @example
83
- # v = ThreadLocalVar.new(14)
84
- #
85
- # t1 = Thread.new do
86
- # v.value #=> 14
87
- # v.value = 1
88
- # v.value #=> 1
89
- # end
90
- #
91
- # t2 = Thread.new do
92
- # v.value #=> 14
93
- # v.value = 2
94
- # v.value #=> 2
95
- # end
96
- #
97
- # v.value #=> 14
98
- #
99
- # @see https://docs.oracle.com/javase/7/docs/api/java/lang/ThreadLocal.html Java ThreadLocal
100
- #
101
- # @!macro thread_local_var_public_api
102
- class ThreadLocalVar < ThreadLocalVarImplementation
102
+ # @!visibility private
103
+ def default
104
+ if @default_block
105
+ self.value = @default_block.call
106
+ else
107
+ @default
108
+ end
109
+ end
103
110
  end
104
111
  end