o-concurrent-ruby 1.1.11
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 +7 -0
- data/CHANGELOG.md +542 -0
- data/Gemfile +37 -0
- data/LICENSE.txt +21 -0
- data/README.md +404 -0
- data/Rakefile +307 -0
- data/ext/concurrent-ruby/ConcurrentRubyService.java +17 -0
- data/ext/concurrent-ruby/com/concurrent_ruby/ext/AtomicReferenceLibrary.java +175 -0
- data/ext/concurrent-ruby/com/concurrent_ruby/ext/JRubyMapBackendLibrary.java +248 -0
- data/ext/concurrent-ruby/com/concurrent_ruby/ext/JavaAtomicBooleanLibrary.java +93 -0
- data/ext/concurrent-ruby/com/concurrent_ruby/ext/JavaAtomicFixnumLibrary.java +113 -0
- data/ext/concurrent-ruby/com/concurrent_ruby/ext/JavaSemaphoreLibrary.java +189 -0
- data/ext/concurrent-ruby/com/concurrent_ruby/ext/SynchronizationLibrary.java +307 -0
- data/ext/concurrent-ruby/com/concurrent_ruby/ext/jsr166e/ConcurrentHashMap.java +31 -0
- data/ext/concurrent-ruby/com/concurrent_ruby/ext/jsr166e/ConcurrentHashMapV8.java +3863 -0
- data/ext/concurrent-ruby/com/concurrent_ruby/ext/jsr166e/LongAdder.java +203 -0
- data/ext/concurrent-ruby/com/concurrent_ruby/ext/jsr166e/Striped64.java +342 -0
- data/ext/concurrent-ruby/com/concurrent_ruby/ext/jsr166e/nounsafe/ConcurrentHashMapV8.java +3800 -0
- data/ext/concurrent-ruby/com/concurrent_ruby/ext/jsr166e/nounsafe/LongAdder.java +204 -0
- data/ext/concurrent-ruby/com/concurrent_ruby/ext/jsr166e/nounsafe/Striped64.java +291 -0
- data/ext/concurrent-ruby/com/concurrent_ruby/ext/jsr166y/ThreadLocalRandom.java +199 -0
- data/lib/concurrent-ruby/concurrent/agent.rb +587 -0
- data/lib/concurrent-ruby/concurrent/array.rb +66 -0
- data/lib/concurrent-ruby/concurrent/async.rb +449 -0
- data/lib/concurrent-ruby/concurrent/atom.rb +222 -0
- data/lib/concurrent-ruby/concurrent/atomic/abstract_thread_local_var.rb +66 -0
- data/lib/concurrent-ruby/concurrent/atomic/atomic_boolean.rb +126 -0
- data/lib/concurrent-ruby/concurrent/atomic/atomic_fixnum.rb +143 -0
- data/lib/concurrent-ruby/concurrent/atomic/atomic_markable_reference.rb +164 -0
- data/lib/concurrent-ruby/concurrent/atomic/atomic_reference.rb +205 -0
- data/lib/concurrent-ruby/concurrent/atomic/count_down_latch.rb +100 -0
- data/lib/concurrent-ruby/concurrent/atomic/cyclic_barrier.rb +128 -0
- data/lib/concurrent-ruby/concurrent/atomic/event.rb +109 -0
- data/lib/concurrent-ruby/concurrent/atomic/java_count_down_latch.rb +42 -0
- data/lib/concurrent-ruby/concurrent/atomic/java_thread_local_var.rb +37 -0
- data/lib/concurrent-ruby/concurrent/atomic/mutex_atomic_boolean.rb +62 -0
- data/lib/concurrent-ruby/concurrent/atomic/mutex_atomic_fixnum.rb +75 -0
- data/lib/concurrent-ruby/concurrent/atomic/mutex_count_down_latch.rb +44 -0
- data/lib/concurrent-ruby/concurrent/atomic/mutex_semaphore.rb +131 -0
- data/lib/concurrent-ruby/concurrent/atomic/read_write_lock.rb +254 -0
- data/lib/concurrent-ruby/concurrent/atomic/reentrant_read_write_lock.rb +377 -0
- data/lib/concurrent-ruby/concurrent/atomic/ruby_thread_local_var.rb +181 -0
- data/lib/concurrent-ruby/concurrent/atomic/semaphore.rb +166 -0
- data/lib/concurrent-ruby/concurrent/atomic/thread_local_var.rb +104 -0
- data/lib/concurrent-ruby/concurrent/atomic_reference/mutex_atomic.rb +56 -0
- data/lib/concurrent-ruby/concurrent/atomic_reference/numeric_cas_wrapper.rb +28 -0
- data/lib/concurrent-ruby/concurrent/atomics.rb +10 -0
- data/lib/concurrent-ruby/concurrent/collection/copy_on_notify_observer_set.rb +107 -0
- data/lib/concurrent-ruby/concurrent/collection/copy_on_write_observer_set.rb +111 -0
- data/lib/concurrent-ruby/concurrent/collection/java_non_concurrent_priority_queue.rb +84 -0
- data/lib/concurrent-ruby/concurrent/collection/lock_free_stack.rb +158 -0
- data/lib/concurrent-ruby/concurrent/collection/map/atomic_reference_map_backend.rb +927 -0
- data/lib/concurrent-ruby/concurrent/collection/map/mri_map_backend.rb +66 -0
- data/lib/concurrent-ruby/concurrent/collection/map/non_concurrent_map_backend.rb +140 -0
- data/lib/concurrent-ruby/concurrent/collection/map/synchronized_map_backend.rb +82 -0
- data/lib/concurrent-ruby/concurrent/collection/map/truffleruby_map_backend.rb +14 -0
- data/lib/concurrent-ruby/concurrent/collection/non_concurrent_priority_queue.rb +143 -0
- data/lib/concurrent-ruby/concurrent/collection/ruby_non_concurrent_priority_queue.rb +160 -0
- data/lib/concurrent-ruby/concurrent/concern/deprecation.rb +34 -0
- data/lib/concurrent-ruby/concurrent/concern/dereferenceable.rb +73 -0
- data/lib/concurrent-ruby/concurrent/concern/logging.rb +32 -0
- data/lib/concurrent-ruby/concurrent/concern/obligation.rb +220 -0
- data/lib/concurrent-ruby/concurrent/concern/observable.rb +110 -0
- data/lib/concurrent-ruby/concurrent/configuration.rb +188 -0
- data/lib/concurrent-ruby/concurrent/constants.rb +8 -0
- data/lib/concurrent-ruby/concurrent/dataflow.rb +81 -0
- data/lib/concurrent-ruby/concurrent/delay.rb +199 -0
- data/lib/concurrent-ruby/concurrent/errors.rb +69 -0
- data/lib/concurrent-ruby/concurrent/exchanger.rb +352 -0
- data/lib/concurrent-ruby/concurrent/executor/abstract_executor_service.rb +131 -0
- data/lib/concurrent-ruby/concurrent/executor/cached_thread_pool.rb +62 -0
- data/lib/concurrent-ruby/concurrent/executor/executor_service.rb +185 -0
- data/lib/concurrent-ruby/concurrent/executor/fixed_thread_pool.rb +220 -0
- data/lib/concurrent-ruby/concurrent/executor/immediate_executor.rb +66 -0
- data/lib/concurrent-ruby/concurrent/executor/indirect_immediate_executor.rb +44 -0
- data/lib/concurrent-ruby/concurrent/executor/java_executor_service.rb +103 -0
- data/lib/concurrent-ruby/concurrent/executor/java_single_thread_executor.rb +30 -0
- data/lib/concurrent-ruby/concurrent/executor/java_thread_pool_executor.rb +140 -0
- data/lib/concurrent-ruby/concurrent/executor/ruby_executor_service.rb +82 -0
- data/lib/concurrent-ruby/concurrent/executor/ruby_single_thread_executor.rb +21 -0
- data/lib/concurrent-ruby/concurrent/executor/ruby_thread_pool_executor.rb +368 -0
- data/lib/concurrent-ruby/concurrent/executor/safe_task_executor.rb +35 -0
- data/lib/concurrent-ruby/concurrent/executor/serial_executor_service.rb +34 -0
- data/lib/concurrent-ruby/concurrent/executor/serialized_execution.rb +107 -0
- data/lib/concurrent-ruby/concurrent/executor/serialized_execution_delegator.rb +28 -0
- data/lib/concurrent-ruby/concurrent/executor/simple_executor_service.rb +100 -0
- data/lib/concurrent-ruby/concurrent/executor/single_thread_executor.rb +57 -0
- data/lib/concurrent-ruby/concurrent/executor/thread_pool_executor.rb +88 -0
- data/lib/concurrent-ruby/concurrent/executor/timer_set.rb +172 -0
- data/lib/concurrent-ruby/concurrent/executors.rb +20 -0
- data/lib/concurrent-ruby/concurrent/future.rb +141 -0
- data/lib/concurrent-ruby/concurrent/hash.rb +59 -0
- data/lib/concurrent-ruby/concurrent/immutable_struct.rb +101 -0
- data/lib/concurrent-ruby/concurrent/ivar.rb +207 -0
- data/lib/concurrent-ruby/concurrent/map.rb +346 -0
- data/lib/concurrent-ruby/concurrent/maybe.rb +229 -0
- data/lib/concurrent-ruby/concurrent/mutable_struct.rb +239 -0
- data/lib/concurrent-ruby/concurrent/mvar.rb +242 -0
- data/lib/concurrent-ruby/concurrent/options.rb +42 -0
- data/lib/concurrent-ruby/concurrent/promise.rb +580 -0
- data/lib/concurrent-ruby/concurrent/promises.rb +2167 -0
- data/lib/concurrent-ruby/concurrent/re_include.rb +58 -0
- data/lib/concurrent-ruby/concurrent/scheduled_task.rb +331 -0
- data/lib/concurrent-ruby/concurrent/set.rb +74 -0
- data/lib/concurrent-ruby/concurrent/settable_struct.rb +139 -0
- data/lib/concurrent-ruby/concurrent/synchronization/abstract_lockable_object.rb +98 -0
- data/lib/concurrent-ruby/concurrent/synchronization/abstract_object.rb +24 -0
- data/lib/concurrent-ruby/concurrent/synchronization/abstract_struct.rb +171 -0
- data/lib/concurrent-ruby/concurrent/synchronization/condition.rb +60 -0
- data/lib/concurrent-ruby/concurrent/synchronization/jruby_lockable_object.rb +13 -0
- data/lib/concurrent-ruby/concurrent/synchronization/jruby_object.rb +45 -0
- data/lib/concurrent-ruby/concurrent/synchronization/lock.rb +36 -0
- data/lib/concurrent-ruby/concurrent/synchronization/lockable_object.rb +72 -0
- data/lib/concurrent-ruby/concurrent/synchronization/mri_object.rb +44 -0
- data/lib/concurrent-ruby/concurrent/synchronization/mutex_lockable_object.rb +88 -0
- data/lib/concurrent-ruby/concurrent/synchronization/object.rb +183 -0
- data/lib/concurrent-ruby/concurrent/synchronization/rbx_lockable_object.rb +71 -0
- data/lib/concurrent-ruby/concurrent/synchronization/rbx_object.rb +49 -0
- data/lib/concurrent-ruby/concurrent/synchronization/truffleruby_object.rb +47 -0
- data/lib/concurrent-ruby/concurrent/synchronization/volatile.rb +36 -0
- data/lib/concurrent-ruby/concurrent/synchronization.rb +30 -0
- data/lib/concurrent-ruby/concurrent/thread_safe/synchronized_delegator.rb +50 -0
- data/lib/concurrent-ruby/concurrent/thread_safe/util/adder.rb +74 -0
- data/lib/concurrent-ruby/concurrent/thread_safe/util/cheap_lockable.rb +118 -0
- data/lib/concurrent-ruby/concurrent/thread_safe/util/data_structures.rb +88 -0
- data/lib/concurrent-ruby/concurrent/thread_safe/util/power_of_two_tuple.rb +38 -0
- data/lib/concurrent-ruby/concurrent/thread_safe/util/striped64.rb +246 -0
- data/lib/concurrent-ruby/concurrent/thread_safe/util/volatile.rb +75 -0
- data/lib/concurrent-ruby/concurrent/thread_safe/util/xor_shift_random.rb +50 -0
- data/lib/concurrent-ruby/concurrent/thread_safe/util.rb +16 -0
- data/lib/concurrent-ruby/concurrent/timer_task.rb +311 -0
- data/lib/concurrent-ruby/concurrent/tuple.rb +86 -0
- data/lib/concurrent-ruby/concurrent/tvar.rb +221 -0
- data/lib/concurrent-ruby/concurrent/utility/engine.rb +56 -0
- data/lib/concurrent-ruby/concurrent/utility/monotonic_time.rb +90 -0
- data/lib/concurrent-ruby/concurrent/utility/native_extension_loader.rb +79 -0
- data/lib/concurrent-ruby/concurrent/utility/native_integer.rb +53 -0
- data/lib/concurrent-ruby/concurrent/utility/processor_counter.rb +130 -0
- data/lib/concurrent-ruby/concurrent/version.rb +3 -0
- data/lib/concurrent-ruby/concurrent-ruby.rb +5 -0
- data/lib/concurrent-ruby/concurrent.rb +134 -0
- metadata +192 -0
@@ -0,0 +1,207 @@
|
|
1
|
+
require 'concurrent/constants'
|
2
|
+
require 'concurrent/errors'
|
3
|
+
require 'concurrent/collection/copy_on_write_observer_set'
|
4
|
+
require 'concurrent/concern/obligation'
|
5
|
+
require 'concurrent/concern/observable'
|
6
|
+
require 'concurrent/synchronization'
|
7
|
+
|
8
|
+
module Concurrent
|
9
|
+
|
10
|
+
# An `IVar` is like a future that you can assign. As a future is a value that
|
11
|
+
# is being computed that you can wait on, an `IVar` is a value that is waiting
|
12
|
+
# to be assigned, that you can wait on. `IVars` are single assignment and
|
13
|
+
# deterministic.
|
14
|
+
#
|
15
|
+
# Then, express futures as an asynchronous computation that assigns an `IVar`.
|
16
|
+
# The `IVar` becomes the primitive on which [futures](Future) and
|
17
|
+
# [dataflow](Dataflow) are built.
|
18
|
+
#
|
19
|
+
# An `IVar` is a single-element container that is normally created empty, and
|
20
|
+
# can only be set once. The I in `IVar` stands for immutable. Reading an
|
21
|
+
# `IVar` normally blocks until it is set. It is safe to set and read an `IVar`
|
22
|
+
# from different threads.
|
23
|
+
#
|
24
|
+
# If you want to have some parallel task set the value in an `IVar`, you want
|
25
|
+
# a `Future`. If you want to create a graph of parallel tasks all executed
|
26
|
+
# when the values they depend on are ready you want `dataflow`. `IVar` is
|
27
|
+
# generally a low-level primitive.
|
28
|
+
#
|
29
|
+
# ## Examples
|
30
|
+
#
|
31
|
+
# Create, set and get an `IVar`
|
32
|
+
#
|
33
|
+
# ```ruby
|
34
|
+
# ivar = Concurrent::IVar.new
|
35
|
+
# ivar.set 14
|
36
|
+
# ivar.value #=> 14
|
37
|
+
# ivar.set 2 # would now be an error
|
38
|
+
# ```
|
39
|
+
#
|
40
|
+
# ## See Also
|
41
|
+
#
|
42
|
+
# 1. For the theory: Arvind, R. Nikhil, and K. Pingali.
|
43
|
+
# [I-Structures: Data structures for parallel computing](http://dl.acm.org/citation.cfm?id=69562).
|
44
|
+
# In Proceedings of Workshop on Graph Reduction, 1986.
|
45
|
+
# 2. For recent application:
|
46
|
+
# [DataDrivenFuture in Habanero Java from Rice](http://www.cs.rice.edu/~vs3/hjlib/doc/edu/rice/hj/api/HjDataDrivenFuture.html).
|
47
|
+
class IVar < Synchronization::LockableObject
|
48
|
+
include Concern::Obligation
|
49
|
+
include Concern::Observable
|
50
|
+
|
51
|
+
# Create a new `IVar` in the `:pending` state with the (optional) initial value.
|
52
|
+
#
|
53
|
+
# @param [Object] value the initial value
|
54
|
+
# @param [Hash] opts the options to create a message with
|
55
|
+
# @option opts [String] :dup_on_deref (false) call `#dup` before returning
|
56
|
+
# the data
|
57
|
+
# @option opts [String] :freeze_on_deref (false) call `#freeze` before
|
58
|
+
# returning the data
|
59
|
+
# @option opts [String] :copy_on_deref (nil) call the given `Proc` passing
|
60
|
+
# the internal value and returning the value returned from the proc
|
61
|
+
def initialize(value = NULL, opts = {}, &block)
|
62
|
+
if value != NULL && block_given?
|
63
|
+
raise ArgumentError.new('provide only a value or a block')
|
64
|
+
end
|
65
|
+
super(&nil)
|
66
|
+
synchronize { ns_initialize(value, opts, &block) }
|
67
|
+
end
|
68
|
+
|
69
|
+
# Add an observer on this object that will receive notification on update.
|
70
|
+
#
|
71
|
+
# Upon completion the `IVar` will notify all observers in a thread-safe way.
|
72
|
+
# The `func` method of the observer will be called with three arguments: the
|
73
|
+
# `Time` at which the `Future` completed the asynchronous operation, the
|
74
|
+
# final `value` (or `nil` on rejection), and the final `reason` (or `nil` on
|
75
|
+
# fulfillment).
|
76
|
+
#
|
77
|
+
# @param [Object] observer the object that will be notified of changes
|
78
|
+
# @param [Symbol] func symbol naming the method to call when this
|
79
|
+
# `Observable` has changes`
|
80
|
+
def add_observer(observer = nil, func = :update, &block)
|
81
|
+
raise ArgumentError.new('cannot provide both an observer and a block') if observer && block
|
82
|
+
direct_notification = false
|
83
|
+
|
84
|
+
if block
|
85
|
+
observer = block
|
86
|
+
func = :call
|
87
|
+
end
|
88
|
+
|
89
|
+
synchronize do
|
90
|
+
if event.set?
|
91
|
+
direct_notification = true
|
92
|
+
else
|
93
|
+
observers.add_observer(observer, func)
|
94
|
+
end
|
95
|
+
end
|
96
|
+
|
97
|
+
observer.send(func, Time.now, self.value, reason) if direct_notification
|
98
|
+
observer
|
99
|
+
end
|
100
|
+
|
101
|
+
# @!macro ivar_set_method
|
102
|
+
# Set the `IVar` to a value and wake or notify all threads waiting on it.
|
103
|
+
#
|
104
|
+
# @!macro ivar_set_parameters_and_exceptions
|
105
|
+
# @param [Object] value the value to store in the `IVar`
|
106
|
+
# @yield A block operation to use for setting the value
|
107
|
+
# @raise [ArgumentError] if both a value and a block are given
|
108
|
+
# @raise [Concurrent::MultipleAssignmentError] if the `IVar` has already
|
109
|
+
# been set or otherwise completed
|
110
|
+
#
|
111
|
+
# @return [IVar] self
|
112
|
+
def set(value = NULL)
|
113
|
+
check_for_block_or_value!(block_given?, value)
|
114
|
+
raise MultipleAssignmentError unless compare_and_set_state(:processing, :pending)
|
115
|
+
|
116
|
+
begin
|
117
|
+
value = yield if block_given?
|
118
|
+
complete_without_notification(true, value, nil)
|
119
|
+
rescue => ex
|
120
|
+
complete_without_notification(false, nil, ex)
|
121
|
+
end
|
122
|
+
|
123
|
+
notify_observers(self.value, reason)
|
124
|
+
self
|
125
|
+
end
|
126
|
+
|
127
|
+
# @!macro ivar_fail_method
|
128
|
+
# Set the `IVar` to failed due to some error and wake or notify all threads waiting on it.
|
129
|
+
#
|
130
|
+
# @param [Object] reason for the failure
|
131
|
+
# @raise [Concurrent::MultipleAssignmentError] if the `IVar` has already
|
132
|
+
# been set or otherwise completed
|
133
|
+
# @return [IVar] self
|
134
|
+
def fail(reason = StandardError.new)
|
135
|
+
complete(false, nil, reason)
|
136
|
+
end
|
137
|
+
|
138
|
+
# Attempt to set the `IVar` with the given value or block. Return a
|
139
|
+
# boolean indicating the success or failure of the set operation.
|
140
|
+
#
|
141
|
+
# @!macro ivar_set_parameters_and_exceptions
|
142
|
+
#
|
143
|
+
# @return [Boolean] true if the value was set else false
|
144
|
+
def try_set(value = NULL, &block)
|
145
|
+
set(value, &block)
|
146
|
+
true
|
147
|
+
rescue MultipleAssignmentError
|
148
|
+
false
|
149
|
+
end
|
150
|
+
|
151
|
+
protected
|
152
|
+
|
153
|
+
# @!visibility private
|
154
|
+
def ns_initialize(value, opts)
|
155
|
+
value = yield if block_given?
|
156
|
+
init_obligation
|
157
|
+
self.observers = Collection::CopyOnWriteObserverSet.new
|
158
|
+
set_deref_options(opts)
|
159
|
+
|
160
|
+
@state = :pending
|
161
|
+
if value != NULL
|
162
|
+
ns_complete_without_notification(true, value, nil)
|
163
|
+
end
|
164
|
+
end
|
165
|
+
|
166
|
+
# @!visibility private
|
167
|
+
def safe_execute(task, args = [])
|
168
|
+
if compare_and_set_state(:processing, :pending)
|
169
|
+
success, val, reason = SafeTaskExecutor.new(task, rescue_exception: true).execute(*@args)
|
170
|
+
complete(success, val, reason)
|
171
|
+
yield(success, val, reason) if block_given?
|
172
|
+
end
|
173
|
+
end
|
174
|
+
|
175
|
+
# @!visibility private
|
176
|
+
def complete(success, value, reason)
|
177
|
+
complete_without_notification(success, value, reason)
|
178
|
+
notify_observers(self.value, reason)
|
179
|
+
self
|
180
|
+
end
|
181
|
+
|
182
|
+
# @!visibility private
|
183
|
+
def complete_without_notification(success, value, reason)
|
184
|
+
synchronize { ns_complete_without_notification(success, value, reason) }
|
185
|
+
self
|
186
|
+
end
|
187
|
+
|
188
|
+
# @!visibility private
|
189
|
+
def notify_observers(value, reason)
|
190
|
+
observers.notify_and_delete_observers{ [Time.now, value, reason] }
|
191
|
+
end
|
192
|
+
|
193
|
+
# @!visibility private
|
194
|
+
def ns_complete_without_notification(success, value, reason)
|
195
|
+
raise MultipleAssignmentError if [:fulfilled, :rejected].include? @state
|
196
|
+
set_state(success, value, reason)
|
197
|
+
event.set
|
198
|
+
end
|
199
|
+
|
200
|
+
# @!visibility private
|
201
|
+
def check_for_block_or_value!(block_given, value) # :nodoc:
|
202
|
+
if (block_given && value != NULL) || (! block_given && value == NULL)
|
203
|
+
raise ArgumentError.new('must set with either a value or a block')
|
204
|
+
end
|
205
|
+
end
|
206
|
+
end
|
207
|
+
end
|
@@ -0,0 +1,346 @@
|
|
1
|
+
require 'thread'
|
2
|
+
require 'concurrent/constants'
|
3
|
+
require 'concurrent/synchronization'
|
4
|
+
require 'concurrent/utility/engine'
|
5
|
+
|
6
|
+
module Concurrent
|
7
|
+
# @!visibility private
|
8
|
+
module Collection
|
9
|
+
|
10
|
+
# @!visibility private
|
11
|
+
MapImplementation = case
|
12
|
+
when Concurrent.on_jruby?
|
13
|
+
# noinspection RubyResolve
|
14
|
+
JRubyMapBackend
|
15
|
+
when Concurrent.on_cruby?
|
16
|
+
require 'concurrent/collection/map/mri_map_backend'
|
17
|
+
MriMapBackend
|
18
|
+
when Concurrent.on_truffleruby? && defined?(::TruffleRuby::ConcurrentMap)
|
19
|
+
require 'concurrent/collection/map/truffleruby_map_backend'
|
20
|
+
TruffleRubyMapBackend
|
21
|
+
when Concurrent.on_truffleruby? || Concurrent.on_rbx?
|
22
|
+
require 'concurrent/collection/map/atomic_reference_map_backend'
|
23
|
+
AtomicReferenceMapBackend
|
24
|
+
else
|
25
|
+
warn 'Concurrent::Map: unsupported Ruby engine, using a fully synchronized Concurrent::Map implementation'
|
26
|
+
require 'concurrent/collection/map/synchronized_map_backend'
|
27
|
+
SynchronizedMapBackend
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
# `Concurrent::Map` is a hash-like object and should have much better performance
|
32
|
+
# characteristics, especially under high concurrency, than `Concurrent::Hash`.
|
33
|
+
# However, `Concurrent::Map `is not strictly semantically equivalent to a ruby `Hash`
|
34
|
+
# -- for instance, it does not necessarily retain ordering by insertion time as `Hash`
|
35
|
+
# does. For most uses it should do fine though, and we recommend you consider
|
36
|
+
# `Concurrent::Map` instead of `Concurrent::Hash` for your concurrency-safe hash needs.
|
37
|
+
class Map < Collection::MapImplementation
|
38
|
+
|
39
|
+
# @!macro map.atomic_method
|
40
|
+
# This method is atomic.
|
41
|
+
|
42
|
+
# @!macro map.atomic_method_with_block
|
43
|
+
# This method is atomic.
|
44
|
+
# @note Atomic methods taking a block do not allow the `self` instance
|
45
|
+
# to be used within the block. Doing so will cause a deadlock.
|
46
|
+
|
47
|
+
# @!method compute_if_absent(key)
|
48
|
+
# Compute and store new value for key if the key is absent.
|
49
|
+
# @param [Object] key
|
50
|
+
# @yield new value
|
51
|
+
# @yieldreturn [Object] new value
|
52
|
+
# @return [Object] new value or current value
|
53
|
+
# @!macro map.atomic_method_with_block
|
54
|
+
|
55
|
+
# @!method compute_if_present(key)
|
56
|
+
# Compute and store new value for key if the key is present.
|
57
|
+
# @param [Object] key
|
58
|
+
# @yield new value
|
59
|
+
# @yieldparam old_value [Object]
|
60
|
+
# @yieldreturn [Object, nil] new value, when nil the key is removed
|
61
|
+
# @return [Object, nil] new value or nil
|
62
|
+
# @!macro map.atomic_method_with_block
|
63
|
+
|
64
|
+
# @!method compute(key)
|
65
|
+
# Compute and store new value for key.
|
66
|
+
# @param [Object] key
|
67
|
+
# @yield compute new value from old one
|
68
|
+
# @yieldparam old_value [Object, nil] old_value, or nil when key is absent
|
69
|
+
# @yieldreturn [Object, nil] new value, when nil the key is removed
|
70
|
+
# @return [Object, nil] new value or nil
|
71
|
+
# @!macro map.atomic_method_with_block
|
72
|
+
|
73
|
+
# @!method merge_pair(key, value)
|
74
|
+
# If the key is absent, the value is stored, otherwise new value is
|
75
|
+
# computed with a block.
|
76
|
+
# @param [Object] key
|
77
|
+
# @param [Object] value
|
78
|
+
# @yield compute new value from old one
|
79
|
+
# @yieldparam old_value [Object] old value
|
80
|
+
# @yieldreturn [Object, nil] new value, when nil the key is removed
|
81
|
+
# @return [Object, nil] new value or nil
|
82
|
+
# @!macro map.atomic_method_with_block
|
83
|
+
|
84
|
+
# @!method replace_pair(key, old_value, new_value)
|
85
|
+
# Replaces old_value with new_value if key exists and current value
|
86
|
+
# matches old_value
|
87
|
+
# @param [Object] key
|
88
|
+
# @param [Object] old_value
|
89
|
+
# @param [Object] new_value
|
90
|
+
# @return [true, false] true if replaced
|
91
|
+
# @!macro map.atomic_method
|
92
|
+
|
93
|
+
# @!method replace_if_exists(key, new_value)
|
94
|
+
# Replaces current value with new_value if key exists
|
95
|
+
# @param [Object] key
|
96
|
+
# @param [Object] new_value
|
97
|
+
# @return [Object, nil] old value or nil
|
98
|
+
# @!macro map.atomic_method
|
99
|
+
|
100
|
+
# @!method get_and_set(key, value)
|
101
|
+
# Get the current value under key and set new value.
|
102
|
+
# @param [Object] key
|
103
|
+
# @param [Object] value
|
104
|
+
# @return [Object, nil] old value or nil when the key was absent
|
105
|
+
# @!macro map.atomic_method
|
106
|
+
|
107
|
+
# @!method delete(key)
|
108
|
+
# Delete key and its value.
|
109
|
+
# @param [Object] key
|
110
|
+
# @return [Object, nil] old value or nil when the key was absent
|
111
|
+
# @!macro map.atomic_method
|
112
|
+
|
113
|
+
# @!method delete_pair(key, value)
|
114
|
+
# Delete pair and its value if current value equals the provided value.
|
115
|
+
# @param [Object] key
|
116
|
+
# @param [Object] value
|
117
|
+
# @return [true, false] true if deleted
|
118
|
+
# @!macro map.atomic_method
|
119
|
+
|
120
|
+
#
|
121
|
+
def initialize(options = nil, &block)
|
122
|
+
if options.kind_of?(::Hash)
|
123
|
+
validate_options_hash!(options)
|
124
|
+
else
|
125
|
+
options = nil
|
126
|
+
end
|
127
|
+
|
128
|
+
super(options)
|
129
|
+
@default_proc = block
|
130
|
+
end
|
131
|
+
|
132
|
+
# Get a value with key
|
133
|
+
# @param [Object] key
|
134
|
+
# @return [Object] the value
|
135
|
+
def [](key)
|
136
|
+
if value = super # non-falsy value is an existing mapping, return it right away
|
137
|
+
value
|
138
|
+
# re-check is done with get_or_default(key, NULL) instead of a simple !key?(key) in order to avoid a race condition, whereby by the time the current thread gets to the key?(key) call
|
139
|
+
# a key => value mapping might have already been created by a different thread (key?(key) would then return true, this elsif branch wouldn't be taken and an incorrent +nil+ value
|
140
|
+
# would be returned)
|
141
|
+
# note: nil == value check is not technically necessary
|
142
|
+
elsif @default_proc && nil == value && NULL == (value = get_or_default(key, NULL))
|
143
|
+
@default_proc.call(self, key)
|
144
|
+
else
|
145
|
+
value
|
146
|
+
end
|
147
|
+
end
|
148
|
+
|
149
|
+
# Set a value with key
|
150
|
+
# @param [Object] key
|
151
|
+
# @param [Object] value
|
152
|
+
# @return [Object] the new value
|
153
|
+
def []=(key, value)
|
154
|
+
super
|
155
|
+
end
|
156
|
+
|
157
|
+
alias_method :get, :[]
|
158
|
+
alias_method :put, :[]=
|
159
|
+
|
160
|
+
# Get a value with key, or default_value when key is absent,
|
161
|
+
# or fail when no default_value is given.
|
162
|
+
# @param [Object] key
|
163
|
+
# @param [Object] default_value
|
164
|
+
# @yield default value for a key
|
165
|
+
# @yieldparam key [Object]
|
166
|
+
# @yieldreturn [Object] default value
|
167
|
+
# @return [Object] the value or default value
|
168
|
+
# @raise [KeyError] when key is missing and no default_value is provided
|
169
|
+
# @!macro map_method_not_atomic
|
170
|
+
# @note The "fetch-then-act" methods of `Map` are not atomic. `Map` is intended
|
171
|
+
# to be use as a concurrency primitive with strong happens-before
|
172
|
+
# guarantees. It is not intended to be used as a high-level abstraction
|
173
|
+
# supporting complex operations. All read and write operations are
|
174
|
+
# thread safe, but no guarantees are made regarding race conditions
|
175
|
+
# between the fetch operation and yielding to the block. Additionally,
|
176
|
+
# this method does not support recursion. This is due to internal
|
177
|
+
# constraints that are very unlikely to change in the near future.
|
178
|
+
def fetch(key, default_value = NULL)
|
179
|
+
if NULL != (value = get_or_default(key, NULL))
|
180
|
+
value
|
181
|
+
elsif block_given?
|
182
|
+
yield key
|
183
|
+
elsif NULL != default_value
|
184
|
+
default_value
|
185
|
+
else
|
186
|
+
raise_fetch_no_key
|
187
|
+
end
|
188
|
+
end
|
189
|
+
|
190
|
+
# Fetch value with key, or store default value when key is absent,
|
191
|
+
# or fail when no default_value is given. This is a two step operation,
|
192
|
+
# therefore not atomic. The store can overwrite other concurrently
|
193
|
+
# stored value.
|
194
|
+
# @param [Object] key
|
195
|
+
# @param [Object] default_value
|
196
|
+
# @yield default value for a key
|
197
|
+
# @yieldparam key [Object]
|
198
|
+
# @yieldreturn [Object] default value
|
199
|
+
# @return [Object] the value or default value
|
200
|
+
# @!macro map.atomic_method_with_block
|
201
|
+
def fetch_or_store(key, default_value = NULL)
|
202
|
+
fetch(key) do
|
203
|
+
put(key, block_given? ? yield(key) : (NULL == default_value ? raise_fetch_no_key : default_value))
|
204
|
+
end
|
205
|
+
end
|
206
|
+
|
207
|
+
# Insert value into map with key if key is absent in one atomic step.
|
208
|
+
# @param [Object] key
|
209
|
+
# @param [Object] value
|
210
|
+
# @return [Object, nil] the previous value when key was present or nil when there was no key
|
211
|
+
def put_if_absent(key, value)
|
212
|
+
computed = false
|
213
|
+
result = compute_if_absent(key) do
|
214
|
+
computed = true
|
215
|
+
value
|
216
|
+
end
|
217
|
+
computed ? nil : result
|
218
|
+
end unless method_defined?(:put_if_absent)
|
219
|
+
|
220
|
+
# Is the value stored in the map. Iterates over all values.
|
221
|
+
# @param [Object] value
|
222
|
+
# @return [true, false]
|
223
|
+
def value?(value)
|
224
|
+
each_value do |v|
|
225
|
+
return true if value.equal?(v)
|
226
|
+
end
|
227
|
+
false
|
228
|
+
end
|
229
|
+
|
230
|
+
# All keys
|
231
|
+
# @return [::Array<Object>] keys
|
232
|
+
def keys
|
233
|
+
arr = []
|
234
|
+
each_pair { |k, v| arr << k }
|
235
|
+
arr
|
236
|
+
end unless method_defined?(:keys)
|
237
|
+
|
238
|
+
# All values
|
239
|
+
# @return [::Array<Object>] values
|
240
|
+
def values
|
241
|
+
arr = []
|
242
|
+
each_pair { |k, v| arr << v }
|
243
|
+
arr
|
244
|
+
end unless method_defined?(:values)
|
245
|
+
|
246
|
+
# Iterates over each key.
|
247
|
+
# @yield for each key in the map
|
248
|
+
# @yieldparam key [Object]
|
249
|
+
# @return [self]
|
250
|
+
# @!macro map.atomic_method_with_block
|
251
|
+
def each_key
|
252
|
+
each_pair { |k, v| yield k }
|
253
|
+
end unless method_defined?(:each_key)
|
254
|
+
|
255
|
+
# Iterates over each value.
|
256
|
+
# @yield for each value in the map
|
257
|
+
# @yieldparam value [Object]
|
258
|
+
# @return [self]
|
259
|
+
# @!macro map.atomic_method_with_block
|
260
|
+
def each_value
|
261
|
+
each_pair { |k, v| yield v }
|
262
|
+
end unless method_defined?(:each_value)
|
263
|
+
|
264
|
+
# Iterates over each key value pair.
|
265
|
+
# @yield for each key value pair in the map
|
266
|
+
# @yieldparam key [Object]
|
267
|
+
# @yieldparam value [Object]
|
268
|
+
# @return [self]
|
269
|
+
# @!macro map.atomic_method_with_block
|
270
|
+
def each_pair
|
271
|
+
return enum_for :each_pair unless block_given?
|
272
|
+
super
|
273
|
+
end
|
274
|
+
|
275
|
+
alias_method :each, :each_pair unless method_defined?(:each)
|
276
|
+
|
277
|
+
# Find key of a value.
|
278
|
+
# @param [Object] value
|
279
|
+
# @return [Object, nil] key or nil when not found
|
280
|
+
def key(value)
|
281
|
+
each_pair { |k, v| return k if v == value }
|
282
|
+
nil
|
283
|
+
end unless method_defined?(:key)
|
284
|
+
|
285
|
+
# Is map empty?
|
286
|
+
# @return [true, false]
|
287
|
+
def empty?
|
288
|
+
each_pair { |k, v| return false }
|
289
|
+
true
|
290
|
+
end unless method_defined?(:empty?)
|
291
|
+
|
292
|
+
# The size of map.
|
293
|
+
# @return [Integer] size
|
294
|
+
def size
|
295
|
+
count = 0
|
296
|
+
each_pair { |k, v| count += 1 }
|
297
|
+
count
|
298
|
+
end unless method_defined?(:size)
|
299
|
+
|
300
|
+
# @!visibility private
|
301
|
+
def marshal_dump
|
302
|
+
raise TypeError, "can't dump hash with default proc" if @default_proc
|
303
|
+
h = {}
|
304
|
+
each_pair { |k, v| h[k] = v }
|
305
|
+
h
|
306
|
+
end
|
307
|
+
|
308
|
+
# @!visibility private
|
309
|
+
def marshal_load(hash)
|
310
|
+
initialize
|
311
|
+
populate_from(hash)
|
312
|
+
end
|
313
|
+
|
314
|
+
undef :freeze
|
315
|
+
|
316
|
+
# @!visibility private
|
317
|
+
def inspect
|
318
|
+
format '%s entries=%d default_proc=%s>', to_s[0..-2], size.to_s, @default_proc.inspect
|
319
|
+
end
|
320
|
+
|
321
|
+
private
|
322
|
+
|
323
|
+
def raise_fetch_no_key
|
324
|
+
raise KeyError, 'key not found'
|
325
|
+
end
|
326
|
+
|
327
|
+
def initialize_copy(other)
|
328
|
+
super
|
329
|
+
populate_from(other)
|
330
|
+
end
|
331
|
+
|
332
|
+
def populate_from(hash)
|
333
|
+
hash.each_pair { |k, v| self[k] = v }
|
334
|
+
self
|
335
|
+
end
|
336
|
+
|
337
|
+
def validate_options_hash!(options)
|
338
|
+
if (initial_capacity = options[:initial_capacity]) && (!initial_capacity.kind_of?(Integer) || initial_capacity < 0)
|
339
|
+
raise ArgumentError, ":initial_capacity must be a positive Integer"
|
340
|
+
end
|
341
|
+
if (load_factor = options[:load_factor]) && (!load_factor.kind_of?(Numeric) || load_factor <= 0 || load_factor > 1)
|
342
|
+
raise ArgumentError, ":load_factor must be a number between 0 and 1"
|
343
|
+
end
|
344
|
+
end
|
345
|
+
end
|
346
|
+
end
|