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.
Files changed (142) hide show
  1. checksums.yaml +7 -0
  2. data/CHANGELOG.md +542 -0
  3. data/Gemfile +37 -0
  4. data/LICENSE.txt +21 -0
  5. data/README.md +404 -0
  6. data/Rakefile +307 -0
  7. data/ext/concurrent-ruby/ConcurrentRubyService.java +17 -0
  8. data/ext/concurrent-ruby/com/concurrent_ruby/ext/AtomicReferenceLibrary.java +175 -0
  9. data/ext/concurrent-ruby/com/concurrent_ruby/ext/JRubyMapBackendLibrary.java +248 -0
  10. data/ext/concurrent-ruby/com/concurrent_ruby/ext/JavaAtomicBooleanLibrary.java +93 -0
  11. data/ext/concurrent-ruby/com/concurrent_ruby/ext/JavaAtomicFixnumLibrary.java +113 -0
  12. data/ext/concurrent-ruby/com/concurrent_ruby/ext/JavaSemaphoreLibrary.java +189 -0
  13. data/ext/concurrent-ruby/com/concurrent_ruby/ext/SynchronizationLibrary.java +307 -0
  14. data/ext/concurrent-ruby/com/concurrent_ruby/ext/jsr166e/ConcurrentHashMap.java +31 -0
  15. data/ext/concurrent-ruby/com/concurrent_ruby/ext/jsr166e/ConcurrentHashMapV8.java +3863 -0
  16. data/ext/concurrent-ruby/com/concurrent_ruby/ext/jsr166e/LongAdder.java +203 -0
  17. data/ext/concurrent-ruby/com/concurrent_ruby/ext/jsr166e/Striped64.java +342 -0
  18. data/ext/concurrent-ruby/com/concurrent_ruby/ext/jsr166e/nounsafe/ConcurrentHashMapV8.java +3800 -0
  19. data/ext/concurrent-ruby/com/concurrent_ruby/ext/jsr166e/nounsafe/LongAdder.java +204 -0
  20. data/ext/concurrent-ruby/com/concurrent_ruby/ext/jsr166e/nounsafe/Striped64.java +291 -0
  21. data/ext/concurrent-ruby/com/concurrent_ruby/ext/jsr166y/ThreadLocalRandom.java +199 -0
  22. data/lib/concurrent-ruby/concurrent/agent.rb +587 -0
  23. data/lib/concurrent-ruby/concurrent/array.rb +66 -0
  24. data/lib/concurrent-ruby/concurrent/async.rb +449 -0
  25. data/lib/concurrent-ruby/concurrent/atom.rb +222 -0
  26. data/lib/concurrent-ruby/concurrent/atomic/abstract_thread_local_var.rb +66 -0
  27. data/lib/concurrent-ruby/concurrent/atomic/atomic_boolean.rb +126 -0
  28. data/lib/concurrent-ruby/concurrent/atomic/atomic_fixnum.rb +143 -0
  29. data/lib/concurrent-ruby/concurrent/atomic/atomic_markable_reference.rb +164 -0
  30. data/lib/concurrent-ruby/concurrent/atomic/atomic_reference.rb +205 -0
  31. data/lib/concurrent-ruby/concurrent/atomic/count_down_latch.rb +100 -0
  32. data/lib/concurrent-ruby/concurrent/atomic/cyclic_barrier.rb +128 -0
  33. data/lib/concurrent-ruby/concurrent/atomic/event.rb +109 -0
  34. data/lib/concurrent-ruby/concurrent/atomic/java_count_down_latch.rb +42 -0
  35. data/lib/concurrent-ruby/concurrent/atomic/java_thread_local_var.rb +37 -0
  36. data/lib/concurrent-ruby/concurrent/atomic/mutex_atomic_boolean.rb +62 -0
  37. data/lib/concurrent-ruby/concurrent/atomic/mutex_atomic_fixnum.rb +75 -0
  38. data/lib/concurrent-ruby/concurrent/atomic/mutex_count_down_latch.rb +44 -0
  39. data/lib/concurrent-ruby/concurrent/atomic/mutex_semaphore.rb +131 -0
  40. data/lib/concurrent-ruby/concurrent/atomic/read_write_lock.rb +254 -0
  41. data/lib/concurrent-ruby/concurrent/atomic/reentrant_read_write_lock.rb +377 -0
  42. data/lib/concurrent-ruby/concurrent/atomic/ruby_thread_local_var.rb +181 -0
  43. data/lib/concurrent-ruby/concurrent/atomic/semaphore.rb +166 -0
  44. data/lib/concurrent-ruby/concurrent/atomic/thread_local_var.rb +104 -0
  45. data/lib/concurrent-ruby/concurrent/atomic_reference/mutex_atomic.rb +56 -0
  46. data/lib/concurrent-ruby/concurrent/atomic_reference/numeric_cas_wrapper.rb +28 -0
  47. data/lib/concurrent-ruby/concurrent/atomics.rb +10 -0
  48. data/lib/concurrent-ruby/concurrent/collection/copy_on_notify_observer_set.rb +107 -0
  49. data/lib/concurrent-ruby/concurrent/collection/copy_on_write_observer_set.rb +111 -0
  50. data/lib/concurrent-ruby/concurrent/collection/java_non_concurrent_priority_queue.rb +84 -0
  51. data/lib/concurrent-ruby/concurrent/collection/lock_free_stack.rb +158 -0
  52. data/lib/concurrent-ruby/concurrent/collection/map/atomic_reference_map_backend.rb +927 -0
  53. data/lib/concurrent-ruby/concurrent/collection/map/mri_map_backend.rb +66 -0
  54. data/lib/concurrent-ruby/concurrent/collection/map/non_concurrent_map_backend.rb +140 -0
  55. data/lib/concurrent-ruby/concurrent/collection/map/synchronized_map_backend.rb +82 -0
  56. data/lib/concurrent-ruby/concurrent/collection/map/truffleruby_map_backend.rb +14 -0
  57. data/lib/concurrent-ruby/concurrent/collection/non_concurrent_priority_queue.rb +143 -0
  58. data/lib/concurrent-ruby/concurrent/collection/ruby_non_concurrent_priority_queue.rb +160 -0
  59. data/lib/concurrent-ruby/concurrent/concern/deprecation.rb +34 -0
  60. data/lib/concurrent-ruby/concurrent/concern/dereferenceable.rb +73 -0
  61. data/lib/concurrent-ruby/concurrent/concern/logging.rb +32 -0
  62. data/lib/concurrent-ruby/concurrent/concern/obligation.rb +220 -0
  63. data/lib/concurrent-ruby/concurrent/concern/observable.rb +110 -0
  64. data/lib/concurrent-ruby/concurrent/configuration.rb +188 -0
  65. data/lib/concurrent-ruby/concurrent/constants.rb +8 -0
  66. data/lib/concurrent-ruby/concurrent/dataflow.rb +81 -0
  67. data/lib/concurrent-ruby/concurrent/delay.rb +199 -0
  68. data/lib/concurrent-ruby/concurrent/errors.rb +69 -0
  69. data/lib/concurrent-ruby/concurrent/exchanger.rb +352 -0
  70. data/lib/concurrent-ruby/concurrent/executor/abstract_executor_service.rb +131 -0
  71. data/lib/concurrent-ruby/concurrent/executor/cached_thread_pool.rb +62 -0
  72. data/lib/concurrent-ruby/concurrent/executor/executor_service.rb +185 -0
  73. data/lib/concurrent-ruby/concurrent/executor/fixed_thread_pool.rb +220 -0
  74. data/lib/concurrent-ruby/concurrent/executor/immediate_executor.rb +66 -0
  75. data/lib/concurrent-ruby/concurrent/executor/indirect_immediate_executor.rb +44 -0
  76. data/lib/concurrent-ruby/concurrent/executor/java_executor_service.rb +103 -0
  77. data/lib/concurrent-ruby/concurrent/executor/java_single_thread_executor.rb +30 -0
  78. data/lib/concurrent-ruby/concurrent/executor/java_thread_pool_executor.rb +140 -0
  79. data/lib/concurrent-ruby/concurrent/executor/ruby_executor_service.rb +82 -0
  80. data/lib/concurrent-ruby/concurrent/executor/ruby_single_thread_executor.rb +21 -0
  81. data/lib/concurrent-ruby/concurrent/executor/ruby_thread_pool_executor.rb +368 -0
  82. data/lib/concurrent-ruby/concurrent/executor/safe_task_executor.rb +35 -0
  83. data/lib/concurrent-ruby/concurrent/executor/serial_executor_service.rb +34 -0
  84. data/lib/concurrent-ruby/concurrent/executor/serialized_execution.rb +107 -0
  85. data/lib/concurrent-ruby/concurrent/executor/serialized_execution_delegator.rb +28 -0
  86. data/lib/concurrent-ruby/concurrent/executor/simple_executor_service.rb +100 -0
  87. data/lib/concurrent-ruby/concurrent/executor/single_thread_executor.rb +57 -0
  88. data/lib/concurrent-ruby/concurrent/executor/thread_pool_executor.rb +88 -0
  89. data/lib/concurrent-ruby/concurrent/executor/timer_set.rb +172 -0
  90. data/lib/concurrent-ruby/concurrent/executors.rb +20 -0
  91. data/lib/concurrent-ruby/concurrent/future.rb +141 -0
  92. data/lib/concurrent-ruby/concurrent/hash.rb +59 -0
  93. data/lib/concurrent-ruby/concurrent/immutable_struct.rb +101 -0
  94. data/lib/concurrent-ruby/concurrent/ivar.rb +207 -0
  95. data/lib/concurrent-ruby/concurrent/map.rb +346 -0
  96. data/lib/concurrent-ruby/concurrent/maybe.rb +229 -0
  97. data/lib/concurrent-ruby/concurrent/mutable_struct.rb +239 -0
  98. data/lib/concurrent-ruby/concurrent/mvar.rb +242 -0
  99. data/lib/concurrent-ruby/concurrent/options.rb +42 -0
  100. data/lib/concurrent-ruby/concurrent/promise.rb +580 -0
  101. data/lib/concurrent-ruby/concurrent/promises.rb +2167 -0
  102. data/lib/concurrent-ruby/concurrent/re_include.rb +58 -0
  103. data/lib/concurrent-ruby/concurrent/scheduled_task.rb +331 -0
  104. data/lib/concurrent-ruby/concurrent/set.rb +74 -0
  105. data/lib/concurrent-ruby/concurrent/settable_struct.rb +139 -0
  106. data/lib/concurrent-ruby/concurrent/synchronization/abstract_lockable_object.rb +98 -0
  107. data/lib/concurrent-ruby/concurrent/synchronization/abstract_object.rb +24 -0
  108. data/lib/concurrent-ruby/concurrent/synchronization/abstract_struct.rb +171 -0
  109. data/lib/concurrent-ruby/concurrent/synchronization/condition.rb +60 -0
  110. data/lib/concurrent-ruby/concurrent/synchronization/jruby_lockable_object.rb +13 -0
  111. data/lib/concurrent-ruby/concurrent/synchronization/jruby_object.rb +45 -0
  112. data/lib/concurrent-ruby/concurrent/synchronization/lock.rb +36 -0
  113. data/lib/concurrent-ruby/concurrent/synchronization/lockable_object.rb +72 -0
  114. data/lib/concurrent-ruby/concurrent/synchronization/mri_object.rb +44 -0
  115. data/lib/concurrent-ruby/concurrent/synchronization/mutex_lockable_object.rb +88 -0
  116. data/lib/concurrent-ruby/concurrent/synchronization/object.rb +183 -0
  117. data/lib/concurrent-ruby/concurrent/synchronization/rbx_lockable_object.rb +71 -0
  118. data/lib/concurrent-ruby/concurrent/synchronization/rbx_object.rb +49 -0
  119. data/lib/concurrent-ruby/concurrent/synchronization/truffleruby_object.rb +47 -0
  120. data/lib/concurrent-ruby/concurrent/synchronization/volatile.rb +36 -0
  121. data/lib/concurrent-ruby/concurrent/synchronization.rb +30 -0
  122. data/lib/concurrent-ruby/concurrent/thread_safe/synchronized_delegator.rb +50 -0
  123. data/lib/concurrent-ruby/concurrent/thread_safe/util/adder.rb +74 -0
  124. data/lib/concurrent-ruby/concurrent/thread_safe/util/cheap_lockable.rb +118 -0
  125. data/lib/concurrent-ruby/concurrent/thread_safe/util/data_structures.rb +88 -0
  126. data/lib/concurrent-ruby/concurrent/thread_safe/util/power_of_two_tuple.rb +38 -0
  127. data/lib/concurrent-ruby/concurrent/thread_safe/util/striped64.rb +246 -0
  128. data/lib/concurrent-ruby/concurrent/thread_safe/util/volatile.rb +75 -0
  129. data/lib/concurrent-ruby/concurrent/thread_safe/util/xor_shift_random.rb +50 -0
  130. data/lib/concurrent-ruby/concurrent/thread_safe/util.rb +16 -0
  131. data/lib/concurrent-ruby/concurrent/timer_task.rb +311 -0
  132. data/lib/concurrent-ruby/concurrent/tuple.rb +86 -0
  133. data/lib/concurrent-ruby/concurrent/tvar.rb +221 -0
  134. data/lib/concurrent-ruby/concurrent/utility/engine.rb +56 -0
  135. data/lib/concurrent-ruby/concurrent/utility/monotonic_time.rb +90 -0
  136. data/lib/concurrent-ruby/concurrent/utility/native_extension_loader.rb +79 -0
  137. data/lib/concurrent-ruby/concurrent/utility/native_integer.rb +53 -0
  138. data/lib/concurrent-ruby/concurrent/utility/processor_counter.rb +130 -0
  139. data/lib/concurrent-ruby/concurrent/version.rb +3 -0
  140. data/lib/concurrent-ruby/concurrent-ruby.rb +5 -0
  141. data/lib/concurrent-ruby/concurrent.rb +134 -0
  142. 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