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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +76 -3
- data/Gemfile +6 -7
- data/README.md +28 -23
- data/Rakefile +90 -76
- data/ext/concurrent-ruby/com/concurrent_ruby/ext/AtomicReferenceLibrary.java +3 -38
- data/ext/concurrent-ruby/com/concurrent_ruby/ext/SynchronizationLibrary.java +10 -25
- data/ext/concurrent-ruby/com/concurrent_ruby/ext/jsr166e/ConcurrentHashMapV8.java +1 -1
- data/ext/concurrent-ruby/com/concurrent_ruby/ext/jsr166e/nounsafe/ConcurrentHashMapV8.java +1 -1
- data/lib/concurrent-ruby/concurrent/agent.rb +4 -3
- data/lib/concurrent-ruby/concurrent/array.rb +3 -13
- data/lib/concurrent-ruby/concurrent/async.rb +1 -1
- data/lib/concurrent-ruby/concurrent/atom.rb +2 -2
- data/lib/concurrent-ruby/concurrent/atomic/atomic_boolean.rb +5 -4
- data/lib/concurrent-ruby/concurrent/atomic/atomic_fixnum.rb +5 -4
- data/lib/concurrent-ruby/concurrent/atomic/atomic_markable_reference.rb +3 -0
- data/lib/concurrent-ruby/concurrent/atomic/atomic_reference.rb +90 -153
- data/lib/concurrent-ruby/concurrent/atomic/cyclic_barrier.rb +1 -1
- data/lib/concurrent-ruby/concurrent/atomic/event.rb +1 -1
- data/lib/concurrent-ruby/concurrent/atomic/fiber_local_var.rb +109 -0
- data/lib/concurrent-ruby/concurrent/atomic/java_count_down_latch.rb +1 -0
- data/lib/concurrent-ruby/concurrent/atomic/locals.rb +189 -0
- data/lib/concurrent-ruby/concurrent/atomic/lock_local_var.rb +29 -0
- data/lib/concurrent-ruby/concurrent/atomic/mutex_atomic_boolean.rb +11 -5
- data/lib/concurrent-ruby/concurrent/atomic/mutex_atomic_fixnum.rb +11 -5
- data/lib/concurrent-ruby/concurrent/atomic/mutex_count_down_latch.rb +1 -1
- data/lib/concurrent-ruby/concurrent/atomic/mutex_semaphore.rb +1 -1
- data/lib/concurrent-ruby/concurrent/atomic/read_write_lock.rb +17 -3
- data/lib/concurrent-ruby/concurrent/atomic/reentrant_read_write_lock.rb +13 -4
- data/lib/concurrent-ruby/concurrent/atomic/semaphore.rb +6 -9
- data/lib/concurrent-ruby/concurrent/atomic/thread_local_var.rb +96 -89
- data/lib/concurrent-ruby/concurrent/atomic_reference/atomic_direct_update.rb +37 -0
- data/lib/concurrent-ruby/concurrent/atomic_reference/mutex_atomic.rb +16 -6
- data/lib/concurrent-ruby/concurrent/atomic_reference/numeric_cas_wrapper.rb +9 -1
- data/lib/concurrent-ruby/concurrent/collection/copy_on_notify_observer_set.rb +1 -1
- data/lib/concurrent-ruby/concurrent/collection/copy_on_write_observer_set.rb +1 -1
- data/lib/concurrent-ruby/concurrent/collection/lock_free_stack.rb +2 -0
- data/lib/concurrent-ruby/concurrent/collection/map/mri_map_backend.rb +2 -2
- data/lib/concurrent-ruby/concurrent/collection/map/non_concurrent_map_backend.rb +16 -8
- data/lib/concurrent-ruby/concurrent/collection/map/synchronized_map_backend.rb +23 -20
- data/lib/concurrent-ruby/concurrent/collection/ruby_timeout_queue.rb +55 -0
- data/lib/concurrent-ruby/concurrent/collection/timeout_queue.rb +18 -0
- data/lib/concurrent-ruby/concurrent/concern/logging.rb +95 -6
- data/lib/concurrent-ruby/concurrent/concurrent_ruby.jar +0 -0
- data/lib/concurrent-ruby/concurrent/configuration.rb +4 -87
- data/lib/concurrent-ruby/concurrent/delay.rb +3 -3
- data/lib/concurrent-ruby/concurrent/errors.rb +5 -0
- data/lib/concurrent-ruby/concurrent/exchanger.rb +1 -0
- data/lib/concurrent-ruby/concurrent/executor/abstract_executor_service.rb +1 -1
- data/lib/concurrent-ruby/concurrent/executor/fixed_thread_pool.rb +6 -4
- data/lib/concurrent-ruby/concurrent/executor/java_executor_service.rb +7 -9
- data/lib/concurrent-ruby/concurrent/executor/java_thread_pool_executor.rb +7 -0
- data/lib/concurrent-ruby/concurrent/executor/ruby_single_thread_executor.rb +2 -0
- data/lib/concurrent-ruby/concurrent/executor/ruby_thread_pool_executor.rb +61 -31
- data/lib/concurrent-ruby/concurrent/executor/safe_task_executor.rb +1 -1
- data/lib/concurrent-ruby/concurrent/executor/serialized_execution.rb +1 -1
- data/lib/concurrent-ruby/concurrent/executor/simple_executor_service.rb +4 -1
- data/lib/concurrent-ruby/concurrent/executor/single_thread_executor.rb +1 -1
- data/lib/concurrent-ruby/concurrent/executor/timer_set.rb +10 -3
- data/lib/concurrent-ruby/concurrent/executors.rb +0 -1
- data/lib/concurrent-ruby/concurrent/hash.rb +5 -12
- data/lib/concurrent-ruby/concurrent/immutable_struct.rb +1 -1
- data/lib/concurrent-ruby/concurrent/ivar.rb +2 -1
- data/lib/concurrent-ruby/concurrent/map.rb +43 -39
- data/lib/concurrent-ruby/concurrent/maybe.rb +1 -1
- data/lib/concurrent-ruby/concurrent/mutable_struct.rb +1 -1
- data/lib/concurrent-ruby/concurrent/mvar.rb +5 -5
- data/lib/concurrent-ruby/concurrent/promise.rb +3 -3
- data/lib/concurrent-ruby/concurrent/promises.rb +40 -29
- data/lib/concurrent-ruby/concurrent/re_include.rb +2 -0
- data/lib/concurrent-ruby/concurrent/scheduled_task.rb +2 -2
- data/lib/concurrent-ruby/concurrent/set.rb +0 -10
- data/lib/concurrent-ruby/concurrent/settable_struct.rb +2 -2
- data/lib/concurrent-ruby/concurrent/synchronization/abstract_lockable_object.rb +5 -1
- data/lib/concurrent-ruby/concurrent/synchronization/abstract_object.rb +1 -3
- data/lib/concurrent-ruby/concurrent/synchronization/abstract_struct.rb +1 -1
- data/lib/concurrent-ruby/concurrent/synchronization/condition.rb +2 -0
- data/lib/concurrent-ruby/concurrent/synchronization/full_memory_barrier.rb +29 -0
- data/lib/concurrent-ruby/concurrent/synchronization/jruby_lockable_object.rb +3 -1
- data/lib/concurrent-ruby/concurrent/synchronization/lock.rb +2 -0
- data/lib/concurrent-ruby/concurrent/synchronization/lockable_object.rb +5 -2
- data/lib/concurrent-ruby/concurrent/synchronization/mutex_lockable_object.rb +6 -5
- data/lib/concurrent-ruby/concurrent/synchronization/object.rb +13 -45
- data/lib/concurrent-ruby/concurrent/synchronization/safe_initialization.rb +36 -0
- data/lib/concurrent-ruby/concurrent/synchronization/volatile.rb +77 -12
- data/lib/concurrent-ruby/concurrent/synchronization.rb +1 -18
- data/lib/concurrent-ruby/concurrent/thread_safe/synchronized_delegator.rb +36 -39
- data/lib/concurrent-ruby/concurrent/thread_safe/util/adder.rb +1 -1
- data/lib/concurrent-ruby/concurrent/thread_safe/util/data_structures.rb +1 -37
- data/lib/concurrent-ruby/concurrent/thread_safe/util/xor_shift_random.rb +1 -1
- data/lib/concurrent-ruby/concurrent/timer_task.rb +65 -10
- data/lib/concurrent-ruby/concurrent/tuple.rb +1 -5
- data/lib/concurrent-ruby/concurrent/tvar.rb +2 -1
- data/lib/concurrent-ruby/concurrent/utility/engine.rb +5 -16
- data/lib/concurrent-ruby/concurrent/utility/monotonic_time.rb +3 -74
- data/lib/concurrent-ruby/concurrent/utility/native_extension_loader.rb +8 -10
- data/lib/concurrent-ruby/concurrent/utility/native_integer.rb +1 -0
- data/lib/concurrent-ruby/concurrent/utility/processor_counter.rb +148 -58
- data/lib/concurrent-ruby/concurrent/version.rb +1 -1
- metadata +14 -19
- data/lib/concurrent-ruby/concurrent/atomic/abstract_thread_local_var.rb +0 -66
- data/lib/concurrent-ruby/concurrent/atomic/java_thread_local_var.rb +0 -37
- data/lib/concurrent-ruby/concurrent/atomic/ruby_thread_local_var.rb +0 -181
- data/lib/concurrent-ruby/concurrent/collection/map/atomic_reference_map_backend.rb +0 -927
- data/lib/concurrent-ruby/concurrent/synchronization/jruby_object.rb +0 -45
- data/lib/concurrent-ruby/concurrent/synchronization/mri_object.rb +0 -44
- data/lib/concurrent-ruby/concurrent/synchronization/rbx_lockable_object.rb +0 -71
- data/lib/concurrent-ruby/concurrent/synchronization/rbx_object.rb +0 -49
- data/lib/concurrent-ruby/concurrent/synchronization/truffleruby_object.rb +0 -47
- 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
|
|
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
|
-
|
|
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
|
|
50
|
-
@
|
|
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
|
|
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
|
-
|
|
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
|
|
64
|
-
|
|
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,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)
|
|
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
|
-
|
|
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/
|
|
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 =
|
|
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
|
|
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
|
|
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 =
|
|
100
|
-
|
|
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/
|
|
2
|
-
|
|
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
|
-
#
|
|
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
|
-
#
|
|
36
|
+
# t2 = Thread.new do
|
|
37
|
+
# v.value #=> 14
|
|
38
|
+
# v.value = 2
|
|
39
|
+
# v.value #=> 2
|
|
40
|
+
# end
|
|
12
41
|
#
|
|
13
|
-
#
|
|
14
|
-
|
|
15
|
-
|
|
42
|
+
# v.value #=> 14
|
|
43
|
+
class ThreadLocalVar
|
|
44
|
+
LOCALS = ThreadLocals.new
|
|
16
45
|
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
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
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
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
|
-
|
|
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
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
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
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
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
|