concurrent-ruby 1.0.5 → 1.1.6
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 +5 -5
- data/CHANGELOG.md +115 -0
- data/Gemfile +42 -0
- data/{LICENSE.txt → LICENSE.md} +2 -0
- data/README.md +242 -105
- data/Rakefile +332 -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 +159 -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-ruby.rb +1 -0
- data/lib/{concurrent.rb → concurrent-ruby/concurrent.rb} +24 -20
- data/lib/{concurrent → concurrent-ruby/concurrent}/agent.rb +7 -7
- data/lib/concurrent-ruby/concurrent/array.rb +66 -0
- data/lib/{concurrent → concurrent-ruby/concurrent}/async.rb +18 -4
- data/lib/{concurrent → concurrent-ruby/concurrent}/atom.rb +10 -10
- data/lib/{concurrent → concurrent-ruby/concurrent}/atomic/abstract_thread_local_var.rb +0 -0
- data/lib/{concurrent → concurrent-ruby/concurrent}/atomic/atomic_boolean.rb +26 -22
- data/lib/{concurrent → concurrent-ruby/concurrent}/atomic/atomic_fixnum.rb +27 -23
- data/lib/concurrent-ruby/concurrent/atomic/atomic_markable_reference.rb +164 -0
- data/lib/concurrent-ruby/concurrent/atomic/atomic_reference.rb +204 -0
- data/lib/{concurrent → concurrent-ruby/concurrent}/atomic/count_down_latch.rb +7 -7
- data/lib/{concurrent → concurrent-ruby/concurrent}/atomic/cyclic_barrier.rb +1 -1
- data/lib/{concurrent → concurrent-ruby/concurrent}/atomic/event.rb +1 -1
- data/lib/{concurrent → concurrent-ruby/concurrent}/atomic/java_count_down_latch.rb +9 -6
- data/lib/{concurrent → concurrent-ruby/concurrent}/atomic/java_thread_local_var.rb +0 -0
- data/lib/{concurrent → concurrent-ruby/concurrent}/atomic/mutex_atomic_boolean.rb +2 -0
- data/lib/{concurrent → concurrent-ruby/concurrent}/atomic/mutex_atomic_fixnum.rb +0 -0
- data/lib/{concurrent → concurrent-ruby/concurrent}/atomic/mutex_count_down_latch.rb +1 -0
- data/lib/{concurrent → concurrent-ruby/concurrent}/atomic/mutex_semaphore.rb +0 -0
- data/lib/{concurrent → concurrent-ruby/concurrent}/atomic/read_write_lock.rb +2 -1
- data/lib/{concurrent → concurrent-ruby/concurrent}/atomic/reentrant_read_write_lock.rb +3 -1
- data/lib/{concurrent → concurrent-ruby/concurrent}/atomic/ruby_thread_local_var.rb +43 -33
- data/lib/{concurrent → concurrent-ruby/concurrent}/atomic/semaphore.rb +8 -8
- data/lib/{concurrent → concurrent-ruby/concurrent}/atomic/thread_local_var.rb +8 -8
- data/lib/{concurrent → concurrent-ruby/concurrent}/atomic_reference/mutex_atomic.rb +3 -8
- data/lib/{concurrent → concurrent-ruby/concurrent}/atomic_reference/numeric_cas_wrapper.rb +1 -1
- data/lib/concurrent-ruby/concurrent/atomics.rb +10 -0
- data/lib/{concurrent → concurrent-ruby/concurrent}/collection/copy_on_notify_observer_set.rb +0 -0
- data/lib/{concurrent → concurrent-ruby/concurrent}/collection/copy_on_write_observer_set.rb +0 -0
- data/lib/{concurrent → concurrent-ruby/concurrent}/collection/java_non_concurrent_priority_queue.rb +0 -0
- data/lib/concurrent-ruby/concurrent/collection/lock_free_stack.rb +158 -0
- data/lib/{concurrent → concurrent-ruby/concurrent}/collection/map/atomic_reference_map_backend.rb +3 -3
- data/lib/{concurrent → concurrent-ruby/concurrent}/collection/map/mri_map_backend.rb +0 -0
- data/lib/{concurrent → concurrent-ruby/concurrent}/collection/map/non_concurrent_map_backend.rb +1 -2
- data/lib/{concurrent → concurrent-ruby/concurrent}/collection/map/synchronized_map_backend.rb +0 -0
- data/lib/{concurrent → concurrent-ruby/concurrent}/collection/non_concurrent_priority_queue.rb +30 -30
- data/lib/{concurrent → concurrent-ruby/concurrent}/collection/ruby_non_concurrent_priority_queue.rb +0 -0
- data/lib/{concurrent → concurrent-ruby/concurrent}/concern/deprecation.rb +0 -0
- data/lib/{concurrent → concurrent-ruby/concurrent}/concern/dereferenceable.rb +3 -3
- data/lib/{concurrent → concurrent-ruby/concurrent}/concern/logging.rb +6 -1
- data/lib/{concurrent → concurrent-ruby/concurrent}/concern/obligation.rb +0 -0
- data/lib/{concurrent → concurrent-ruby/concurrent}/concern/observable.rb +7 -7
- data/lib/concurrent-ruby/concurrent/concurrent_ruby.jar +0 -0
- data/lib/{concurrent → concurrent-ruby/concurrent}/configuration.rb +15 -15
- data/lib/{concurrent → concurrent-ruby/concurrent}/constants.rb +1 -1
- data/lib/{concurrent → concurrent-ruby/concurrent}/dataflow.rb +2 -1
- data/lib/{concurrent → concurrent-ruby/concurrent}/delay.rb +9 -7
- data/lib/{concurrent → concurrent-ruby/concurrent}/errors.rb +0 -0
- data/lib/{concurrent → concurrent-ruby/concurrent}/exchanger.rb +21 -25
- data/lib/{concurrent → concurrent-ruby/concurrent}/executor/abstract_executor_service.rb +19 -25
- data/lib/{concurrent → concurrent-ruby/concurrent}/executor/cached_thread_pool.rb +5 -5
- data/lib/{concurrent → concurrent-ruby/concurrent}/executor/executor_service.rb +17 -17
- data/lib/{concurrent → concurrent-ruby/concurrent}/executor/fixed_thread_pool.rb +27 -30
- data/lib/{concurrent → concurrent-ruby/concurrent}/executor/immediate_executor.rb +0 -0
- data/lib/{concurrent → concurrent-ruby/concurrent}/executor/indirect_immediate_executor.rb +0 -0
- data/lib/{concurrent → concurrent-ruby/concurrent}/executor/java_executor_service.rb +19 -16
- data/lib/{concurrent → concurrent-ruby/concurrent}/executor/java_single_thread_executor.rb +4 -3
- data/lib/{concurrent → concurrent-ruby/concurrent}/executor/java_thread_pool_executor.rb +12 -8
- data/lib/{concurrent → concurrent-ruby/concurrent}/executor/ruby_executor_service.rb +0 -2
- data/lib/{concurrent → concurrent-ruby/concurrent}/executor/ruby_single_thread_executor.rb +0 -1
- data/lib/{concurrent → concurrent-ruby/concurrent}/executor/ruby_thread_pool_executor.rb +9 -4
- data/lib/{concurrent → concurrent-ruby/concurrent}/executor/safe_task_executor.rb +0 -0
- data/lib/{concurrent → concurrent-ruby/concurrent}/executor/serial_executor_service.rb +0 -0
- data/lib/{concurrent → concurrent-ruby/concurrent}/executor/serialized_execution.rb +0 -0
- data/lib/{concurrent → concurrent-ruby/concurrent}/executor/serialized_execution_delegator.rb +0 -0
- data/lib/{concurrent → concurrent-ruby/concurrent}/executor/simple_executor_service.rb +1 -1
- data/lib/{concurrent → concurrent-ruby/concurrent}/executor/single_thread_executor.rb +3 -2
- data/lib/{concurrent → concurrent-ruby/concurrent}/executor/thread_pool_executor.rb +6 -6
- data/lib/{concurrent → concurrent-ruby/concurrent}/executor/timer_set.rb +14 -17
- data/lib/{concurrent → concurrent-ruby/concurrent}/executors.rb +0 -0
- data/lib/{concurrent → concurrent-ruby/concurrent}/future.rb +4 -1
- data/lib/concurrent-ruby/concurrent/hash.rb +59 -0
- data/lib/{concurrent → concurrent-ruby/concurrent}/immutable_struct.rb +8 -0
- data/lib/{concurrent → concurrent-ruby/concurrent}/ivar.rb +5 -6
- data/lib/concurrent-ruby/concurrent/map.rb +337 -0
- data/lib/{concurrent → concurrent-ruby/concurrent}/maybe.rb +1 -1
- data/lib/{concurrent → concurrent-ruby/concurrent}/mutable_struct.rb +25 -14
- data/lib/{concurrent → concurrent-ruby/concurrent}/mvar.rb +2 -2
- data/lib/{concurrent → concurrent-ruby/concurrent}/options.rb +0 -0
- data/lib/{concurrent → concurrent-ruby/concurrent}/promise.rb +53 -21
- data/lib/concurrent-ruby/concurrent/promises.rb +2167 -0
- data/lib/concurrent-ruby/concurrent/re_include.rb +58 -0
- data/lib/{concurrent → concurrent-ruby/concurrent}/scheduled_task.rb +0 -0
- data/lib/concurrent-ruby/concurrent/set.rb +66 -0
- data/lib/{concurrent → concurrent-ruby/concurrent}/settable_struct.rb +11 -0
- data/lib/{concurrent → concurrent-ruby/concurrent}/synchronization.rb +4 -5
- data/lib/{concurrent → concurrent-ruby/concurrent}/synchronization/abstract_lockable_object.rb +5 -5
- data/lib/{concurrent → concurrent-ruby/concurrent}/synchronization/abstract_object.rb +0 -0
- data/lib/{concurrent → concurrent-ruby/concurrent}/synchronization/abstract_struct.rb +18 -4
- data/lib/{concurrent → concurrent-ruby/concurrent}/synchronization/condition.rb +2 -0
- data/lib/{concurrent → concurrent-ruby/concurrent}/synchronization/jruby_lockable_object.rb +0 -0
- data/lib/{concurrent → concurrent-ruby/concurrent}/synchronization/jruby_object.rb +1 -0
- data/lib/{concurrent → concurrent-ruby/concurrent}/synchronization/lock.rb +2 -0
- data/lib/{concurrent → concurrent-ruby/concurrent}/synchronization/lockable_object.rb +6 -6
- data/lib/{concurrent → concurrent-ruby/concurrent}/synchronization/mri_object.rb +1 -0
- data/lib/{concurrent/synchronization/mri_lockable_object.rb → concurrent-ruby/concurrent/synchronization/mutex_lockable_object.rb} +19 -14
- data/lib/{concurrent → concurrent-ruby/concurrent}/synchronization/object.rb +53 -23
- data/lib/{concurrent → concurrent-ruby/concurrent}/synchronization/rbx_lockable_object.rb +0 -0
- data/lib/{concurrent → concurrent-ruby/concurrent}/synchronization/rbx_object.rb +1 -0
- data/lib/concurrent-ruby/concurrent/synchronization/truffleruby_object.rb +47 -0
- data/lib/{concurrent → concurrent-ruby/concurrent}/synchronization/volatile.rb +11 -9
- data/lib/{concurrent → concurrent-ruby/concurrent}/thread_safe/synchronized_delegator.rb +0 -0
- data/lib/{concurrent → concurrent-ruby/concurrent}/thread_safe/util.rb +0 -0
- data/lib/{concurrent → concurrent-ruby/concurrent}/thread_safe/util/adder.rb +0 -0
- data/lib/{concurrent → concurrent-ruby/concurrent}/thread_safe/util/cheap_lockable.rb +0 -0
- data/lib/concurrent-ruby/concurrent/thread_safe/util/data_structures.rb +63 -0
- data/lib/{concurrent → concurrent-ruby/concurrent}/thread_safe/util/power_of_two_tuple.rb +0 -0
- data/lib/{concurrent → concurrent-ruby/concurrent}/thread_safe/util/striped64.rb +9 -4
- data/lib/{concurrent → concurrent-ruby/concurrent}/thread_safe/util/volatile.rb +0 -0
- data/lib/{concurrent → concurrent-ruby/concurrent}/thread_safe/util/xor_shift_random.rb +0 -0
- data/lib/{concurrent → concurrent-ruby/concurrent}/timer_task.rb +5 -2
- data/lib/{concurrent → concurrent-ruby/concurrent}/tuple.rb +1 -1
- data/lib/{concurrent → concurrent-ruby/concurrent}/tvar.rb +2 -2
- data/lib/{concurrent → concurrent-ruby/concurrent}/utility/engine.rb +4 -4
- data/lib/{concurrent → concurrent-ruby/concurrent}/utility/monotonic_time.rb +3 -3
- data/lib/concurrent-ruby/concurrent/utility/native_extension_loader.rb +79 -0
- data/lib/{concurrent → concurrent-ruby/concurrent}/utility/native_integer.rb +0 -0
- data/lib/{concurrent → concurrent-ruby/concurrent}/utility/processor_counter.rb +5 -2
- data/lib/concurrent-ruby/concurrent/version.rb +3 -0
- metadata +146 -131
- data/lib/concurrent/array.rb +0 -39
- data/lib/concurrent/atomic/atomic_reference.rb +0 -51
- data/lib/concurrent/atomic_reference/concurrent_update_error.rb +0 -8
- data/lib/concurrent/atomic_reference/direct_update.rb +0 -81
- data/lib/concurrent/atomic_reference/jruby+truffle.rb +0 -2
- data/lib/concurrent/atomic_reference/jruby.rb +0 -16
- data/lib/concurrent/atomic_reference/rbx.rb +0 -22
- data/lib/concurrent/atomic_reference/ruby.rb +0 -32
- data/lib/concurrent/atomics.rb +0 -53
- data/lib/concurrent/edge.rb +0 -26
- data/lib/concurrent/hash.rb +0 -36
- data/lib/concurrent/lazy_register.rb +0 -81
- data/lib/concurrent/map.rb +0 -240
- data/lib/concurrent/synchronization/truffle_lockable_object.rb +0 -9
- data/lib/concurrent/synchronization/truffle_object.rb +0 -31
- data/lib/concurrent/thread_safe/util/array_hash_rbx.rb +0 -30
- data/lib/concurrent/utility/at_exit.rb +0 -97
- data/lib/concurrent/utility/native_extension_loader.rb +0 -73
- data/lib/concurrent/version.rb +0 -4
@@ -122,8 +122,6 @@ module Concurrent
|
|
122
122
|
raise ArgumentError.new("`min_threads` cannot be less than #{DEFAULT_MIN_POOL_SIZE}") if @min_length < DEFAULT_MIN_POOL_SIZE
|
123
123
|
raise ArgumentError.new("`min_threads` cannot be more than `max_threads`") if min_length > max_length
|
124
124
|
|
125
|
-
self.auto_terminate = opts.fetch(:auto_terminate, true)
|
126
|
-
|
127
125
|
@pool = [] # all workers
|
128
126
|
@ready = [] # used as a stash (most idle worker is at the start)
|
129
127
|
@queue = [] # used as queue
|
@@ -131,6 +129,7 @@ module Concurrent
|
|
131
129
|
@scheduled_task_count = 0
|
132
130
|
@completed_task_count = 0
|
133
131
|
@largest_length = 0
|
132
|
+
@workers_counter = 0
|
134
133
|
@ruby_pid = $$ # detects if Ruby has forked
|
135
134
|
|
136
135
|
@gc_interval = opts.fetch(:gc_interval, @idletime / 2.0).to_i # undocumented
|
@@ -224,7 +223,8 @@ module Concurrent
|
|
224
223
|
def ns_add_busy_worker
|
225
224
|
return if @pool.size >= @max_length
|
226
225
|
|
227
|
-
@
|
226
|
+
@workers_counter += 1
|
227
|
+
@pool << (worker = Worker.new(self, @workers_counter))
|
228
228
|
@largest_length = @pool.length if @pool.length > @largest_length
|
229
229
|
worker
|
230
230
|
end
|
@@ -284,6 +284,7 @@ module Concurrent
|
|
284
284
|
@scheduled_task_count = 0
|
285
285
|
@completed_task_count = 0
|
286
286
|
@largest_length = 0
|
287
|
+
@workers_counter = 0
|
287
288
|
@ruby_pid = $$
|
288
289
|
end
|
289
290
|
end
|
@@ -292,11 +293,15 @@ module Concurrent
|
|
292
293
|
class Worker
|
293
294
|
include Concern::Logging
|
294
295
|
|
295
|
-
def initialize(pool)
|
296
|
+
def initialize(pool, id)
|
296
297
|
# instance variables accessed only under pool's lock so no need to sync here again
|
297
298
|
@queue = Queue.new
|
298
299
|
@pool = pool
|
299
300
|
@thread = create_worker @queue, pool, pool.idletime
|
301
|
+
|
302
|
+
if @thread.respond_to?(:name=)
|
303
|
+
@thread.name = [pool.name, 'worker', id].compact.join('-')
|
304
|
+
end
|
300
305
|
end
|
301
306
|
|
302
307
|
def <<(message)
|
File without changes
|
File without changes
|
File without changes
|
data/lib/{concurrent → concurrent-ruby/concurrent}/executor/serialized_execution_delegator.rb
RENAMED
File without changes
|
@@ -1,3 +1,4 @@
|
|
1
|
+
require 'concurrent/utility/engine'
|
1
2
|
require 'concurrent/executor/ruby_single_thread_executor'
|
2
3
|
|
3
4
|
module Concurrent
|
@@ -14,7 +15,7 @@ module Concurrent
|
|
14
15
|
end
|
15
16
|
private_constant :SingleThreadExecutorImplementation
|
16
17
|
|
17
|
-
# @!macro
|
18
|
+
# @!macro single_thread_executor
|
18
19
|
#
|
19
20
|
# A thread pool with a single thread an unlimited queue. Should the thread
|
20
21
|
# die for any reason it will be removed and replaced, thus ensuring that
|
@@ -35,7 +36,7 @@ module Concurrent
|
|
35
36
|
# @!macro abstract_executor_service_public_api
|
36
37
|
class SingleThreadExecutor < SingleThreadExecutorImplementation
|
37
38
|
|
38
|
-
# @!macro
|
39
|
+
# @!macro single_thread_executor_method_initialize
|
39
40
|
#
|
40
41
|
# Create a new thread pool.
|
41
42
|
#
|
@@ -15,7 +15,7 @@ module Concurrent
|
|
15
15
|
end
|
16
16
|
private_constant :ThreadPoolExecutorImplementation
|
17
17
|
|
18
|
-
# @!macro
|
18
|
+
# @!macro thread_pool_executor
|
19
19
|
#
|
20
20
|
# An abstraction composed of one or more threads and a task queue. Tasks
|
21
21
|
# (blocks or `proc` objects) are submitted to the pool and added to the queue.
|
@@ -55,12 +55,12 @@ module Concurrent
|
|
55
55
|
# @!macro thread_pool_executor_public_api
|
56
56
|
class ThreadPoolExecutor < ThreadPoolExecutorImplementation
|
57
57
|
|
58
|
-
# @!macro
|
58
|
+
# @!macro thread_pool_executor_method_initialize
|
59
59
|
#
|
60
60
|
# Create a new thread pool.
|
61
|
-
#
|
61
|
+
#
|
62
62
|
# @param [Hash] opts the options which configure the thread pool.
|
63
|
-
#
|
63
|
+
#
|
64
64
|
# @option opts [Integer] :max_threads (DEFAULT_MAX_POOL_SIZE) the maximum
|
65
65
|
# number of threads to be created
|
66
66
|
# @option opts [Integer] :min_threads (DEFAULT_MIN_POOL_SIZE) When a new task is submitted
|
@@ -73,12 +73,12 @@ module Concurrent
|
|
73
73
|
# @option opts [Symbol] :fallback_policy (:abort) the policy for handling new
|
74
74
|
# tasks that are received when the queue size has reached
|
75
75
|
# `max_queue` or the executor has shut down
|
76
|
-
#
|
76
|
+
#
|
77
77
|
# @raise [ArgumentError] if `:max_threads` is less than one
|
78
78
|
# @raise [ArgumentError] if `:min_threads` is less than zero
|
79
79
|
# @raise [ArgumentError] if `:fallback_policy` is not one of the values specified
|
80
80
|
# in `FALLBACK_POLICIES`
|
81
|
-
#
|
81
|
+
#
|
82
82
|
# @see http://docs.oracle.com/javase/7/docs/api/java/util/concurrent/ThreadPoolExecutor.html
|
83
83
|
|
84
84
|
# @!method initialize(opts = {})
|
@@ -20,7 +20,7 @@ module Concurrent
|
|
20
20
|
|
21
21
|
# Create a new set of timed tasks.
|
22
22
|
#
|
23
|
-
# @!macro
|
23
|
+
# @!macro executor_options
|
24
24
|
#
|
25
25
|
# @param [Hash] opts the options used to specify the executor on which to perform actions
|
26
26
|
# @option opts [Executor] :executor when set use the given `Executor` instance.
|
@@ -48,11 +48,9 @@ module Concurrent
|
|
48
48
|
def post(delay, *args, &task)
|
49
49
|
raise ArgumentError.new('no block given') unless block_given?
|
50
50
|
return false unless running?
|
51
|
-
opts = {
|
52
|
-
|
53
|
-
|
54
|
-
timer_set: self
|
55
|
-
}
|
51
|
+
opts = { executor: @task_executor,
|
52
|
+
args: args,
|
53
|
+
timer_set: self }
|
56
54
|
task = ScheduledTask.execute(delay, opts, &task) # may raise exception
|
57
55
|
task.unscheduled? ? false : task
|
58
56
|
end
|
@@ -74,12 +72,11 @@ module Concurrent
|
|
74
72
|
# @param [Hash] opts the options to create the object with.
|
75
73
|
# @!visibility private
|
76
74
|
def ns_initialize(opts)
|
77
|
-
@queue
|
78
|
-
@task_executor
|
79
|
-
@timer_executor
|
80
|
-
@condition
|
81
|
-
@ruby_pid
|
82
|
-
self.auto_terminate = opts.fetch(:auto_terminate, true)
|
75
|
+
@queue = Collection::NonConcurrentPriorityQueue.new(order: :min)
|
76
|
+
@task_executor = Options.executor_from_options(opts) || Concurrent.global_io_executor
|
77
|
+
@timer_executor = SingleThreadExecutor.new
|
78
|
+
@condition = Event.new
|
79
|
+
@ruby_pid = $$ # detects if Ruby has forked
|
83
80
|
end
|
84
81
|
|
85
82
|
# Post the task to the internal queue.
|
@@ -90,7 +87,7 @@ module Concurrent
|
|
90
87
|
#
|
91
88
|
# @!visibility private
|
92
89
|
def post_task(task)
|
93
|
-
synchronize{ ns_post_task(task) }
|
90
|
+
synchronize { ns_post_task(task) }
|
94
91
|
end
|
95
92
|
|
96
93
|
# @!visibility private
|
@@ -98,7 +95,7 @@ module Concurrent
|
|
98
95
|
return false unless ns_running?
|
99
96
|
ns_reset_if_forked
|
100
97
|
if (task.initial_delay) <= 0.01
|
101
|
-
task.executor.post{ task.process_task }
|
98
|
+
task.executor.post { task.process_task }
|
102
99
|
else
|
103
100
|
@queue.push(task)
|
104
101
|
# only post the process method when the queue is empty
|
@@ -116,7 +113,7 @@ module Concurrent
|
|
116
113
|
#
|
117
114
|
# @!visibility private
|
118
115
|
def remove_task(task)
|
119
|
-
synchronize{ @queue.delete(task) }
|
116
|
+
synchronize { @queue.delete(task) }
|
120
117
|
end
|
121
118
|
|
122
119
|
# `ExecutorService` callback called during shutdown.
|
@@ -148,7 +145,7 @@ module Concurrent
|
|
148
145
|
task = synchronize { @condition.reset; @queue.peek }
|
149
146
|
break unless task
|
150
147
|
|
151
|
-
now
|
148
|
+
now = Concurrent.monotonic_time
|
152
149
|
diff = task.schedule_time - now
|
153
150
|
|
154
151
|
if diff <= 0
|
@@ -165,7 +162,7 @@ module Concurrent
|
|
165
162
|
# queue now must have the same pop time, or a closer one, as
|
166
163
|
# when we peeked).
|
167
164
|
task = synchronize { @queue.pop }
|
168
|
-
task.executor.post{ task.process_task }
|
165
|
+
task.executor.post { task.process_task }
|
169
166
|
else
|
170
167
|
@condition.wait([diff, 60].min)
|
171
168
|
end
|
File without changes
|
@@ -6,9 +6,12 @@ require 'concurrent/executor/safe_task_executor'
|
|
6
6
|
|
7
7
|
require 'concurrent/options'
|
8
8
|
|
9
|
+
# TODO (pitr-ch 14-Mar-2017): deprecate, Future, Promise, etc.
|
10
|
+
|
11
|
+
|
9
12
|
module Concurrent
|
10
13
|
|
11
|
-
# {include:file:
|
14
|
+
# {include:file:docs-source/future.md}
|
12
15
|
#
|
13
16
|
# @!macro copy_options
|
14
17
|
#
|
@@ -0,0 +1,59 @@
|
|
1
|
+
require 'concurrent/utility/engine'
|
2
|
+
require 'concurrent/thread_safe/util'
|
3
|
+
|
4
|
+
module Concurrent
|
5
|
+
|
6
|
+
# @!macro concurrent_hash
|
7
|
+
#
|
8
|
+
# A thread-safe subclass of Hash. This version locks against the object
|
9
|
+
# itself for every method call, ensuring only one thread can be reading
|
10
|
+
# or writing at a time. This includes iteration methods like `#each`,
|
11
|
+
# which takes the lock repeatedly when reading an item.
|
12
|
+
#
|
13
|
+
# @see http://ruby-doc.org/core-2.2.0/Hash.html Ruby standard library `Hash`
|
14
|
+
|
15
|
+
# @!macro internal_implementation_note
|
16
|
+
HashImplementation = case
|
17
|
+
when Concurrent.on_cruby?
|
18
|
+
# Hash is thread-safe in practice because CRuby runs
|
19
|
+
# threads one at a time and does not do context
|
20
|
+
# switching during the execution of C functions.
|
21
|
+
::Hash
|
22
|
+
|
23
|
+
when Concurrent.on_jruby?
|
24
|
+
require 'jruby/synchronized'
|
25
|
+
|
26
|
+
class JRubyHash < ::Hash
|
27
|
+
include JRuby::Synchronized
|
28
|
+
end
|
29
|
+
JRubyHash
|
30
|
+
|
31
|
+
when Concurrent.on_rbx?
|
32
|
+
require 'monitor'
|
33
|
+
require 'concurrent/thread_safe/util/data_structures'
|
34
|
+
|
35
|
+
class RbxHash < ::Hash
|
36
|
+
end
|
37
|
+
ThreadSafe::Util.make_synchronized_on_rbx RbxHash
|
38
|
+
RbxHash
|
39
|
+
|
40
|
+
when Concurrent.on_truffleruby?
|
41
|
+
require 'concurrent/thread_safe/util/data_structures'
|
42
|
+
|
43
|
+
class TruffleRubyHash < ::Hash
|
44
|
+
end
|
45
|
+
|
46
|
+
ThreadSafe::Util.make_synchronized_on_truffleruby TruffleRubyHash
|
47
|
+
TruffleRubyHash
|
48
|
+
|
49
|
+
else
|
50
|
+
warn 'Possibly unsupported Ruby implementation'
|
51
|
+
::Hash
|
52
|
+
end
|
53
|
+
private_constant :HashImplementation
|
54
|
+
|
55
|
+
# @!macro concurrent_hash
|
56
|
+
class Hash < HashImplementation
|
57
|
+
end
|
58
|
+
|
59
|
+
end
|
@@ -70,6 +70,14 @@ module Concurrent
|
|
70
70
|
ns_select(&block)
|
71
71
|
end
|
72
72
|
|
73
|
+
private
|
74
|
+
|
75
|
+
# @!visibility private
|
76
|
+
def initialize_copy(original)
|
77
|
+
super(original)
|
78
|
+
ns_initialize_copy
|
79
|
+
end
|
80
|
+
|
73
81
|
# @!macro struct_new
|
74
82
|
def self.new(*args, &block)
|
75
83
|
clazz_name = nil
|
@@ -98,10 +98,10 @@ module Concurrent
|
|
98
98
|
observer
|
99
99
|
end
|
100
100
|
|
101
|
-
# @!macro
|
101
|
+
# @!macro ivar_set_method
|
102
102
|
# Set the `IVar` to a value and wake or notify all threads waiting on it.
|
103
103
|
#
|
104
|
-
# @!macro
|
104
|
+
# @!macro ivar_set_parameters_and_exceptions
|
105
105
|
# @param [Object] value the value to store in the `IVar`
|
106
106
|
# @yield A block operation to use for setting the value
|
107
107
|
# @raise [ArgumentError] if both a value and a block are given
|
@@ -124,7 +124,7 @@ module Concurrent
|
|
124
124
|
self
|
125
125
|
end
|
126
126
|
|
127
|
-
# @!macro
|
127
|
+
# @!macro ivar_fail_method
|
128
128
|
# Set the `IVar` to failed due to some error and wake or notify all threads waiting on it.
|
129
129
|
#
|
130
130
|
# @param [Object] reason for the failure
|
@@ -157,9 +157,8 @@ module Concurrent
|
|
157
157
|
self.observers = Collection::CopyOnWriteObserverSet.new
|
158
158
|
set_deref_options(opts)
|
159
159
|
|
160
|
-
|
161
|
-
|
162
|
-
else
|
160
|
+
@state = :pending
|
161
|
+
if value != NULL
|
163
162
|
ns_complete_without_notification(true, value, nil)
|
164
163
|
end
|
165
164
|
end
|
@@ -0,0 +1,337 @@
|
|
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_rbx? || Concurrent.on_truffleruby?
|
19
|
+
require 'concurrent/collection/map/atomic_reference_map_backend'
|
20
|
+
AtomicReferenceMapBackend
|
21
|
+
else
|
22
|
+
warn 'Concurrent::Map: unsupported Ruby engine, using a fully synchronized Concurrent::Map implementation'
|
23
|
+
require 'concurrent/collection/map/synchronized_map_backend'
|
24
|
+
SynchronizedMapBackend
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
# `Concurrent::Map` is a hash-like object and should have much better performance
|
29
|
+
# characteristics, especially under high concurrency, than `Concurrent::Hash`.
|
30
|
+
# However, `Concurrent::Map `is not strictly semantically equivalent to a ruby `Hash`
|
31
|
+
# -- for instance, it does not necessarily retain ordering by insertion time as `Hash`
|
32
|
+
# does. For most uses it should do fine though, and we recommend you consider
|
33
|
+
# `Concurrent::Map` instead of `Concurrent::Hash` for your concurrency-safe hash needs.
|
34
|
+
class Map < Collection::MapImplementation
|
35
|
+
|
36
|
+
# @!macro map.atomic_method
|
37
|
+
# This method is atomic.
|
38
|
+
|
39
|
+
# @!macro map.atomic_method_with_block
|
40
|
+
# This method is atomic.
|
41
|
+
# @note Atomic methods taking a block do not allow the `self` instance
|
42
|
+
# to be used within the block. Doing so will cause a deadlock.
|
43
|
+
|
44
|
+
# @!method compute_if_absent(key)
|
45
|
+
# Compute and store new value for key if the key is absent.
|
46
|
+
# @param [Object] key
|
47
|
+
# @yield new value
|
48
|
+
# @yieldreturn [Object] new value
|
49
|
+
# @return [Object] new value or current value
|
50
|
+
# @!macro map.atomic_method_with_block
|
51
|
+
|
52
|
+
# @!method compute_if_present(key)
|
53
|
+
# Compute and store new value for key if the key is present.
|
54
|
+
# @param [Object] key
|
55
|
+
# @yield new value
|
56
|
+
# @yieldparam old_value [Object]
|
57
|
+
# @yieldreturn [Object, nil] new value, when nil the key is removed
|
58
|
+
# @return [Object, nil] new value or nil
|
59
|
+
# @!macro map.atomic_method_with_block
|
60
|
+
|
61
|
+
# @!method compute(key)
|
62
|
+
# Compute and store new value for key.
|
63
|
+
# @param [Object] key
|
64
|
+
# @yield compute new value from old one
|
65
|
+
# @yieldparam old_value [Object, nil] old_value, or nil when key is absent
|
66
|
+
# @yieldreturn [Object, nil] new value, when nil the key is removed
|
67
|
+
# @return [Object, nil] new value or nil
|
68
|
+
# @!macro map.atomic_method_with_block
|
69
|
+
|
70
|
+
# @!method merge_pair(key, value)
|
71
|
+
# If the key is absent, the value is stored, otherwise new value is
|
72
|
+
# computed with a block.
|
73
|
+
# @param [Object] key
|
74
|
+
# @param [Object] value
|
75
|
+
# @yield compute new value from old one
|
76
|
+
# @yieldparam old_value [Object] old value
|
77
|
+
# @yieldreturn [Object, nil] new value, when nil the key is removed
|
78
|
+
# @return [Object, nil] new value or nil
|
79
|
+
# @!macro map.atomic_method_with_block
|
80
|
+
|
81
|
+
# @!method replace_pair(key, old_value, new_value)
|
82
|
+
# Replaces old_value with new_value if key exists and current value
|
83
|
+
# matches old_value
|
84
|
+
# @param [Object] key
|
85
|
+
# @param [Object] old_value
|
86
|
+
# @param [Object] new_value
|
87
|
+
# @return [true, false] true if replaced
|
88
|
+
# @!macro map.atomic_method
|
89
|
+
|
90
|
+
# @!method replace_if_exists(key, new_value)
|
91
|
+
# Replaces current value with new_value if key exists
|
92
|
+
# @param [Object] key
|
93
|
+
# @param [Object] new_value
|
94
|
+
# @return [Object, nil] old value or nil
|
95
|
+
# @!macro map.atomic_method
|
96
|
+
|
97
|
+
# @!method get_and_set(key, value)
|
98
|
+
# Get the current value under key and set new value.
|
99
|
+
# @param [Object] key
|
100
|
+
# @param [Object] value
|
101
|
+
# @return [Object, nil] old value or nil when the key was absent
|
102
|
+
# @!macro map.atomic_method
|
103
|
+
|
104
|
+
# @!method delete(key)
|
105
|
+
# Delete key and its value.
|
106
|
+
# @param [Object] key
|
107
|
+
# @return [Object, nil] old value or nil when the key was absent
|
108
|
+
# @!macro map.atomic_method
|
109
|
+
|
110
|
+
# @!method delete_pair(key, value)
|
111
|
+
# Delete pair and its value if current value equals the provided value.
|
112
|
+
# @param [Object] key
|
113
|
+
# @param [Object] value
|
114
|
+
# @return [true, false] true if deleted
|
115
|
+
# @!macro map.atomic_method
|
116
|
+
|
117
|
+
|
118
|
+
def initialize(options = nil, &block)
|
119
|
+
if options.kind_of?(::Hash)
|
120
|
+
validate_options_hash!(options)
|
121
|
+
else
|
122
|
+
options = nil
|
123
|
+
end
|
124
|
+
|
125
|
+
super(options)
|
126
|
+
@default_proc = block
|
127
|
+
end
|
128
|
+
|
129
|
+
# Get a value with key
|
130
|
+
# @param [Object] key
|
131
|
+
# @return [Object] the value
|
132
|
+
def [](key)
|
133
|
+
if value = super # non-falsy value is an existing mapping, return it right away
|
134
|
+
value
|
135
|
+
# 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
|
136
|
+
# 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
|
137
|
+
# would be returned)
|
138
|
+
# note: nil == value check is not technically necessary
|
139
|
+
elsif @default_proc && nil == value && NULL == (value = get_or_default(key, NULL))
|
140
|
+
@default_proc.call(self, key)
|
141
|
+
else
|
142
|
+
value
|
143
|
+
end
|
144
|
+
end
|
145
|
+
|
146
|
+
alias_method :get, :[]
|
147
|
+
# TODO (pitr-ch 30-Oct-2018): doc
|
148
|
+
alias_method :put, :[]=
|
149
|
+
|
150
|
+
# Get a value with key, or default_value when key is absent,
|
151
|
+
# or fail when no default_value is given.
|
152
|
+
# @param [Object] key
|
153
|
+
# @param [Object] default_value
|
154
|
+
# @yield default value for a key
|
155
|
+
# @yieldparam key [Object]
|
156
|
+
# @yieldreturn [Object] default value
|
157
|
+
# @return [Object] the value or default value
|
158
|
+
# @raise [KeyError] when key is missing and no default_value is provided
|
159
|
+
# @!macro map_method_not_atomic
|
160
|
+
# @note The "fetch-then-act" methods of `Map` are not atomic. `Map` is intended
|
161
|
+
# to be use as a concurrency primitive with strong happens-before
|
162
|
+
# guarantees. It is not intended to be used as a high-level abstraction
|
163
|
+
# supporting complex operations. All read and write operations are
|
164
|
+
# thread safe, but no guarantees are made regarding race conditions
|
165
|
+
# between the fetch operation and yielding to the block. Additionally,
|
166
|
+
# this method does not support recursion. This is due to internal
|
167
|
+
# constraints that are very unlikely to change in the near future.
|
168
|
+
def fetch(key, default_value = NULL)
|
169
|
+
if NULL != (value = get_or_default(key, NULL))
|
170
|
+
value
|
171
|
+
elsif block_given?
|
172
|
+
yield key
|
173
|
+
elsif NULL != default_value
|
174
|
+
default_value
|
175
|
+
else
|
176
|
+
raise_fetch_no_key
|
177
|
+
end
|
178
|
+
end
|
179
|
+
|
180
|
+
# Fetch value with key, or store default value when key is absent,
|
181
|
+
# or fail when no default_value is given. This is a two step operation,
|
182
|
+
# therefore not atomic. The store can overwrite other concurrently
|
183
|
+
# stored value.
|
184
|
+
# @param [Object] key
|
185
|
+
# @param [Object] default_value
|
186
|
+
# @yield default value for a key
|
187
|
+
# @yieldparam key [Object]
|
188
|
+
# @yieldreturn [Object] default value
|
189
|
+
# @return [Object] the value or default value
|
190
|
+
# @!macro map.atomic_method_with_block
|
191
|
+
def fetch_or_store(key, default_value = NULL)
|
192
|
+
fetch(key) do
|
193
|
+
put(key, block_given? ? yield(key) : (NULL == default_value ? raise_fetch_no_key : default_value))
|
194
|
+
end
|
195
|
+
end
|
196
|
+
|
197
|
+
# Insert value into map with key if key is absent in one atomic step.
|
198
|
+
# @param [Object] key
|
199
|
+
# @param [Object] value
|
200
|
+
# @return [Object, nil] the previous value when key was present or nil when there was no key
|
201
|
+
def put_if_absent(key, value)
|
202
|
+
computed = false
|
203
|
+
result = compute_if_absent(key) do
|
204
|
+
computed = true
|
205
|
+
value
|
206
|
+
end
|
207
|
+
computed ? nil : result
|
208
|
+
end unless method_defined?(:put_if_absent)
|
209
|
+
|
210
|
+
# Is the value stored in the map. Iterates over all values.
|
211
|
+
# @param [Object] value
|
212
|
+
# @return [true, false]
|
213
|
+
def value?(value)
|
214
|
+
each_value do |v|
|
215
|
+
return true if value.equal?(v)
|
216
|
+
end
|
217
|
+
false
|
218
|
+
end
|
219
|
+
|
220
|
+
# All keys
|
221
|
+
# @return [::Array<Object>] keys
|
222
|
+
def keys
|
223
|
+
arr = []
|
224
|
+
each_pair { |k, v| arr << k }
|
225
|
+
arr
|
226
|
+
end unless method_defined?(:keys)
|
227
|
+
|
228
|
+
# All values
|
229
|
+
# @return [::Array<Object>] values
|
230
|
+
def values
|
231
|
+
arr = []
|
232
|
+
each_pair { |k, v| arr << v }
|
233
|
+
arr
|
234
|
+
end unless method_defined?(:values)
|
235
|
+
|
236
|
+
# Iterates over each key.
|
237
|
+
# @yield for each key in the map
|
238
|
+
# @yieldparam key [Object]
|
239
|
+
# @return [self]
|
240
|
+
# @!macro map.atomic_method_with_block
|
241
|
+
def each_key
|
242
|
+
each_pair { |k, v| yield k }
|
243
|
+
end unless method_defined?(:each_key)
|
244
|
+
|
245
|
+
# Iterates over each value.
|
246
|
+
# @yield for each value in the map
|
247
|
+
# @yieldparam value [Object]
|
248
|
+
# @return [self]
|
249
|
+
# @!macro map.atomic_method_with_block
|
250
|
+
def each_value
|
251
|
+
each_pair { |k, v| yield v }
|
252
|
+
end unless method_defined?(:each_value)
|
253
|
+
|
254
|
+
# Iterates over each key value pair.
|
255
|
+
# @yield for each key value pair in the map
|
256
|
+
# @yieldparam key [Object]
|
257
|
+
# @yieldparam value [Object]
|
258
|
+
# @return [self]
|
259
|
+
# @!macro map.atomic_method_with_block
|
260
|
+
def each_pair
|
261
|
+
return enum_for :each_pair unless block_given?
|
262
|
+
super
|
263
|
+
end
|
264
|
+
|
265
|
+
alias_method :each, :each_pair unless method_defined?(:each)
|
266
|
+
|
267
|
+
# Find key of a value.
|
268
|
+
# @param [Object] value
|
269
|
+
# @return [Object, nil] key or nil when not found
|
270
|
+
def key(value)
|
271
|
+
each_pair { |k, v| return k if v == value }
|
272
|
+
nil
|
273
|
+
end unless method_defined?(:key)
|
274
|
+
alias_method :index, :key if RUBY_VERSION < '1.9'
|
275
|
+
|
276
|
+
# Is map empty?
|
277
|
+
# @return [true, false]
|
278
|
+
def empty?
|
279
|
+
each_pair { |k, v| return false }
|
280
|
+
true
|
281
|
+
end unless method_defined?(:empty?)
|
282
|
+
|
283
|
+
# The size of map.
|
284
|
+
# @return [Integer] size
|
285
|
+
def size
|
286
|
+
count = 0
|
287
|
+
each_pair { |k, v| count += 1 }
|
288
|
+
count
|
289
|
+
end unless method_defined?(:size)
|
290
|
+
|
291
|
+
# @!visibility private
|
292
|
+
def marshal_dump
|
293
|
+
raise TypeError, "can't dump hash with default proc" if @default_proc
|
294
|
+
h = {}
|
295
|
+
each_pair { |k, v| h[k] = v }
|
296
|
+
h
|
297
|
+
end
|
298
|
+
|
299
|
+
# @!visibility private
|
300
|
+
def marshal_load(hash)
|
301
|
+
initialize
|
302
|
+
populate_from(hash)
|
303
|
+
end
|
304
|
+
|
305
|
+
undef :freeze
|
306
|
+
|
307
|
+
# @!visibility private
|
308
|
+
def inspect
|
309
|
+
format '%s entries=%d default_proc=%s>', to_s[0..-2], size.to_s, @default_proc.inspect
|
310
|
+
end
|
311
|
+
|
312
|
+
private
|
313
|
+
|
314
|
+
def raise_fetch_no_key
|
315
|
+
raise KeyError, 'key not found'
|
316
|
+
end
|
317
|
+
|
318
|
+
def initialize_copy(other)
|
319
|
+
super
|
320
|
+
populate_from(other)
|
321
|
+
end
|
322
|
+
|
323
|
+
def populate_from(hash)
|
324
|
+
hash.each_pair { |k, v| self[k] = v }
|
325
|
+
self
|
326
|
+
end
|
327
|
+
|
328
|
+
def validate_options_hash!(options)
|
329
|
+
if (initial_capacity = options[:initial_capacity]) && (!initial_capacity.kind_of?(Integer) || initial_capacity < 0)
|
330
|
+
raise ArgumentError, ":initial_capacity must be a positive Integer"
|
331
|
+
end
|
332
|
+
if (load_factor = options[:load_factor]) && (!load_factor.kind_of?(Numeric) || load_factor <= 0 || load_factor > 1)
|
333
|
+
raise ArgumentError, ":load_factor must be a number between 0 and 1"
|
334
|
+
end
|
335
|
+
end
|
336
|
+
end
|
337
|
+
end
|