concurrent-ruby 0.8.0.pre2-java → 0.9.0-java
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/CHANGELOG.md +114 -3
- data/README.md +111 -55
- data/lib/concurrent.rb +90 -14
- data/lib/concurrent/async.rb +143 -51
- data/lib/concurrent/atom.rb +131 -0
- data/lib/concurrent/atomic/atomic_boolean.rb +57 -107
- data/lib/concurrent/atomic/atomic_fixnum.rb +73 -101
- data/lib/concurrent/atomic/atomic_reference.rb +49 -0
- data/lib/concurrent/atomic/condition.rb +23 -12
- data/lib/concurrent/atomic/count_down_latch.rb +23 -21
- data/lib/concurrent/atomic/cyclic_barrier.rb +47 -47
- data/lib/concurrent/atomic/event.rb +33 -42
- data/lib/concurrent/atomic/read_write_lock.rb +252 -0
- data/lib/concurrent/atomic/semaphore.rb +64 -89
- data/lib/concurrent/atomic/thread_local_var.rb +130 -58
- data/lib/concurrent/atomic/thread_local_var/weak_key_map.rb +236 -0
- data/lib/concurrent/atomic_reference/direct_update.rb +34 -3
- data/lib/concurrent/atomic_reference/jruby.rb +6 -3
- data/lib/concurrent/atomic_reference/mutex_atomic.rb +17 -39
- data/lib/concurrent/atomic_reference/numeric_cas_wrapper.rb +3 -0
- data/lib/concurrent/atomic_reference/rbx.rb +4 -1
- data/lib/concurrent/atomic_reference/ruby.rb +6 -3
- data/lib/concurrent/atomics.rb +74 -4
- data/lib/concurrent/collection/copy_on_notify_observer_set.rb +115 -0
- data/lib/concurrent/collection/copy_on_write_observer_set.rb +119 -0
- data/lib/concurrent/collection/priority_queue.rb +300 -245
- data/lib/concurrent/concern/deprecation.rb +34 -0
- data/lib/concurrent/concern/dereferenceable.rb +88 -0
- data/lib/concurrent/concern/logging.rb +27 -0
- data/lib/concurrent/concern/obligation.rb +228 -0
- data/lib/concurrent/concern/observable.rb +85 -0
- data/lib/concurrent/configuration.rb +234 -109
- data/lib/concurrent/dataflow.rb +2 -3
- data/lib/concurrent/delay.rb +141 -50
- data/lib/concurrent/edge.rb +30 -0
- data/lib/concurrent/errors.rb +19 -7
- data/lib/concurrent/exchanger.rb +25 -1
- data/lib/concurrent/executor/cached_thread_pool.rb +51 -33
- data/lib/concurrent/executor/executor.rb +46 -299
- data/lib/concurrent/executor/executor_service.rb +521 -0
- data/lib/concurrent/executor/fixed_thread_pool.rb +196 -23
- data/lib/concurrent/executor/immediate_executor.rb +9 -9
- data/lib/concurrent/executor/indirect_immediate_executor.rb +4 -3
- data/lib/concurrent/executor/java_single_thread_executor.rb +17 -16
- data/lib/concurrent/executor/java_thread_pool_executor.rb +55 -102
- data/lib/concurrent/executor/ruby_single_thread_executor.rb +14 -16
- data/lib/concurrent/executor/ruby_thread_pool_executor.rb +250 -166
- data/lib/concurrent/executor/safe_task_executor.rb +5 -4
- data/lib/concurrent/executor/serialized_execution.rb +22 -18
- data/lib/concurrent/executor/{per_thread_executor.rb → simple_executor_service.rb} +29 -20
- data/lib/concurrent/executor/single_thread_executor.rb +32 -21
- data/lib/concurrent/executor/thread_pool_executor.rb +73 -60
- data/lib/concurrent/executor/timer_set.rb +96 -84
- data/lib/concurrent/executors.rb +1 -1
- data/lib/concurrent/future.rb +71 -38
- data/lib/concurrent/immutable_struct.rb +89 -0
- data/lib/concurrent/ivar.rb +152 -60
- data/lib/concurrent/lazy_register.rb +40 -20
- data/lib/concurrent/maybe.rb +226 -0
- data/lib/concurrent/mutable_struct.rb +227 -0
- data/lib/concurrent/mvar.rb +44 -43
- data/lib/concurrent/promise.rb +229 -136
- data/lib/concurrent/scheduled_task.rb +341 -43
- data/lib/concurrent/settable_struct.rb +127 -0
- data/lib/concurrent/synchronization.rb +17 -0
- data/lib/concurrent/synchronization/abstract_object.rb +163 -0
- data/lib/concurrent/synchronization/abstract_struct.rb +158 -0
- data/lib/concurrent/synchronization/condition.rb +53 -0
- data/lib/concurrent/synchronization/java_object.rb +34 -0
- data/lib/concurrent/synchronization/lock.rb +32 -0
- data/lib/concurrent/synchronization/monitor_object.rb +26 -0
- data/lib/concurrent/synchronization/mutex_object.rb +43 -0
- data/lib/concurrent/synchronization/object.rb +78 -0
- data/lib/concurrent/synchronization/rbx_object.rb +75 -0
- data/lib/concurrent/timer_task.rb +92 -103
- data/lib/concurrent/tvar.rb +42 -38
- data/lib/concurrent/utilities.rb +3 -1
- data/lib/concurrent/utility/at_exit.rb +97 -0
- data/lib/concurrent/utility/engine.rb +44 -0
- data/lib/concurrent/utility/monotonic_time.rb +59 -0
- data/lib/concurrent/utility/native_extension_loader.rb +56 -0
- data/lib/concurrent/utility/processor_counter.rb +156 -0
- data/lib/concurrent/utility/timeout.rb +18 -14
- data/lib/concurrent/utility/timer.rb +11 -6
- data/lib/concurrent/version.rb +2 -1
- data/lib/concurrent_ruby.rb +1 -0
- data/lib/concurrent_ruby_ext.jar +0 -0
- metadata +46 -66
- data/lib/concurrent/actor.rb +0 -103
- data/lib/concurrent/actor/behaviour.rb +0 -70
- data/lib/concurrent/actor/behaviour/abstract.rb +0 -48
- data/lib/concurrent/actor/behaviour/awaits.rb +0 -21
- data/lib/concurrent/actor/behaviour/buffer.rb +0 -54
- data/lib/concurrent/actor/behaviour/errors_on_unknown_message.rb +0 -12
- data/lib/concurrent/actor/behaviour/executes_context.rb +0 -18
- data/lib/concurrent/actor/behaviour/linking.rb +0 -45
- data/lib/concurrent/actor/behaviour/pausing.rb +0 -77
- data/lib/concurrent/actor/behaviour/removes_child.rb +0 -16
- data/lib/concurrent/actor/behaviour/sets_results.rb +0 -36
- data/lib/concurrent/actor/behaviour/supervised.rb +0 -59
- data/lib/concurrent/actor/behaviour/supervising.rb +0 -34
- data/lib/concurrent/actor/behaviour/terminates_children.rb +0 -13
- data/lib/concurrent/actor/behaviour/termination.rb +0 -54
- data/lib/concurrent/actor/context.rb +0 -154
- data/lib/concurrent/actor/core.rb +0 -217
- data/lib/concurrent/actor/default_dead_letter_handler.rb +0 -9
- data/lib/concurrent/actor/envelope.rb +0 -41
- data/lib/concurrent/actor/errors.rb +0 -27
- data/lib/concurrent/actor/internal_delegations.rb +0 -49
- data/lib/concurrent/actor/public_delegations.rb +0 -40
- data/lib/concurrent/actor/reference.rb +0 -81
- data/lib/concurrent/actor/root.rb +0 -37
- data/lib/concurrent/actor/type_check.rb +0 -48
- data/lib/concurrent/actor/utils.rb +0 -10
- data/lib/concurrent/actor/utils/ad_hoc.rb +0 -21
- data/lib/concurrent/actor/utils/balancer.rb +0 -42
- data/lib/concurrent/actor/utils/broadcast.rb +0 -52
- data/lib/concurrent/actor/utils/pool.rb +0 -59
- data/lib/concurrent/actress.rb +0 -3
- data/lib/concurrent/agent.rb +0 -209
- data/lib/concurrent/atomic.rb +0 -92
- data/lib/concurrent/atomic/copy_on_notify_observer_set.rb +0 -118
- data/lib/concurrent/atomic/copy_on_write_observer_set.rb +0 -117
- data/lib/concurrent/atomic/synchronization.rb +0 -51
- data/lib/concurrent/channel/buffered_channel.rb +0 -85
- data/lib/concurrent/channel/channel.rb +0 -41
- data/lib/concurrent/channel/unbuffered_channel.rb +0 -35
- data/lib/concurrent/channel/waitable_list.rb +0 -40
- data/lib/concurrent/channels.rb +0 -5
- data/lib/concurrent/collection/blocking_ring_buffer.rb +0 -71
- data/lib/concurrent/collection/ring_buffer.rb +0 -59
- data/lib/concurrent/collections.rb +0 -3
- data/lib/concurrent/dereferenceable.rb +0 -108
- data/lib/concurrent/executor/java_cached_thread_pool.rb +0 -32
- data/lib/concurrent/executor/java_fixed_thread_pool.rb +0 -31
- data/lib/concurrent/executor/ruby_cached_thread_pool.rb +0 -29
- data/lib/concurrent/executor/ruby_fixed_thread_pool.rb +0 -32
- data/lib/concurrent/executor/ruby_thread_pool_worker.rb +0 -73
- data/lib/concurrent/logging.rb +0 -20
- data/lib/concurrent/obligation.rb +0 -171
- data/lib/concurrent/observable.rb +0 -73
- data/lib/concurrent/options_parser.rb +0 -48
- data/lib/concurrent/utility/processor_count.rb +0 -152
- data/lib/extension_helper.rb +0 -37
@@ -1,31 +1,29 @@
|
|
1
|
-
|
1
|
+
require 'concurrent/executor/executor_service'
|
2
2
|
|
3
3
|
module Concurrent
|
4
4
|
|
5
5
|
# @!macro single_thread_executor
|
6
|
-
|
7
|
-
|
8
|
-
|
6
|
+
# @!macro thread_pool_options
|
7
|
+
# @!macro abstract_executor_service_public_api
|
8
|
+
# @!visibility private
|
9
|
+
class RubySingleThreadExecutor < RubyExecutorService
|
10
|
+
include SerialExecutorService
|
9
11
|
|
10
|
-
#
|
11
|
-
#
|
12
|
-
# @option opts [Symbol] :fallback_policy (:discard) the policy for
|
13
|
-
# handling new tasks that are received when the queue size has
|
14
|
-
# reached `max_queue` or after the executor has shut down
|
15
|
-
#
|
16
|
-
# @see http://docs.oracle.com/javase/tutorial/essential/concurrency/pools.html
|
17
|
-
# @see http://docs.oracle.com/javase/7/docs/api/java/util/concurrent/Executors.html
|
18
|
-
# @see http://docs.oracle.com/javase/8/docs/api/java/util/concurrent/ExecutorService.html
|
12
|
+
# @!macro single_thread_executor_method_initialize
|
19
13
|
def initialize(opts = {})
|
14
|
+
super
|
15
|
+
end
|
16
|
+
|
17
|
+
protected
|
18
|
+
|
19
|
+
def ns_initialize(opts)
|
20
20
|
@queue = Queue.new
|
21
21
|
@thread = nil
|
22
22
|
@fallback_policy = opts.fetch(:fallback_policy, :discard)
|
23
23
|
raise ArgumentError.new("#{@fallback_policy} is not a valid fallback policy") unless FALLBACK_POLICIES.include?(@fallback_policy)
|
24
|
-
|
24
|
+
self.auto_terminate = opts.fetch(:auto_terminate, true)
|
25
25
|
end
|
26
26
|
|
27
|
-
protected
|
28
|
-
|
29
27
|
# @!visibility private
|
30
28
|
def execute(*args, &task)
|
31
29
|
supervise
|
@@ -1,261 +1,345 @@
|
|
1
1
|
require 'thread'
|
2
2
|
|
3
|
-
require_relative 'executor'
|
4
3
|
require 'concurrent/atomic/event'
|
5
|
-
require 'concurrent/
|
4
|
+
require 'concurrent/concern/logging'
|
5
|
+
require 'concurrent/executor/executor_service'
|
6
|
+
require 'concurrent/utility/monotonic_time'
|
6
7
|
|
7
8
|
module Concurrent
|
8
9
|
|
9
10
|
# @!macro thread_pool_executor
|
10
|
-
|
11
|
-
|
11
|
+
# @!macro thread_pool_options
|
12
|
+
# @!visibility private
|
13
|
+
class RubyThreadPoolExecutor < RubyExecutorService
|
12
14
|
|
13
|
-
#
|
14
|
-
DEFAULT_MAX_POOL_SIZE =
|
15
|
+
# @!macro thread_pool_executor_constant_default_max_pool_size
|
16
|
+
DEFAULT_MAX_POOL_SIZE = 2_147_483_647 # java.lang.Integer::MAX_VALUE
|
15
17
|
|
16
|
-
#
|
18
|
+
# @!macro thread_pool_executor_constant_default_min_pool_size
|
17
19
|
DEFAULT_MIN_POOL_SIZE = 0
|
18
20
|
|
19
|
-
#
|
21
|
+
# @!macro thread_pool_executor_constant_default_max_queue_size
|
20
22
|
DEFAULT_MAX_QUEUE_SIZE = 0
|
21
23
|
|
22
|
-
#
|
23
|
-
# before being reclaimed.
|
24
|
+
# @!macro thread_pool_executor_constant_default_thread_timeout
|
24
25
|
DEFAULT_THREAD_IDLETIMEOUT = 60
|
25
26
|
|
26
|
-
#
|
27
|
+
# @!macro thread_pool_executor_attr_reader_max_length
|
27
28
|
attr_reader :max_length
|
28
29
|
|
29
|
-
#
|
30
|
+
# @!macro thread_pool_executor_attr_reader_min_length
|
30
31
|
attr_reader :min_length
|
31
32
|
|
32
|
-
#
|
33
|
+
# @!macro thread_pool_executor_attr_reader_largest_length
|
33
34
|
attr_reader :largest_length
|
34
35
|
|
35
|
-
#
|
36
|
+
# @!macro thread_pool_executor_attr_reader_scheduled_task_count
|
36
37
|
attr_reader :scheduled_task_count
|
37
38
|
|
38
|
-
#
|
39
|
+
# @!macro thread_pool_executor_attr_reader_completed_task_count
|
39
40
|
attr_reader :completed_task_count
|
40
41
|
|
41
|
-
#
|
42
|
+
# @!macro thread_pool_executor_attr_reader_idletime
|
42
43
|
attr_reader :idletime
|
43
44
|
|
44
|
-
#
|
45
|
-
# When the queue size reaches `max_queue` subsequent tasks will be rejected in
|
46
|
-
# accordance with the configured `fallback_policy`.
|
45
|
+
# @!macro thread_pool_executor_attr_reader_max_queue
|
47
46
|
attr_reader :max_queue
|
48
47
|
|
49
|
-
#
|
50
|
-
#
|
51
|
-
# @param [Hash] opts the options which configure the thread pool
|
52
|
-
#
|
53
|
-
# @option opts [Integer] :max_threads (DEFAULT_MAX_POOL_SIZE) the maximum
|
54
|
-
# number of threads to be created
|
55
|
-
# @option opts [Integer] :min_threads (DEFAULT_MIN_POOL_SIZE) the minimum
|
56
|
-
# number of threads to be retained
|
57
|
-
# @option opts [Integer] :idletime (DEFAULT_THREAD_IDLETIMEOUT) the maximum
|
58
|
-
# number of seconds a thread may be idle before being reclaimed
|
59
|
-
# @option opts [Integer] :max_queue (DEFAULT_MAX_QUEUE_SIZE) the maximum
|
60
|
-
# number of tasks allowed in the work queue at any one time; a value of
|
61
|
-
# zero means the queue may grow without bound
|
62
|
-
# @option opts [Symbol] :fallback_policy (:abort) the policy for handling new
|
63
|
-
# tasks that are received when the queue size has reached
|
64
|
-
# `max_queue` or the executor has shut down
|
65
|
-
#
|
66
|
-
# @raise [ArgumentError] if `:max_threads` is less than one
|
67
|
-
# @raise [ArgumentError] if `:min_threads` is less than zero
|
68
|
-
# @raise [ArgumentError] if `:fallback_policy` is not one of the values specified
|
69
|
-
# in `FALLBACK_POLICIES`
|
70
|
-
#
|
71
|
-
# @see http://docs.oracle.com/javase/7/docs/api/java/util/concurrent/ThreadPoolExecutor.html
|
48
|
+
# @!macro thread_pool_executor_method_initialize
|
72
49
|
def initialize(opts = {})
|
73
|
-
|
74
|
-
@max_length = opts.fetch(:max_threads, DEFAULT_MAX_POOL_SIZE).to_i
|
75
|
-
@idletime = opts.fetch(:idletime, DEFAULT_THREAD_IDLETIMEOUT).to_i
|
76
|
-
@max_queue = opts.fetch(:max_queue, DEFAULT_MAX_QUEUE_SIZE).to_i
|
77
|
-
@fallback_policy = opts.fetch(:fallback_policy, opts.fetch(:overflow_policy, :abort))
|
78
|
-
warn '[DEPRECATED] :overflow_policy is deprecated terminology, please use :fallback_policy instead' if opts.has_key?(:overflow_policy)
|
79
|
-
|
80
|
-
raise ArgumentError.new('max_threads must be greater than zero') if @max_length <= 0
|
81
|
-
raise ArgumentError.new('min_threads cannot be less than zero') if @min_length < 0
|
82
|
-
raise ArgumentError.new("#{fallback_policy} is not a valid fallback policy") unless FALLBACK_POLICIES.include?(@fallback_policy)
|
83
|
-
raise ArgumentError.new('min_threads cannot be more than max_threads') if min_length > max_length
|
84
|
-
|
85
|
-
init_executor
|
86
|
-
|
87
|
-
@pool = []
|
88
|
-
@queue = Queue.new
|
89
|
-
@scheduled_task_count = 0
|
90
|
-
@completed_task_count = 0
|
91
|
-
@largest_length = 0
|
92
|
-
|
93
|
-
@gc_interval = opts.fetch(:gc_interval, 1).to_i # undocumented
|
94
|
-
@last_gc_time = Time.now.to_f - [1.0, (@gc_interval * 2.0)].max
|
50
|
+
super(opts)
|
95
51
|
end
|
96
52
|
|
97
|
-
# @!macro
|
53
|
+
# @!macro executor_service_method_can_overflow_question
|
98
54
|
def can_overflow?
|
99
|
-
|
55
|
+
synchronize { ns_limited_queue? }
|
100
56
|
end
|
101
57
|
|
102
|
-
#
|
103
|
-
#
|
104
|
-
# @return [Integer] the length
|
58
|
+
# @!macro thread_pool_executor_attr_reader_length
|
105
59
|
def length
|
106
|
-
|
60
|
+
synchronize { @pool.length }
|
107
61
|
end
|
108
62
|
|
109
|
-
|
110
|
-
|
111
|
-
# The number of tasks in the queue awaiting execution.
|
112
|
-
#
|
113
|
-
# @return [Integer] the queue_length
|
63
|
+
# @!macro thread_pool_executor_attr_reader_queue_length
|
114
64
|
def queue_length
|
115
|
-
|
65
|
+
synchronize { @queue.length }
|
116
66
|
end
|
117
67
|
|
118
|
-
#
|
119
|
-
# new tasks. A value of -1 indicates that the queue may grow without bound.
|
120
|
-
#
|
121
|
-
# @return [Integer] the remaining_capacity
|
68
|
+
# @!macro thread_pool_executor_attr_reader_remaining_capacity
|
122
69
|
def remaining_capacity
|
123
|
-
|
70
|
+
synchronize do
|
71
|
+
if ns_limited_queue?
|
72
|
+
@max_queue - @queue.length
|
73
|
+
else
|
74
|
+
-1
|
75
|
+
end
|
76
|
+
end
|
124
77
|
end
|
125
78
|
|
126
|
-
#
|
127
|
-
|
128
|
-
|
129
|
-
def status
|
130
|
-
warn '[DEPRECATED] `status` is deprecated and will be removed soon.'
|
131
|
-
mutex.synchronize { @pool.collect { |worker| worker.status } }
|
79
|
+
# @!visibility private
|
80
|
+
def remove_busy_worker(worker)
|
81
|
+
synchronize { ns_remove_busy_worker worker }
|
132
82
|
end
|
133
83
|
|
134
|
-
# Run on task completion.
|
135
|
-
#
|
136
84
|
# @!visibility private
|
137
|
-
def
|
138
|
-
|
139
|
-
@completed_task_count += 1 #if success
|
140
|
-
break unless running?
|
141
|
-
end
|
85
|
+
def ready_worker(worker)
|
86
|
+
synchronize { ns_ready_worker worker }
|
142
87
|
end
|
143
88
|
|
144
|
-
# Run when a thread worker exits.
|
145
|
-
#
|
146
89
|
# @!visibility private
|
147
|
-
def
|
148
|
-
|
149
|
-
|
150
|
-
|
151
|
-
|
152
|
-
|
153
|
-
|
154
|
-
end
|
90
|
+
def worker_not_old_enough(worker)
|
91
|
+
synchronize { ns_worker_not_old_enough worker }
|
92
|
+
end
|
93
|
+
|
94
|
+
# @!visibility private
|
95
|
+
def worker_died(worker)
|
96
|
+
synchronize { ns_worker_died worker }
|
155
97
|
end
|
156
98
|
|
157
99
|
protected
|
158
100
|
|
159
101
|
# @!visibility private
|
160
|
-
def
|
161
|
-
|
162
|
-
|
102
|
+
def ns_initialize(opts)
|
103
|
+
@min_length = opts.fetch(:min_threads, DEFAULT_MIN_POOL_SIZE).to_i
|
104
|
+
@max_length = opts.fetch(:max_threads, DEFAULT_MAX_POOL_SIZE).to_i
|
105
|
+
@idletime = opts.fetch(:idletime, DEFAULT_THREAD_IDLETIMEOUT).to_i
|
106
|
+
@max_queue = opts.fetch(:max_queue, DEFAULT_MAX_QUEUE_SIZE).to_i
|
107
|
+
@fallback_policy = opts.fetch(:fallback_policy, opts.fetch(:overflow_policy, :abort))
|
108
|
+
raise ArgumentError.new("#{@fallback_policy} is not a valid fallback policy") unless FALLBACK_POLICIES.include?(@fallback_policy)
|
109
|
+
deprecated ':overflow_policy is deprecated terminology, please use :fallback_policy instead' if opts.has_key?(:overflow_policy)
|
110
|
+
|
111
|
+
raise ArgumentError.new("`max_threads` cannot be less than #{DEFAULT_MIN_POOL_SIZE}") if @max_length < DEFAULT_MIN_POOL_SIZE
|
112
|
+
raise ArgumentError.new("`max_threads` cannot be greater than #{DEFAULT_MAX_POOL_SIZE}") if @max_length > DEFAULT_MAX_POOL_SIZE
|
113
|
+
raise ArgumentError.new("`min_threads` cannot be less than #{DEFAULT_MIN_POOL_SIZE}") if @min_length < DEFAULT_MIN_POOL_SIZE
|
114
|
+
raise ArgumentError.new("`min_threads` cannot be more than `max_threads`") if min_length > max_length
|
115
|
+
|
116
|
+
self.auto_terminate = opts.fetch(:auto_terminate, true)
|
117
|
+
|
118
|
+
@pool = [] # all workers
|
119
|
+
@ready = [] # used as a stash (most idle worker is at the start)
|
120
|
+
@queue = [] # used as queue
|
121
|
+
# @ready or @queue is empty at all times
|
122
|
+
@scheduled_task_count = 0
|
123
|
+
@completed_task_count = 0
|
124
|
+
@largest_length = 0
|
125
|
+
|
126
|
+
@gc_interval = opts.fetch(:gc_interval, @idletime / 2.0).to_i # undocumented
|
127
|
+
@next_gc_time = Concurrent.monotonic_time + @gc_interval
|
128
|
+
end
|
129
|
+
|
130
|
+
# @!visibility private
|
131
|
+
def ns_limited_queue?
|
132
|
+
@max_queue != 0
|
133
|
+
end
|
134
|
+
|
135
|
+
# @!visibility private
|
136
|
+
def ns_execute(*args, &task)
|
137
|
+
if ns_assign_worker(*args, &task) || ns_enqueue(*args, &task)
|
163
138
|
@scheduled_task_count += 1
|
164
|
-
@queue << [args, task]
|
165
139
|
else
|
166
|
-
handle_fallback(*args, &task)
|
140
|
+
handle_fallback(*args, &task)
|
167
141
|
end
|
142
|
+
|
143
|
+
ns_prune_pool if @next_gc_time < Concurrent.monotonic_time
|
144
|
+
# raise unless @ready.empty? || @queue.empty? # assert
|
168
145
|
end
|
169
146
|
|
147
|
+
alias_method :execute, :ns_execute
|
148
|
+
|
170
149
|
# @!visibility private
|
171
|
-
def
|
150
|
+
def ns_shutdown_execution
|
172
151
|
if @pool.empty?
|
152
|
+
# nothing to do
|
173
153
|
stopped_event.set
|
174
|
-
else
|
175
|
-
@pool.length.times { @queue << :stop }
|
176
154
|
end
|
155
|
+
if @queue.empty?
|
156
|
+
# no more tasks will be accepted, just stop all workers
|
157
|
+
@pool.each(&:stop)
|
158
|
+
end
|
159
|
+
|
160
|
+
# raise unless @ready.empty? || @queue.empty? # assert
|
177
161
|
end
|
178
162
|
|
163
|
+
alias_method :shutdown_execution, :ns_shutdown_execution
|
164
|
+
|
179
165
|
# @!visibility private
|
180
|
-
def
|
181
|
-
|
182
|
-
|
166
|
+
def ns_kill_execution
|
167
|
+
# TODO log out unprocessed tasks in queue
|
168
|
+
# TODO try to shutdown first?
|
169
|
+
@pool.each &:kill
|
170
|
+
@pool.clear
|
171
|
+
@ready.clear
|
183
172
|
end
|
184
173
|
|
185
|
-
|
186
|
-
|
187
|
-
#
|
188
|
-
#
|
189
|
-
# @return [Boolean] true if the pool has enough capacity else false
|
174
|
+
alias_method :kill_execution, :ns_kill_execution
|
175
|
+
|
176
|
+
# tries to assign task to a worker, tries to get one from @ready or to create new one
|
177
|
+
# @return [true, false] if task is assigned to a worker
|
190
178
|
#
|
191
179
|
# @!visibility private
|
192
|
-
def
|
193
|
-
|
194
|
-
|
195
|
-
|
196
|
-
|
197
|
-
|
198
|
-
elsif @queue.empty? && @queue.num_waiting >= 1
|
199
|
-
additional = 0
|
200
|
-
elsif @pool.size == 0 && @min_length == 0
|
201
|
-
additional = 1
|
202
|
-
elsif @pool.size < @max_length || @max_length == 0
|
203
|
-
additional = 1
|
204
|
-
elsif @max_queue == 0 || @queue.size < @max_queue
|
205
|
-
additional = 0
|
180
|
+
def ns_assign_worker(*args, &task)
|
181
|
+
# keep growing if the pool is not at the minimum yet
|
182
|
+
worker = (@ready.pop if @pool.size >= @min_length) || ns_add_busy_worker
|
183
|
+
if worker
|
184
|
+
worker << [task, args]
|
185
|
+
true
|
206
186
|
else
|
207
|
-
|
187
|
+
false
|
208
188
|
end
|
189
|
+
rescue ThreadError
|
190
|
+
# Raised when the operating system refuses to create the new thread
|
191
|
+
return false
|
192
|
+
end
|
209
193
|
|
210
|
-
|
211
|
-
|
194
|
+
# tries to enqueue task
|
195
|
+
# @return [true, false] if enqueued
|
196
|
+
#
|
197
|
+
# @!visibility private
|
198
|
+
def ns_enqueue(*args, &task)
|
199
|
+
if !ns_limited_queue? || @queue.size < @max_queue
|
200
|
+
@queue << [task, args]
|
201
|
+
true
|
202
|
+
else
|
203
|
+
false
|
212
204
|
end
|
205
|
+
end
|
213
206
|
|
214
|
-
|
215
|
-
|
216
|
-
|
207
|
+
# @!visibility private
|
208
|
+
def ns_worker_died(worker)
|
209
|
+
ns_remove_busy_worker worker
|
210
|
+
replacement_worker = ns_add_busy_worker
|
211
|
+
ns_ready_worker replacement_worker, false if replacement_worker
|
212
|
+
end
|
213
|
+
|
214
|
+
# creates new worker which has to receive work to do after it's added
|
215
|
+
# @return [nil, Worker] nil of max capacity is reached
|
216
|
+
#
|
217
|
+
# @!visibility private
|
218
|
+
def ns_add_busy_worker
|
219
|
+
return if @pool.size >= @max_length
|
217
220
|
|
218
|
-
|
221
|
+
@pool << (worker = Worker.new(self))
|
222
|
+
@largest_length = @pool.length if @pool.length > @largest_length
|
223
|
+
worker
|
219
224
|
end
|
220
225
|
|
221
|
-
#
|
222
|
-
# have been idle too long. Will check the last time the pool was
|
223
|
-
# pruned and only run if the configured garbage collection
|
224
|
-
# interval has passed.
|
226
|
+
# handle ready worker, giving it new job or assigning back to @ready
|
225
227
|
#
|
226
228
|
# @!visibility private
|
227
|
-
def
|
228
|
-
|
229
|
-
|
230
|
-
|
231
|
-
|
232
|
-
|
233
|
-
|
234
|
-
|
229
|
+
def ns_ready_worker(worker, success = true)
|
230
|
+
@completed_task_count += 1 if success
|
231
|
+
task_and_args = @queue.shift
|
232
|
+
if task_and_args
|
233
|
+
worker << task_and_args
|
234
|
+
else
|
235
|
+
# stop workers when !running?, do not return them to @ready
|
236
|
+
if running?
|
237
|
+
@ready.push(worker)
|
238
|
+
else
|
239
|
+
worker.stop
|
240
|
+
end
|
235
241
|
end
|
236
242
|
end
|
237
243
|
|
238
|
-
#
|
244
|
+
# returns back worker to @ready which was not idle for enough time
|
239
245
|
#
|
240
246
|
# @!visibility private
|
241
|
-
def
|
242
|
-
|
243
|
-
@
|
247
|
+
def ns_worker_not_old_enough(worker)
|
248
|
+
# let's put workers coming from idle_test back to the start (as the oldest worker)
|
249
|
+
@ready.unshift(worker)
|
250
|
+
true
|
244
251
|
end
|
245
252
|
|
246
|
-
#
|
253
|
+
# removes a worker which is not in not tracked in @ready
|
247
254
|
#
|
248
|
-
#
|
255
|
+
# @!visibility private
|
256
|
+
def ns_remove_busy_worker(worker)
|
257
|
+
@pool.delete(worker)
|
258
|
+
stopped_event.set if @pool.empty? && !running?
|
259
|
+
true
|
260
|
+
end
|
261
|
+
|
262
|
+
# try oldest worker if it is idle for enough time, it's returned back at the start
|
249
263
|
#
|
250
264
|
# @!visibility private
|
251
|
-
def
|
252
|
-
|
253
|
-
|
254
|
-
|
255
|
-
|
256
|
-
|
265
|
+
def ns_prune_pool
|
266
|
+
return if @pool.size <= @min_length
|
267
|
+
|
268
|
+
last_used = @ready.shift
|
269
|
+
last_used << :idle_test if last_used
|
270
|
+
|
271
|
+
@next_gc_time = Concurrent.monotonic_time + @gc_interval
|
272
|
+
end
|
273
|
+
|
274
|
+
# @!visibility private
|
275
|
+
class Worker
|
276
|
+
include Concern::Logging
|
277
|
+
|
278
|
+
def initialize(pool)
|
279
|
+
# instance variables accessed only under pool's lock so no need to sync here again
|
280
|
+
@queue = Queue.new
|
281
|
+
@pool = pool
|
282
|
+
@thread = create_worker @queue, pool, pool.idletime
|
283
|
+
end
|
284
|
+
|
285
|
+
def <<(message)
|
286
|
+
@queue << message
|
287
|
+
end
|
288
|
+
|
289
|
+
def stop
|
290
|
+
@queue << :stop
|
291
|
+
end
|
292
|
+
|
293
|
+
def kill
|
294
|
+
@thread.kill
|
295
|
+
end
|
296
|
+
|
297
|
+
private
|
298
|
+
|
299
|
+
def create_worker(queue, pool, idletime)
|
300
|
+
Thread.new(queue, pool, idletime) do |queue, pool, idletime|
|
301
|
+
last_message = Concurrent.monotonic_time
|
302
|
+
catch(:stop) do
|
303
|
+
loop do
|
304
|
+
|
305
|
+
case message = queue.pop
|
306
|
+
when :idle_test
|
307
|
+
if (Concurrent.monotonic_time - last_message) > idletime
|
308
|
+
pool.remove_busy_worker(self)
|
309
|
+
throw :stop
|
310
|
+
else
|
311
|
+
pool.worker_not_old_enough(self)
|
312
|
+
end
|
313
|
+
|
314
|
+
when :stop
|
315
|
+
pool.remove_busy_worker(self)
|
316
|
+
throw :stop
|
317
|
+
|
318
|
+
else
|
319
|
+
task, args = message
|
320
|
+
run_task pool, task, args
|
321
|
+
last_message = Concurrent.monotonic_time
|
322
|
+
|
323
|
+
pool.ready_worker(self)
|
324
|
+
end
|
325
|
+
|
326
|
+
end
|
327
|
+
end
|
328
|
+
end
|
329
|
+
end
|
330
|
+
|
331
|
+
def run_task(pool, task, args)
|
332
|
+
task.call(*args)
|
333
|
+
rescue => ex
|
334
|
+
# let it fail
|
335
|
+
log DEBUG, ex
|
336
|
+
rescue Exception => ex
|
337
|
+
log ERROR, ex
|
338
|
+
pool.worker_died(self)
|
339
|
+
throw :stop
|
257
340
|
end
|
258
|
-
return wrkr
|
259
341
|
end
|
342
|
+
|
343
|
+
private_constant :Worker
|
260
344
|
end
|
261
345
|
end
|