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,57 @@
|
|
1
|
+
require 'concurrent/utility/engine'
|
2
|
+
require 'concurrent/executor/ruby_single_thread_executor'
|
3
|
+
|
4
|
+
module Concurrent
|
5
|
+
|
6
|
+
if Concurrent.on_jruby?
|
7
|
+
require 'concurrent/executor/java_single_thread_executor'
|
8
|
+
end
|
9
|
+
|
10
|
+
SingleThreadExecutorImplementation = case
|
11
|
+
when Concurrent.on_jruby?
|
12
|
+
JavaSingleThreadExecutor
|
13
|
+
else
|
14
|
+
RubySingleThreadExecutor
|
15
|
+
end
|
16
|
+
private_constant :SingleThreadExecutorImplementation
|
17
|
+
|
18
|
+
# @!macro single_thread_executor
|
19
|
+
#
|
20
|
+
# A thread pool with a single thread an unlimited queue. Should the thread
|
21
|
+
# die for any reason it will be removed and replaced, thus ensuring that
|
22
|
+
# the executor will always remain viable and available to process jobs.
|
23
|
+
#
|
24
|
+
# A common pattern for background processing is to create a single thread
|
25
|
+
# on which an infinite loop is run. The thread's loop blocks on an input
|
26
|
+
# source (perhaps blocking I/O or a queue) and processes each input as it
|
27
|
+
# is received. This pattern has several issues. The thread itself is highly
|
28
|
+
# susceptible to errors during processing. Also, the thread itself must be
|
29
|
+
# constantly monitored and restarted should it die. `SingleThreadExecutor`
|
30
|
+
# encapsulates all these bahaviors. The task processor is highly resilient
|
31
|
+
# to errors from within tasks. Also, should the thread die it will
|
32
|
+
# automatically be restarted.
|
33
|
+
#
|
34
|
+
# The API and behavior of this class are based on Java's `SingleThreadExecutor`.
|
35
|
+
#
|
36
|
+
# @!macro abstract_executor_service_public_api
|
37
|
+
class SingleThreadExecutor < SingleThreadExecutorImplementation
|
38
|
+
|
39
|
+
# @!macro single_thread_executor_method_initialize
|
40
|
+
#
|
41
|
+
# Create a new thread pool.
|
42
|
+
#
|
43
|
+
# @option opts [Symbol] :fallback_policy (:discard) the policy for handling new
|
44
|
+
# tasks that are received when the queue size has reached
|
45
|
+
# `max_queue` or the executor has shut down
|
46
|
+
#
|
47
|
+
# @raise [ArgumentError] if `:fallback_policy` is not one of the values specified
|
48
|
+
# in `FALLBACK_POLICIES`
|
49
|
+
#
|
50
|
+
# @see http://docs.oracle.com/javase/tutorial/essential/concurrency/pools.html
|
51
|
+
# @see http://docs.oracle.com/javase/7/docs/api/java/util/concurrent/Executors.html
|
52
|
+
# @see http://docs.oracle.com/javase/8/docs/api/java/util/concurrent/ExecutorService.html
|
53
|
+
|
54
|
+
# @!method initialize(opts = {})
|
55
|
+
# @!macro single_thread_executor_method_initialize
|
56
|
+
end
|
57
|
+
end
|
@@ -0,0 +1,88 @@
|
|
1
|
+
require 'concurrent/utility/engine'
|
2
|
+
require 'concurrent/executor/ruby_thread_pool_executor'
|
3
|
+
|
4
|
+
module Concurrent
|
5
|
+
|
6
|
+
if Concurrent.on_jruby?
|
7
|
+
require 'concurrent/executor/java_thread_pool_executor'
|
8
|
+
end
|
9
|
+
|
10
|
+
ThreadPoolExecutorImplementation = case
|
11
|
+
when Concurrent.on_jruby?
|
12
|
+
JavaThreadPoolExecutor
|
13
|
+
else
|
14
|
+
RubyThreadPoolExecutor
|
15
|
+
end
|
16
|
+
private_constant :ThreadPoolExecutorImplementation
|
17
|
+
|
18
|
+
# @!macro thread_pool_executor
|
19
|
+
#
|
20
|
+
# An abstraction composed of one or more threads and a task queue. Tasks
|
21
|
+
# (blocks or `proc` objects) are submitted to the pool and added to the queue.
|
22
|
+
# The threads in the pool remove the tasks and execute them in the order
|
23
|
+
# they were received.
|
24
|
+
#
|
25
|
+
# A `ThreadPoolExecutor` will automatically adjust the pool size according
|
26
|
+
# to the bounds set by `min-threads` and `max-threads`. When a new task is
|
27
|
+
# submitted and fewer than `min-threads` threads are running, a new thread
|
28
|
+
# is created to handle the request, even if other worker threads are idle.
|
29
|
+
# If there are more than `min-threads` but less than `max-threads` threads
|
30
|
+
# running, a new thread will be created only if the queue is full.
|
31
|
+
#
|
32
|
+
# Threads that are idle for too long will be garbage collected, down to the
|
33
|
+
# configured minimum options. Should a thread crash it, too, will be garbage collected.
|
34
|
+
#
|
35
|
+
# `ThreadPoolExecutor` is based on the Java class of the same name. From
|
36
|
+
# the official Java documentation;
|
37
|
+
#
|
38
|
+
# > Thread pools address two different problems: they usually provide
|
39
|
+
# > improved performance when executing large numbers of asynchronous tasks,
|
40
|
+
# > due to reduced per-task invocation overhead, and they provide a means
|
41
|
+
# > of bounding and managing the resources, including threads, consumed
|
42
|
+
# > when executing a collection of tasks. Each ThreadPoolExecutor also
|
43
|
+
# > maintains some basic statistics, such as the number of completed tasks.
|
44
|
+
# >
|
45
|
+
# > To be useful across a wide range of contexts, this class provides many
|
46
|
+
# > adjustable parameters and extensibility hooks. However, programmers are
|
47
|
+
# > urged to use the more convenient Executors factory methods
|
48
|
+
# > [CachedThreadPool] (unbounded thread pool, with automatic thread reclamation),
|
49
|
+
# > [FixedThreadPool] (fixed size thread pool) and [SingleThreadExecutor] (single
|
50
|
+
# > background thread), that preconfigure settings for the most common usage
|
51
|
+
# > scenarios.
|
52
|
+
#
|
53
|
+
# @!macro thread_pool_options
|
54
|
+
#
|
55
|
+
# @!macro thread_pool_executor_public_api
|
56
|
+
class ThreadPoolExecutor < ThreadPoolExecutorImplementation
|
57
|
+
|
58
|
+
# @!macro thread_pool_executor_method_initialize
|
59
|
+
#
|
60
|
+
# Create a new thread pool.
|
61
|
+
#
|
62
|
+
# @param [Hash] opts the options which configure the thread pool.
|
63
|
+
#
|
64
|
+
# @option opts [Integer] :max_threads (DEFAULT_MAX_POOL_SIZE) the maximum
|
65
|
+
# number of threads to be created
|
66
|
+
# @option opts [Integer] :min_threads (DEFAULT_MIN_POOL_SIZE) When a new task is submitted
|
67
|
+
# and fewer than `min_threads` are running, a new thread is created
|
68
|
+
# @option opts [Integer] :idletime (DEFAULT_THREAD_IDLETIMEOUT) the maximum
|
69
|
+
# number of seconds a thread may be idle before being reclaimed
|
70
|
+
# @option opts [Integer] :max_queue (DEFAULT_MAX_QUEUE_SIZE) the maximum
|
71
|
+
# number of tasks allowed in the work queue at any one time; a value of
|
72
|
+
# zero means the queue may grow without bound
|
73
|
+
# @option opts [Symbol] :fallback_policy (:abort) the policy for handling new
|
74
|
+
# tasks that are received when the queue size has reached
|
75
|
+
# `max_queue` or the executor has shut down
|
76
|
+
# @option opts [Boolean] :synchronous (DEFAULT_SYNCHRONOUS) whether or not a value of 0
|
77
|
+
# for :max_queue means the queue must perform direct hand-off rather than unbounded.
|
78
|
+
# @raise [ArgumentError] if `:max_threads` is less than one
|
79
|
+
# @raise [ArgumentError] if `:min_threads` is less than zero
|
80
|
+
# @raise [ArgumentError] if `:fallback_policy` is not one of the values specified
|
81
|
+
# in `FALLBACK_POLICIES`
|
82
|
+
#
|
83
|
+
# @see http://docs.oracle.com/javase/7/docs/api/java/util/concurrent/ThreadPoolExecutor.html
|
84
|
+
|
85
|
+
# @!method initialize(opts = {})
|
86
|
+
# @!macro thread_pool_executor_method_initialize
|
87
|
+
end
|
88
|
+
end
|
@@ -0,0 +1,172 @@
|
|
1
|
+
require 'concurrent/scheduled_task'
|
2
|
+
require 'concurrent/atomic/event'
|
3
|
+
require 'concurrent/collection/non_concurrent_priority_queue'
|
4
|
+
require 'concurrent/executor/executor_service'
|
5
|
+
require 'concurrent/executor/single_thread_executor'
|
6
|
+
|
7
|
+
require 'concurrent/options'
|
8
|
+
|
9
|
+
module Concurrent
|
10
|
+
|
11
|
+
# Executes a collection of tasks, each after a given delay. A master task
|
12
|
+
# monitors the set and schedules each task for execution at the appropriate
|
13
|
+
# time. Tasks are run on the global thread pool or on the supplied executor.
|
14
|
+
# Each task is represented as a `ScheduledTask`.
|
15
|
+
#
|
16
|
+
# @see Concurrent::ScheduledTask
|
17
|
+
#
|
18
|
+
# @!macro monotonic_clock_warning
|
19
|
+
class TimerSet < RubyExecutorService
|
20
|
+
|
21
|
+
# Create a new set of timed tasks.
|
22
|
+
#
|
23
|
+
# @!macro executor_options
|
24
|
+
#
|
25
|
+
# @param [Hash] opts the options used to specify the executor on which to perform actions
|
26
|
+
# @option opts [Executor] :executor when set use the given `Executor` instance.
|
27
|
+
# Three special values are also supported: `:task` returns the global task pool,
|
28
|
+
# `:operation` returns the global operation pool, and `:immediate` returns a new
|
29
|
+
# `ImmediateExecutor` object.
|
30
|
+
def initialize(opts = {})
|
31
|
+
super(opts)
|
32
|
+
end
|
33
|
+
|
34
|
+
# Post a task to be execute run after a given delay (in seconds). If the
|
35
|
+
# delay is less than 1/100th of a second the task will be immediately post
|
36
|
+
# to the executor.
|
37
|
+
#
|
38
|
+
# @param [Float] delay the number of seconds to wait for before executing the task.
|
39
|
+
# @param [Array<Object>] args the arguments passed to the task on execution.
|
40
|
+
#
|
41
|
+
# @yield the task to be performed.
|
42
|
+
#
|
43
|
+
# @return [Concurrent::ScheduledTask, false] IVar representing the task if the post
|
44
|
+
# is successful; false after shutdown.
|
45
|
+
#
|
46
|
+
# @raise [ArgumentError] if the intended execution time is not in the future.
|
47
|
+
# @raise [ArgumentError] if no block is given.
|
48
|
+
def post(delay, *args, &task)
|
49
|
+
raise ArgumentError.new('no block given') unless block_given?
|
50
|
+
return false unless running?
|
51
|
+
opts = { executor: @task_executor,
|
52
|
+
args: args,
|
53
|
+
timer_set: self }
|
54
|
+
task = ScheduledTask.execute(delay, opts, &task) # may raise exception
|
55
|
+
task.unscheduled? ? false : task
|
56
|
+
end
|
57
|
+
|
58
|
+
# Begin an immediate shutdown. In-progress tasks will be allowed to
|
59
|
+
# complete but enqueued tasks will be dismissed and no new tasks
|
60
|
+
# will be accepted. Has no additional effect if the thread pool is
|
61
|
+
# not running.
|
62
|
+
def kill
|
63
|
+
shutdown
|
64
|
+
end
|
65
|
+
|
66
|
+
private :<<
|
67
|
+
|
68
|
+
private
|
69
|
+
|
70
|
+
# Initialize the object.
|
71
|
+
#
|
72
|
+
# @param [Hash] opts the options to create the object with.
|
73
|
+
# @!visibility private
|
74
|
+
def ns_initialize(opts)
|
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
|
80
|
+
end
|
81
|
+
|
82
|
+
# Post the task to the internal queue.
|
83
|
+
#
|
84
|
+
# @note This is intended as a callback method from ScheduledTask
|
85
|
+
# only. It is not intended to be used directly. Post a task
|
86
|
+
# by using the `SchedulesTask#execute` method.
|
87
|
+
#
|
88
|
+
# @!visibility private
|
89
|
+
def post_task(task)
|
90
|
+
synchronize { ns_post_task(task) }
|
91
|
+
end
|
92
|
+
|
93
|
+
# @!visibility private
|
94
|
+
def ns_post_task(task)
|
95
|
+
return false unless ns_running?
|
96
|
+
ns_reset_if_forked
|
97
|
+
if (task.initial_delay) <= 0.01
|
98
|
+
task.executor.post { task.process_task }
|
99
|
+
else
|
100
|
+
@queue.push(task)
|
101
|
+
# only post the process method when the queue is empty
|
102
|
+
@timer_executor.post(&method(:process_tasks)) if @queue.length == 1
|
103
|
+
@condition.set
|
104
|
+
end
|
105
|
+
true
|
106
|
+
end
|
107
|
+
|
108
|
+
# Remove the given task from the queue.
|
109
|
+
#
|
110
|
+
# @note This is intended as a callback method from `ScheduledTask`
|
111
|
+
# only. It is not intended to be used directly. Cancel a task
|
112
|
+
# by using the `ScheduledTask#cancel` method.
|
113
|
+
#
|
114
|
+
# @!visibility private
|
115
|
+
def remove_task(task)
|
116
|
+
synchronize { @queue.delete(task) }
|
117
|
+
end
|
118
|
+
|
119
|
+
# `ExecutorService` callback called during shutdown.
|
120
|
+
#
|
121
|
+
# @!visibility private
|
122
|
+
def ns_shutdown_execution
|
123
|
+
ns_reset_if_forked
|
124
|
+
@queue.clear
|
125
|
+
@timer_executor.kill
|
126
|
+
stopped_event.set
|
127
|
+
end
|
128
|
+
|
129
|
+
def ns_reset_if_forked
|
130
|
+
if $$ != @ruby_pid
|
131
|
+
@queue.clear
|
132
|
+
@condition.reset
|
133
|
+
@ruby_pid = $$
|
134
|
+
end
|
135
|
+
end
|
136
|
+
|
137
|
+
# Run a loop and execute tasks in the scheduled order and at the approximate
|
138
|
+
# scheduled time. If no tasks remain the thread will exit gracefully so that
|
139
|
+
# garbage collection can occur. If there are no ready tasks it will sleep
|
140
|
+
# for up to 60 seconds waiting for the next scheduled task.
|
141
|
+
#
|
142
|
+
# @!visibility private
|
143
|
+
def process_tasks
|
144
|
+
loop do
|
145
|
+
task = synchronize { @condition.reset; @queue.peek }
|
146
|
+
break unless task
|
147
|
+
|
148
|
+
now = Concurrent.monotonic_time
|
149
|
+
diff = task.schedule_time - now
|
150
|
+
|
151
|
+
if diff <= 0
|
152
|
+
# We need to remove the task from the queue before passing
|
153
|
+
# it to the executor, to avoid race conditions where we pass
|
154
|
+
# the peek'ed task to the executor and then pop a different
|
155
|
+
# one that's been added in the meantime.
|
156
|
+
#
|
157
|
+
# Note that there's no race condition between the peek and
|
158
|
+
# this pop - this pop could retrieve a different task from
|
159
|
+
# the peek, but that task would be due to fire now anyway
|
160
|
+
# (because @queue is a priority queue, and this thread is
|
161
|
+
# the only reader, so whatever timer is at the head of the
|
162
|
+
# queue now must have the same pop time, or a closer one, as
|
163
|
+
# when we peeked).
|
164
|
+
task = synchronize { @queue.pop }
|
165
|
+
task.executor.post { task.process_task }
|
166
|
+
else
|
167
|
+
@condition.wait([diff, 60].min)
|
168
|
+
end
|
169
|
+
end
|
170
|
+
end
|
171
|
+
end
|
172
|
+
end
|
@@ -0,0 +1,20 @@
|
|
1
|
+
require 'concurrent/executor/abstract_executor_service'
|
2
|
+
require 'concurrent/executor/cached_thread_pool'
|
3
|
+
require 'concurrent/executor/executor_service'
|
4
|
+
require 'concurrent/executor/fixed_thread_pool'
|
5
|
+
require 'concurrent/executor/immediate_executor'
|
6
|
+
require 'concurrent/executor/indirect_immediate_executor'
|
7
|
+
require 'concurrent/executor/java_executor_service'
|
8
|
+
require 'concurrent/executor/java_single_thread_executor'
|
9
|
+
require 'concurrent/executor/java_thread_pool_executor'
|
10
|
+
require 'concurrent/executor/ruby_executor_service'
|
11
|
+
require 'concurrent/executor/ruby_single_thread_executor'
|
12
|
+
require 'concurrent/executor/ruby_thread_pool_executor'
|
13
|
+
require 'concurrent/executor/cached_thread_pool'
|
14
|
+
require 'concurrent/executor/safe_task_executor'
|
15
|
+
require 'concurrent/executor/serial_executor_service'
|
16
|
+
require 'concurrent/executor/serialized_execution'
|
17
|
+
require 'concurrent/executor/serialized_execution_delegator'
|
18
|
+
require 'concurrent/executor/single_thread_executor'
|
19
|
+
require 'concurrent/executor/thread_pool_executor'
|
20
|
+
require 'concurrent/executor/timer_set'
|
@@ -0,0 +1,141 @@
|
|
1
|
+
require 'thread'
|
2
|
+
require 'concurrent/constants'
|
3
|
+
require 'concurrent/errors'
|
4
|
+
require 'concurrent/ivar'
|
5
|
+
require 'concurrent/executor/safe_task_executor'
|
6
|
+
|
7
|
+
require 'concurrent/options'
|
8
|
+
|
9
|
+
# TODO (pitr-ch 14-Mar-2017): deprecate, Future, Promise, etc.
|
10
|
+
|
11
|
+
|
12
|
+
module Concurrent
|
13
|
+
|
14
|
+
# {include:file:docs-source/future.md}
|
15
|
+
#
|
16
|
+
# @!macro copy_options
|
17
|
+
#
|
18
|
+
# @see http://ruby-doc.org/stdlib-2.1.1/libdoc/observer/rdoc/Observable.html Ruby Observable module
|
19
|
+
# @see http://clojuredocs.org/clojure_core/clojure.core/future Clojure's future function
|
20
|
+
# @see http://docs.oracle.com/javase/7/docs/api/java/util/concurrent/Future.html java.util.concurrent.Future
|
21
|
+
class Future < IVar
|
22
|
+
|
23
|
+
# Create a new `Future` in the `:unscheduled` state.
|
24
|
+
#
|
25
|
+
# @yield the asynchronous operation to perform
|
26
|
+
#
|
27
|
+
# @!macro executor_and_deref_options
|
28
|
+
#
|
29
|
+
# @option opts [object, Array] :args zero or more arguments to be passed the task
|
30
|
+
# block on execution
|
31
|
+
#
|
32
|
+
# @raise [ArgumentError] if no block is given
|
33
|
+
def initialize(opts = {}, &block)
|
34
|
+
raise ArgumentError.new('no block given') unless block_given?
|
35
|
+
super(NULL, opts.merge(__task_from_block__: block), &nil)
|
36
|
+
end
|
37
|
+
|
38
|
+
# Execute an `:unscheduled` `Future`. Immediately sets the state to `:pending` and
|
39
|
+
# passes the block to a new thread/thread pool for eventual execution.
|
40
|
+
# Does nothing if the `Future` is in any state other than `:unscheduled`.
|
41
|
+
#
|
42
|
+
# @return [Future] a reference to `self`
|
43
|
+
#
|
44
|
+
# @example Instance and execute in separate steps
|
45
|
+
# future = Concurrent::Future.new{ sleep(1); 42 }
|
46
|
+
# future.state #=> :unscheduled
|
47
|
+
# future.execute
|
48
|
+
# future.state #=> :pending
|
49
|
+
#
|
50
|
+
# @example Instance and execute in one line
|
51
|
+
# future = Concurrent::Future.new{ sleep(1); 42 }.execute
|
52
|
+
# future.state #=> :pending
|
53
|
+
def execute
|
54
|
+
if compare_and_set_state(:pending, :unscheduled)
|
55
|
+
@executor.post{ safe_execute(@task, @args) }
|
56
|
+
self
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
# Create a new `Future` object with the given block, execute it, and return the
|
61
|
+
# `:pending` object.
|
62
|
+
#
|
63
|
+
# @yield the asynchronous operation to perform
|
64
|
+
#
|
65
|
+
# @!macro executor_and_deref_options
|
66
|
+
#
|
67
|
+
# @option opts [object, Array] :args zero or more arguments to be passed the task
|
68
|
+
# block on execution
|
69
|
+
#
|
70
|
+
# @raise [ArgumentError] if no block is given
|
71
|
+
#
|
72
|
+
# @return [Future] the newly created `Future` in the `:pending` state
|
73
|
+
#
|
74
|
+
# @example
|
75
|
+
# future = Concurrent::Future.execute{ sleep(1); 42 }
|
76
|
+
# future.state #=> :pending
|
77
|
+
def self.execute(opts = {}, &block)
|
78
|
+
Future.new(opts, &block).execute
|
79
|
+
end
|
80
|
+
|
81
|
+
# @!macro ivar_set_method
|
82
|
+
def set(value = NULL, &block)
|
83
|
+
check_for_block_or_value!(block_given?, value)
|
84
|
+
synchronize do
|
85
|
+
if @state != :unscheduled
|
86
|
+
raise MultipleAssignmentError
|
87
|
+
else
|
88
|
+
@task = block || Proc.new { value }
|
89
|
+
end
|
90
|
+
end
|
91
|
+
execute
|
92
|
+
end
|
93
|
+
|
94
|
+
# Attempt to cancel the operation if it has not already processed.
|
95
|
+
# The operation can only be cancelled while still `pending`. It cannot
|
96
|
+
# be cancelled once it has begun processing or has completed.
|
97
|
+
#
|
98
|
+
# @return [Boolean] was the operation successfully cancelled.
|
99
|
+
def cancel
|
100
|
+
if compare_and_set_state(:cancelled, :pending)
|
101
|
+
complete(false, nil, CancelledOperationError.new)
|
102
|
+
true
|
103
|
+
else
|
104
|
+
false
|
105
|
+
end
|
106
|
+
end
|
107
|
+
|
108
|
+
# Has the operation been successfully cancelled?
|
109
|
+
#
|
110
|
+
# @return [Boolean]
|
111
|
+
def cancelled?
|
112
|
+
state == :cancelled
|
113
|
+
end
|
114
|
+
|
115
|
+
# Wait the given number of seconds for the operation to complete.
|
116
|
+
# On timeout attempt to cancel the operation.
|
117
|
+
#
|
118
|
+
# @param [Numeric] timeout the maximum time in seconds to wait.
|
119
|
+
# @return [Boolean] true if the operation completed before the timeout
|
120
|
+
# else false
|
121
|
+
def wait_or_cancel(timeout)
|
122
|
+
wait(timeout)
|
123
|
+
if complete?
|
124
|
+
true
|
125
|
+
else
|
126
|
+
cancel
|
127
|
+
false
|
128
|
+
end
|
129
|
+
end
|
130
|
+
|
131
|
+
protected
|
132
|
+
|
133
|
+
def ns_initialize(value, opts)
|
134
|
+
super
|
135
|
+
@state = :unscheduled
|
136
|
+
@task = opts[:__task_from_block__]
|
137
|
+
@executor = Options.executor_from_options(opts) || Concurrent.global_io_executor
|
138
|
+
@args = get_arguments_from(opts)
|
139
|
+
end
|
140
|
+
end
|
141
|
+
end
|
@@ -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/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
|
@@ -0,0 +1,101 @@
|
|
1
|
+
require 'concurrent/synchronization/abstract_struct'
|
2
|
+
require 'concurrent/synchronization'
|
3
|
+
|
4
|
+
module Concurrent
|
5
|
+
|
6
|
+
# A thread-safe, immutable variation of Ruby's standard `Struct`.
|
7
|
+
#
|
8
|
+
# @see http://ruby-doc.org/core/Struct.html Ruby standard library `Struct`
|
9
|
+
module ImmutableStruct
|
10
|
+
include Synchronization::AbstractStruct
|
11
|
+
|
12
|
+
def self.included(base)
|
13
|
+
base.safe_initialization!
|
14
|
+
end
|
15
|
+
|
16
|
+
# @!macro struct_values
|
17
|
+
def values
|
18
|
+
ns_values
|
19
|
+
end
|
20
|
+
|
21
|
+
alias_method :to_a, :values
|
22
|
+
|
23
|
+
# @!macro struct_values_at
|
24
|
+
def values_at(*indexes)
|
25
|
+
ns_values_at(indexes)
|
26
|
+
end
|
27
|
+
|
28
|
+
# @!macro struct_inspect
|
29
|
+
def inspect
|
30
|
+
ns_inspect
|
31
|
+
end
|
32
|
+
|
33
|
+
alias_method :to_s, :inspect
|
34
|
+
|
35
|
+
# @!macro struct_merge
|
36
|
+
def merge(other, &block)
|
37
|
+
ns_merge(other, &block)
|
38
|
+
end
|
39
|
+
|
40
|
+
# @!macro struct_to_h
|
41
|
+
def to_h
|
42
|
+
ns_to_h
|
43
|
+
end
|
44
|
+
|
45
|
+
# @!macro struct_get
|
46
|
+
def [](member)
|
47
|
+
ns_get(member)
|
48
|
+
end
|
49
|
+
|
50
|
+
# @!macro struct_equality
|
51
|
+
def ==(other)
|
52
|
+
ns_equality(other)
|
53
|
+
end
|
54
|
+
|
55
|
+
# @!macro struct_each
|
56
|
+
def each(&block)
|
57
|
+
return enum_for(:each) unless block_given?
|
58
|
+
ns_each(&block)
|
59
|
+
end
|
60
|
+
|
61
|
+
# @!macro struct_each_pair
|
62
|
+
def each_pair(&block)
|
63
|
+
return enum_for(:each_pair) unless block_given?
|
64
|
+
ns_each_pair(&block)
|
65
|
+
end
|
66
|
+
|
67
|
+
# @!macro struct_select
|
68
|
+
def select(&block)
|
69
|
+
return enum_for(:select) unless block_given?
|
70
|
+
ns_select(&block)
|
71
|
+
end
|
72
|
+
|
73
|
+
private
|
74
|
+
|
75
|
+
# @!visibility private
|
76
|
+
def initialize_copy(original)
|
77
|
+
super(original)
|
78
|
+
ns_initialize_copy
|
79
|
+
end
|
80
|
+
|
81
|
+
# @!macro struct_new
|
82
|
+
def self.new(*args, &block)
|
83
|
+
clazz_name = nil
|
84
|
+
if args.length == 0
|
85
|
+
raise ArgumentError.new('wrong number of arguments (0 for 1+)')
|
86
|
+
elsif args.length > 0 && args.first.is_a?(String)
|
87
|
+
clazz_name = args.shift
|
88
|
+
end
|
89
|
+
FACTORY.define_struct(clazz_name, args, &block)
|
90
|
+
end
|
91
|
+
|
92
|
+
FACTORY = Class.new(Synchronization::LockableObject) do
|
93
|
+
def define_struct(name, members, &block)
|
94
|
+
synchronize do
|
95
|
+
Synchronization::AbstractStruct.define_struct_class(ImmutableStruct, Synchronization::Object, name, members, &block)
|
96
|
+
end
|
97
|
+
end
|
98
|
+
end.new
|
99
|
+
private_constant :FACTORY
|
100
|
+
end
|
101
|
+
end
|