concurrent-ruby 0.5.0 → 0.6.0.pre.1
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/README.md +88 -77
- data/lib/concurrent.rb +17 -2
- data/lib/concurrent/actor.rb +17 -0
- data/lib/concurrent/actor_context.rb +31 -0
- data/lib/concurrent/actor_ref.rb +39 -0
- data/lib/concurrent/agent.rb +12 -3
- data/lib/concurrent/async.rb +290 -0
- data/lib/concurrent/atomic.rb +5 -9
- data/lib/concurrent/cached_thread_pool.rb +39 -137
- data/lib/concurrent/channel/blocking_ring_buffer.rb +60 -0
- data/lib/concurrent/channel/buffered_channel.rb +83 -0
- data/lib/concurrent/channel/channel.rb +11 -0
- data/lib/concurrent/channel/probe.rb +19 -0
- data/lib/concurrent/channel/ring_buffer.rb +54 -0
- data/lib/concurrent/channel/unbuffered_channel.rb +34 -0
- data/lib/concurrent/channel/waitable_list.rb +38 -0
- data/lib/concurrent/configuration.rb +92 -0
- data/lib/concurrent/dataflow.rb +9 -3
- data/lib/concurrent/delay.rb +88 -0
- data/lib/concurrent/exchanger.rb +31 -0
- data/lib/concurrent/fixed_thread_pool.rb +28 -122
- data/lib/concurrent/future.rb +10 -5
- data/lib/concurrent/immediate_executor.rb +3 -2
- data/lib/concurrent/ivar.rb +2 -1
- data/lib/concurrent/java_cached_thread_pool.rb +45 -0
- data/lib/concurrent/java_fixed_thread_pool.rb +37 -0
- data/lib/concurrent/java_thread_pool_executor.rb +194 -0
- data/lib/concurrent/per_thread_executor.rb +23 -0
- data/lib/concurrent/postable.rb +2 -0
- data/lib/concurrent/processor_count.rb +125 -0
- data/lib/concurrent/promise.rb +42 -18
- data/lib/concurrent/ruby_cached_thread_pool.rb +37 -0
- data/lib/concurrent/ruby_fixed_thread_pool.rb +31 -0
- data/lib/concurrent/ruby_thread_pool_executor.rb +268 -0
- data/lib/concurrent/ruby_thread_pool_worker.rb +69 -0
- data/lib/concurrent/simple_actor_ref.rb +124 -0
- data/lib/concurrent/thread_local_var.rb +1 -1
- data/lib/concurrent/thread_pool_executor.rb +30 -0
- data/lib/concurrent/timer_task.rb +13 -10
- data/lib/concurrent/tvar.rb +212 -0
- data/lib/concurrent/utilities.rb +1 -0
- data/lib/concurrent/version.rb +1 -1
- data/spec/concurrent/actor_context_spec.rb +37 -0
- data/spec/concurrent/actor_ref_shared.rb +313 -0
- data/spec/concurrent/actor_spec.rb +9 -1
- data/spec/concurrent/agent_spec.rb +97 -96
- data/spec/concurrent/async_spec.rb +320 -0
- data/spec/concurrent/cached_thread_pool_shared.rb +137 -0
- data/spec/concurrent/channel/blocking_ring_buffer_spec.rb +149 -0
- data/spec/concurrent/channel/buffered_channel_spec.rb +151 -0
- data/spec/concurrent/channel/channel_spec.rb +37 -0
- data/spec/concurrent/channel/probe_spec.rb +49 -0
- data/spec/concurrent/channel/ring_buffer_spec.rb +126 -0
- data/spec/concurrent/channel/unbuffered_channel_spec.rb +132 -0
- data/spec/concurrent/configuration_spec.rb +134 -0
- data/spec/concurrent/dataflow_spec.rb +109 -27
- data/spec/concurrent/delay_spec.rb +77 -0
- data/spec/concurrent/exchanger_spec.rb +66 -0
- data/spec/concurrent/fixed_thread_pool_shared.rb +136 -0
- data/spec/concurrent/future_spec.rb +60 -51
- data/spec/concurrent/global_thread_pool_shared.rb +33 -0
- data/spec/concurrent/immediate_executor_spec.rb +4 -25
- data/spec/concurrent/ivar_spec.rb +36 -23
- data/spec/concurrent/java_cached_thread_pool_spec.rb +64 -0
- data/spec/concurrent/java_fixed_thread_pool_spec.rb +64 -0
- data/spec/concurrent/java_thread_pool_executor_spec.rb +71 -0
- data/spec/concurrent/obligation_shared.rb +32 -20
- data/spec/concurrent/{global_thread_pool_spec.rb → per_thread_executor_spec.rb} +9 -13
- data/spec/concurrent/processor_count_spec.rb +20 -0
- data/spec/concurrent/promise_spec.rb +29 -41
- data/spec/concurrent/ruby_cached_thread_pool_spec.rb +69 -0
- data/spec/concurrent/ruby_fixed_thread_pool_spec.rb +39 -0
- data/spec/concurrent/ruby_thread_pool_executor_spec.rb +183 -0
- data/spec/concurrent/simple_actor_ref_spec.rb +219 -0
- data/spec/concurrent/thread_pool_class_cast_spec.rb +40 -0
- data/spec/concurrent/thread_pool_executor_shared.rb +155 -0
- data/spec/concurrent/thread_pool_shared.rb +98 -36
- data/spec/concurrent/tvar_spec.rb +137 -0
- data/spec/spec_helper.rb +4 -0
- data/spec/support/functions.rb +4 -0
- metadata +85 -20
- data/lib/concurrent/cached_thread_pool/worker.rb +0 -91
- data/lib/concurrent/channel.rb +0 -63
- data/lib/concurrent/fixed_thread_pool/worker.rb +0 -54
- data/lib/concurrent/global_thread_pool.rb +0 -42
- data/spec/concurrent/cached_thread_pool_spec.rb +0 -101
- data/spec/concurrent/channel_spec.rb +0 -86
- data/spec/concurrent/fixed_thread_pool_spec.rb +0 -92
- data/spec/concurrent/uses_global_thread_pool_shared.rb +0 -64
@@ -0,0 +1,31 @@
|
|
1
|
+
module Concurrent
|
2
|
+
class Exchanger
|
3
|
+
|
4
|
+
EMPTY = Object.new
|
5
|
+
|
6
|
+
def initialize(opts = {})
|
7
|
+
@first = MVar.new(EMPTY, opts)
|
8
|
+
@second = MVar.new(MVar::EMPTY, opts)
|
9
|
+
end
|
10
|
+
|
11
|
+
def exchange(value, timeout = nil)
|
12
|
+
first = @first.take(timeout)
|
13
|
+
if first == MVar::TIMEOUT
|
14
|
+
nil
|
15
|
+
elsif first == EMPTY
|
16
|
+
@first.put value
|
17
|
+
second = @second.take timeout
|
18
|
+
if second == MVar::TIMEOUT
|
19
|
+
nil
|
20
|
+
else
|
21
|
+
second
|
22
|
+
end
|
23
|
+
else
|
24
|
+
@first.put EMPTY
|
25
|
+
@second.put value
|
26
|
+
first
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
end
|
31
|
+
end
|
@@ -1,128 +1,34 @@
|
|
1
|
-
require '
|
2
|
-
|
3
|
-
require 'concurrent/event'
|
4
|
-
require 'concurrent/fixed_thread_pool/worker'
|
1
|
+
require 'concurrent/ruby_fixed_thread_pool'
|
5
2
|
|
6
3
|
module Concurrent
|
7
4
|
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
def post(*args, &block)
|
37
|
-
raise ArgumentError.new('no block given') if block.nil?
|
38
|
-
@mutex.synchronize do
|
39
|
-
break false unless @state == :running
|
40
|
-
@queue << [args, block]
|
41
|
-
clean_pool
|
42
|
-
fill_pool
|
43
|
-
true
|
44
|
-
end
|
45
|
-
end
|
46
|
-
|
47
|
-
def <<(block)
|
48
|
-
self.post(&block)
|
49
|
-
return self
|
50
|
-
end
|
51
|
-
|
52
|
-
def shutdown
|
53
|
-
@mutex.synchronize do
|
54
|
-
break unless @state == :running
|
55
|
-
if @pool.empty?
|
56
|
-
@state = :shutdown
|
57
|
-
@terminator.set
|
58
|
-
else
|
59
|
-
@state = :shuttingdown
|
60
|
-
@pool.length.times{ @queue << :stop }
|
61
|
-
end
|
62
|
-
end
|
63
|
-
end
|
64
|
-
|
65
|
-
def kill
|
66
|
-
@mutex.synchronize do
|
67
|
-
break if @state == :shutdown
|
68
|
-
@state = :shutdown
|
69
|
-
@queue.clear
|
70
|
-
drain_pool
|
71
|
-
@terminator.set
|
72
|
-
end
|
73
|
-
end
|
74
|
-
|
75
|
-
def length
|
76
|
-
@mutex.synchronize do
|
77
|
-
@state == :running ? @pool.length : 0
|
78
|
-
end
|
79
|
-
end
|
80
|
-
|
81
|
-
def create_worker_thread
|
82
|
-
wrkr = Worker.new(@queue, self)
|
83
|
-
Thread.new(wrkr, self) do |worker, parent|
|
84
|
-
Thread.current.abort_on_exception = false
|
85
|
-
worker.run
|
86
|
-
parent.on_worker_exit(worker)
|
87
|
-
end
|
88
|
-
return wrkr
|
89
|
-
end
|
90
|
-
|
91
|
-
def fill_pool
|
92
|
-
return unless @state == :running
|
93
|
-
while @pool.length < @max_threads
|
94
|
-
@pool << create_worker_thread
|
95
|
-
end
|
96
|
-
end
|
97
|
-
|
98
|
-
def clean_pool
|
99
|
-
@pool.reject! {|worker| worker.dead? }
|
100
|
-
end
|
101
|
-
|
102
|
-
def drain_pool
|
103
|
-
@pool.each {|worker| worker.kill }
|
104
|
-
@pool.clear
|
105
|
-
end
|
106
|
-
|
107
|
-
def on_start_task(worker)
|
108
|
-
end
|
109
|
-
|
110
|
-
def on_end_task(worker)
|
111
|
-
@mutex.synchronize do
|
112
|
-
break unless @state == :running
|
113
|
-
clean_pool
|
114
|
-
fill_pool
|
115
|
-
end
|
116
|
-
end
|
117
|
-
|
118
|
-
def on_worker_exit(worker)
|
119
|
-
@mutex.synchronize do
|
120
|
-
@pool.delete(worker)
|
121
|
-
if @pool.empty? && @state != :running
|
122
|
-
@state = :shutdown
|
123
|
-
@terminator.set
|
124
|
-
end
|
125
|
-
end
|
5
|
+
if RUBY_PLATFORM == 'java'
|
6
|
+
require 'concurrent/java_fixed_thread_pool'
|
7
|
+
# @!macro [attach] fixed_thread_pool
|
8
|
+
#
|
9
|
+
# A thread pool with a set number of threads. The number of threads in the pool
|
10
|
+
# is set on construction and remains constant. When all threads are busy new
|
11
|
+
# tasks +#post+ to the thread pool are enqueued until a thread becomes available.
|
12
|
+
# Should a thread crash for any reason the thread will immediately be removed
|
13
|
+
# from the pool and replaced.
|
14
|
+
#
|
15
|
+
# The API and behavior of this class are based on Java's +FixedThreadPool+
|
16
|
+
#
|
17
|
+
# @note When running on the JVM (JRuby) this class will inherit from +JavaFixedThreadPool+.
|
18
|
+
# On all other platforms it will inherit from +RubyFixedThreadPool+.
|
19
|
+
#
|
20
|
+
# @see Concurrent::RubyFixedThreadPool
|
21
|
+
# @see Concurrent::JavaFixedThreadPool
|
22
|
+
#
|
23
|
+
# @see http://docs.oracle.com/javase/tutorial/essential/concurrency/pools.html
|
24
|
+
# @see http://docs.oracle.com/javase/7/docs/api/java/util/concurrent/Executors.html
|
25
|
+
# @see http://docs.oracle.com/javase/8/docs/api/java/util/concurrent/ExecutorService.html
|
26
|
+
# @see http://stackoverflow.com/questions/17957382/fixedthreadpool-vs-fixedthreadpool-the-lesser-of-two-evils
|
27
|
+
class FixedThreadPool < JavaFixedThreadPool
|
28
|
+
end
|
29
|
+
else
|
30
|
+
# @!macro fixed_thread_pool
|
31
|
+
class FixedThreadPool < RubyFixedThreadPool
|
126
32
|
end
|
127
33
|
end
|
128
34
|
end
|
data/lib/concurrent/future.rb
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
require 'thread'
|
2
2
|
|
3
|
+
require 'concurrent/configuration'
|
3
4
|
require 'concurrent/obligation'
|
4
|
-
require 'concurrent/global_thread_pool'
|
5
5
|
require 'concurrent/safe_task_executor'
|
6
6
|
|
7
7
|
module Concurrent
|
@@ -41,13 +41,18 @@ module Concurrent
|
|
41
41
|
# @see http://docs.oracle.com/javase/7/docs/api/java/util/concurrent/Future.html java.util.concurrent.Future
|
42
42
|
class Future < IVar
|
43
43
|
include Obligation
|
44
|
-
include
|
44
|
+
include OptionsParser
|
45
45
|
|
46
46
|
# Create a new +Future+ in the +:unscheduled+ state.
|
47
47
|
#
|
48
48
|
# @yield the asynchronous operation to perform
|
49
49
|
#
|
50
50
|
# @param [Hash] opts the options to create a message with
|
51
|
+
# @option opts [Boolean] :operation (false) when +true+ will execute the future on the global
|
52
|
+
# operation pool (for long-running operations), when +false+ will execute the future on the
|
53
|
+
# global task pool (for short-running tasks)
|
54
|
+
# @option opts [object] :executor when provided will run all operations on
|
55
|
+
# this executor rather than the global thread pool (overrides :operation)
|
51
56
|
# @option opts [String] :dup_on_deref (false) call +#dup+ before returning the data
|
52
57
|
# @option opts [String] :freeze_on_deref (false) call +#freeze+ before returning the data
|
53
58
|
# @option opts [String] :copy_on_deref (nil) call the given +Proc+ passing the internal value and
|
@@ -59,6 +64,7 @@ module Concurrent
|
|
59
64
|
super(IVar::NO_VALUE, opts)
|
60
65
|
@state = :unscheduled
|
61
66
|
@task = block
|
67
|
+
@executor = get_executor_from(opts)
|
62
68
|
end
|
63
69
|
|
64
70
|
# Execute an +:unscheduled+ +Future+. Immediately sets the state to +:pending+ and
|
@@ -80,7 +86,7 @@ module Concurrent
|
|
80
86
|
# @since 0.5.0
|
81
87
|
def execute
|
82
88
|
if compare_and_set_state(:pending, :unscheduled)
|
83
|
-
|
89
|
+
@executor.post{ work }
|
84
90
|
self
|
85
91
|
end
|
86
92
|
end
|
@@ -105,7 +111,7 @@ module Concurrent
|
|
105
111
|
#
|
106
112
|
# @since 0.5.0
|
107
113
|
def self.execute(opts = {}, &block)
|
108
|
-
|
114
|
+
Future.new(opts, &block).execute
|
109
115
|
end
|
110
116
|
|
111
117
|
protected :set, :fail, :complete
|
@@ -117,6 +123,5 @@ module Concurrent
|
|
117
123
|
success, val, reason = SafeTaskExecutor.new(@task).execute
|
118
124
|
complete(success, val, reason)
|
119
125
|
end
|
120
|
-
|
121
126
|
end
|
122
127
|
end
|
@@ -2,13 +2,14 @@ module Concurrent
|
|
2
2
|
class ImmediateExecutor
|
3
3
|
|
4
4
|
def post(*args, &block)
|
5
|
+
raise ArgumentError.new('no block given') unless block_given?
|
5
6
|
block.call(*args)
|
7
|
+
return true
|
6
8
|
end
|
7
9
|
|
8
10
|
def <<(block)
|
9
11
|
post(&block)
|
10
12
|
self
|
11
13
|
end
|
12
|
-
|
13
14
|
end
|
14
|
-
end
|
15
|
+
end
|
data/lib/concurrent/ivar.rb
CHANGED
@@ -60,7 +60,7 @@ module Concurrent
|
|
60
60
|
complete(true, value, nil)
|
61
61
|
end
|
62
62
|
|
63
|
-
def fail(reason =
|
63
|
+
def fail(reason = StandardError.new)
|
64
64
|
complete(false, nil, reason)
|
65
65
|
end
|
66
66
|
|
@@ -73,6 +73,7 @@ module Concurrent
|
|
73
73
|
|
74
74
|
time = Time.now
|
75
75
|
@observers.notify_and_delete_observers{ [time, self.value, reason] }
|
76
|
+
self
|
76
77
|
end
|
77
78
|
end
|
78
79
|
end
|
@@ -0,0 +1,45 @@
|
|
1
|
+
if RUBY_PLATFORM == 'java'
|
2
|
+
|
3
|
+
require 'concurrent/java_thread_pool_executor'
|
4
|
+
|
5
|
+
module Concurrent
|
6
|
+
|
7
|
+
# @!macro cached_thread_pool
|
8
|
+
class JavaCachedThreadPool < JavaThreadPoolExecutor
|
9
|
+
|
10
|
+
# Create a new thread pool.
|
11
|
+
#
|
12
|
+
# @param [Hash] opts the options defining pool behavior.
|
13
|
+
# @option opts [Integer] :max_threads (+DEFAULT_MAX_POOL_SIZE+) maximum number
|
14
|
+
# of threads which may be created in the pool
|
15
|
+
# @option opts [Integer] :idletime (+DEFAULT_THREAD_IDLETIMEOUT+) maximum
|
16
|
+
# number of seconds a thread may be idle before it is reclaimed
|
17
|
+
# @option opts [Symbol] :overflow_policy (+:abort+) the overflow policy
|
18
|
+
#
|
19
|
+
# @raise [ArgumentError] if +max_threads+ is less than or equal to zero
|
20
|
+
# @raise [ArgumentError] if +idletime+ is less than or equal to zero
|
21
|
+
# @raise [ArgumentError] if +overflow_policy+ is not a known policy
|
22
|
+
#
|
23
|
+
# @see http://docs.oracle.com/javase/8/docs/api/java/util/concurrent/Executors.html#newCachedThreadPool--
|
24
|
+
def initialize(opts = {})
|
25
|
+
max_length = opts.fetch(:max_threads, DEFAULT_MAX_POOL_SIZE).to_i
|
26
|
+
idletime = opts.fetch(:idletime, DEFAULT_THREAD_IDLETIMEOUT).to_i
|
27
|
+
@overflow_policy = opts.fetch(:overflow_policy, :abort)
|
28
|
+
@max_queue = 0
|
29
|
+
|
30
|
+
raise ArgumentError.new('idletime must be greater than zero') if idletime <= 0
|
31
|
+
raise ArgumentError.new('max_threads must be greater than zero') if max_length <= 0
|
32
|
+
raise ArgumentError.new("#{@overflow_policy} is not a valid overflow policy") unless OVERFLOW_POLICIES.keys.include?(@overflow_policy)
|
33
|
+
|
34
|
+
@executor = java.util.concurrent.ThreadPoolExecutor.new(
|
35
|
+
@max_queue, max_length,
|
36
|
+
idletime, java.util.concurrent.TimeUnit::SECONDS,
|
37
|
+
java.util.concurrent.SynchronousQueue.new,
|
38
|
+
OVERFLOW_POLICIES[@overflow_policy].new)
|
39
|
+
|
40
|
+
# without this the process may fail to exit
|
41
|
+
at_exit { self.kill }
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
@@ -0,0 +1,37 @@
|
|
1
|
+
if RUBY_PLATFORM == 'java'
|
2
|
+
|
3
|
+
require 'concurrent/java_thread_pool_executor'
|
4
|
+
|
5
|
+
module Concurrent
|
6
|
+
|
7
|
+
# @!macro fixed_thread_pool
|
8
|
+
class JavaFixedThreadPool < JavaThreadPoolExecutor
|
9
|
+
|
10
|
+
# Create a new thread pool.
|
11
|
+
#
|
12
|
+
# @param [Hash] opts the options defining pool behavior.
|
13
|
+
# @option opts [Symbol] :overflow_policy (+:abort+) the overflow policy
|
14
|
+
#
|
15
|
+
# @raise [ArgumentError] if +num_threads+ is less than or equal to zero
|
16
|
+
# @raise [ArgumentError] if +overflow_policy+ is not a known policy
|
17
|
+
#
|
18
|
+
# @see http://docs.oracle.com/javase/8/docs/api/java/util/concurrent/Executors.html#newFixedThreadPool-int-
|
19
|
+
def initialize(num_threads, opts = {})
|
20
|
+
@overflow_policy = opts.fetch(:overflow_policy, :abort)
|
21
|
+
@max_queue = 0
|
22
|
+
|
23
|
+
raise ArgumentError.new('number of threads must be greater than zero') if num_threads < 1
|
24
|
+
raise ArgumentError.new("#{@overflow_policy} is not a valid overflow policy") unless OVERFLOW_POLICIES.keys.include?(@overflow_policy)
|
25
|
+
|
26
|
+
@executor = java.util.concurrent.ThreadPoolExecutor.new(
|
27
|
+
num_threads, num_threads,
|
28
|
+
@max_queue, java.util.concurrent.TimeUnit::SECONDS,
|
29
|
+
java.util.concurrent.LinkedBlockingQueue.new,
|
30
|
+
OVERFLOW_POLICIES[@overflow_policy].new)
|
31
|
+
|
32
|
+
# without this the process may fail to exit
|
33
|
+
at_exit { self.kill }
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
@@ -0,0 +1,194 @@
|
|
1
|
+
if RUBY_PLATFORM == 'java'
|
2
|
+
|
3
|
+
module Concurrent
|
4
|
+
|
5
|
+
RejectedExecutionError = Class.new(StandardError) unless defined? RejectedExecutionError
|
6
|
+
|
7
|
+
# @!macro thread_pool_executor
|
8
|
+
class JavaThreadPoolExecutor
|
9
|
+
|
10
|
+
# The maximum number of threads that will be created in the pool
|
11
|
+
# (unless overridden during construction).
|
12
|
+
DEFAULT_MAX_POOL_SIZE = java.lang.Integer::MAX_VALUE # 2147483647
|
13
|
+
|
14
|
+
# The minimum number of threads that will be created in the pool
|
15
|
+
# (unless overridden during construction).
|
16
|
+
DEFAULT_MIN_POOL_SIZE = 0
|
17
|
+
|
18
|
+
DEFAULT_MAX_QUEUE_SIZE = 0
|
19
|
+
|
20
|
+
# The maximum number of seconds a thread in the pool may remain idle before
|
21
|
+
# being reclaimed (unless overridden during construction).
|
22
|
+
DEFAULT_THREAD_IDLETIMEOUT = 60
|
23
|
+
|
24
|
+
OVERFLOW_POLICIES = {
|
25
|
+
abort: java.util.concurrent.ThreadPoolExecutor::AbortPolicy,
|
26
|
+
discard: java.util.concurrent.ThreadPoolExecutor::DiscardPolicy,
|
27
|
+
caller_runs: java.util.concurrent.ThreadPoolExecutor::CallerRunsPolicy
|
28
|
+
}.freeze
|
29
|
+
|
30
|
+
# The maximum number of threads that may be created in the pool.
|
31
|
+
attr_reader :max_length
|
32
|
+
|
33
|
+
attr_reader :max_queue
|
34
|
+
|
35
|
+
attr_reader :overflow_policy
|
36
|
+
|
37
|
+
# Create a new thread pool.
|
38
|
+
#
|
39
|
+
# @see http://docs.oracle.com/javase/7/docs/api/java/util/concurrent/ThreadPoolExecutor.html
|
40
|
+
def initialize(opts = {})
|
41
|
+
min_length = opts.fetch(:min_threads, DEFAULT_MIN_POOL_SIZE).to_i
|
42
|
+
max_length = opts.fetch(:max_threads, DEFAULT_MAX_POOL_SIZE).to_i
|
43
|
+
idletime = opts.fetch(:idletime, DEFAULT_THREAD_IDLETIMEOUT).to_i
|
44
|
+
@max_queue = opts.fetch(:max_queue, DEFAULT_MAX_QUEUE_SIZE).to_i
|
45
|
+
@overflow_policy = opts.fetch(:overflow_policy, :abort)
|
46
|
+
|
47
|
+
raise ArgumentError.new('max_threads must be greater than zero') if max_length <= 0
|
48
|
+
raise ArgumentError.new('min_threads cannot be less than zero') if min_length < 0
|
49
|
+
raise ArgumentError.new("#{@overflow_policy} is not a valid overflow policy") unless OVERFLOW_POLICIES.keys.include?(@overflow_policy)
|
50
|
+
|
51
|
+
if min_length == 0 && @max_queue == 0
|
52
|
+
queue = java.util.concurrent.SynchronousQueue.new
|
53
|
+
elsif @max_queue == 0
|
54
|
+
queue = java.util.concurrent.LinkedBlockingQueue.new
|
55
|
+
else
|
56
|
+
queue = java.util.concurrent.LinkedBlockingQueue.new(@max_queue)
|
57
|
+
end
|
58
|
+
|
59
|
+
@executor = java.util.concurrent.ThreadPoolExecutor.new(
|
60
|
+
min_length, max_length,
|
61
|
+
idletime, java.util.concurrent.TimeUnit::SECONDS,
|
62
|
+
queue, OVERFLOW_POLICIES[@overflow_policy].new)
|
63
|
+
|
64
|
+
# without this the process may fail to exit
|
65
|
+
at_exit { self.kill }
|
66
|
+
end
|
67
|
+
|
68
|
+
def min_length
|
69
|
+
@executor.getCorePoolSize
|
70
|
+
end
|
71
|
+
|
72
|
+
def max_length
|
73
|
+
@executor.getMaximumPoolSize
|
74
|
+
end
|
75
|
+
|
76
|
+
def length
|
77
|
+
@executor.getPoolSize
|
78
|
+
end
|
79
|
+
alias_method :current_length, :length
|
80
|
+
|
81
|
+
def largest_length
|
82
|
+
@executor.getLargestPoolSize
|
83
|
+
end
|
84
|
+
|
85
|
+
def scheduled_task_count
|
86
|
+
@executor.getTaskCount
|
87
|
+
end
|
88
|
+
|
89
|
+
def completed_task_count
|
90
|
+
@executor.getCompletedTaskCount
|
91
|
+
end
|
92
|
+
|
93
|
+
def idletime
|
94
|
+
@executor.getKeepAliveTime(java.util.concurrent.TimeUnit::SECONDS)
|
95
|
+
end
|
96
|
+
|
97
|
+
def queue_length
|
98
|
+
@executor.getQueue.size
|
99
|
+
end
|
100
|
+
|
101
|
+
def remaining_capacity
|
102
|
+
@max_queue == 0 ? -1 : @executor.getQueue.remainingCapacity
|
103
|
+
end
|
104
|
+
|
105
|
+
# This method is deprecated and will be removed soon.
|
106
|
+
# This method is supost to return the threads status, but Java API doesn't
|
107
|
+
# provide a way to get the thread status. So we return an empty Array instead.
|
108
|
+
def status
|
109
|
+
warn '[DEPRECATED] `status` is deprecated and will be removed soon.'
|
110
|
+
warn "Calls to `status` return an empty Array. Java ThreadPoolExecutor does not provide thread's status."
|
111
|
+
[]
|
112
|
+
end
|
113
|
+
|
114
|
+
# Is the thread pool running?
|
115
|
+
#
|
116
|
+
# @return [Boolean] +true+ when running, +false+ when shutting down or shutdown
|
117
|
+
def running?
|
118
|
+
! (@executor.isShutdown || @executor.isTerminated || @executor.isTerminating)
|
119
|
+
end
|
120
|
+
|
121
|
+
# Is the thread pool shutdown?
|
122
|
+
#
|
123
|
+
# @return [Boolean] +true+ when shutdown, +false+ when shutting down or running
|
124
|
+
def shutdown?
|
125
|
+
@executor.isShutdown
|
126
|
+
end
|
127
|
+
|
128
|
+
# Block until thread pool shutdown is complete or until +timeout+ seconds have
|
129
|
+
# passed.
|
130
|
+
#
|
131
|
+
# @note Does not initiate shutdown or termination. Either +shutdown+ or +kill+
|
132
|
+
# must be called before this method (or on another thread).
|
133
|
+
#
|
134
|
+
# @param [Integer] timeout the maximum number of seconds to wait for shutdown to complete
|
135
|
+
#
|
136
|
+
# @return [Boolean] +true+ if shutdown complete or false on +timeout+
|
137
|
+
def wait_for_termination(timeout)
|
138
|
+
@executor.awaitTermination(timeout.to_i, java.util.concurrent.TimeUnit::SECONDS)
|
139
|
+
end
|
140
|
+
|
141
|
+
# Submit a task to the thread pool for asynchronous processing.
|
142
|
+
#
|
143
|
+
# @param [Array] args zero or more arguments to be passed to the task
|
144
|
+
#
|
145
|
+
# @yield the asynchronous task to perform
|
146
|
+
#
|
147
|
+
# @return [Boolean] +true+ if the task is queued, +false+ if the thread pool
|
148
|
+
# is not running
|
149
|
+
#
|
150
|
+
# @raise [ArgumentError] if no task is given
|
151
|
+
def post(*args)
|
152
|
+
raise ArgumentError.new('no block given') unless block_given?
|
153
|
+
if running?
|
154
|
+
@executor.submit{ yield(*args) }
|
155
|
+
true
|
156
|
+
else
|
157
|
+
false
|
158
|
+
end
|
159
|
+
rescue Java::JavaUtilConcurrent::RejectedExecutionException => ex
|
160
|
+
raise RejectedExecutionError
|
161
|
+
end
|
162
|
+
|
163
|
+
# Submit a task to the thread pool for asynchronous processing.
|
164
|
+
#
|
165
|
+
# @param [Proc] task the asynchronous task to perform
|
166
|
+
#
|
167
|
+
# @return [self] returns itself
|
168
|
+
def <<(task)
|
169
|
+
@executor.submit(&task)
|
170
|
+
rescue Java::JavaUtilConcurrent::RejectedExecutionException => ex
|
171
|
+
raise RejectedExecutionError
|
172
|
+
end
|
173
|
+
|
174
|
+
# Begin an orderly shutdown. Tasks already in the queue will be executed,
|
175
|
+
# but no new tasks will be accepted. Has no additional effect if the
|
176
|
+
# thread pool is not running.
|
177
|
+
def shutdown
|
178
|
+
@executor.shutdown
|
179
|
+
@executor.getQueue.clear
|
180
|
+
return nil
|
181
|
+
end
|
182
|
+
|
183
|
+
# Begin an immediate shutdown. In-progress tasks will be allowed to
|
184
|
+
# complete but enqueued tasks will be dismissed and no new tasks
|
185
|
+
# will be accepted. Has no additional effect if the thread pool is
|
186
|
+
# not running.
|
187
|
+
def kill
|
188
|
+
@executor.shutdownNow
|
189
|
+
@executor.getQueue.clear
|
190
|
+
return nil
|
191
|
+
end
|
192
|
+
end
|
193
|
+
end
|
194
|
+
end
|