1
+ require 'thread'
2
+
3
+ require 'concurrent/event'
4
+ require 'concurrent/ruby_thread_pool_executor/worker'
5
+
1
6
module Concurrent
2
7
3
8
# @!macro thread_pool_executor
@@ -21,45 +26,51 @@ class RubyThreadPoolExecutor
21
26
22
27
# The maximum number of threads that may be created in the pool.
23
28
attr_reader :max_length
29
+ attr_reader :min_length
30
+
31
+ attr_reader :largest_length
32
+
33
+ attr_reader :scheduled_task_count
34
+ attr_reader :completed_task_count
35
+
36
+ attr_reader :idletime
24
37
25
38
attr_reader :max_queue
26
39
27
40
# Create a new thread pool.
28
41
#
29
42
# @see http://docs.oracle.com/javase/7/docs/api/java/util/concurrent/ThreadPoolExecutor.html
30
43
def initialize ( opts = { } )
31
- min_length = opts . fetch ( :min_threads , DEFAULT_MIN_POOL_SIZE ) . to_i
44
+ @ min_length = opts . fetch ( :min_threads , DEFAULT_MIN_POOL_SIZE ) . to_i
32
45
@max_length = opts . fetch ( :max_threads , DEFAULT_MAX_POOL_SIZE ) . to_i
33
- idletime = opts . fetch ( :idletime , DEFAULT_THREAD_IDLETIMEOUT ) . to_i
46
+ @ idletime = opts . fetch ( :idletime , DEFAULT_THREAD_IDLETIMEOUT ) . to_i
34
47
@max_queue = opts . fetch ( :max_queue , DEFAULT_MAX_QUEUE_SIZE ) . to_i
35
- overflow_policy = opts . fetch ( :overflow_policy , :abort )
48
+ @ overflow_policy = opts . fetch ( :overflow_policy , :abort )
36
49
37
50
raise ArgumentError . new ( 'max_threads must be greater than zero' ) if @max_length <= 0
38
- raise ArgumentError . new ( "#{ overflow_policy } is not a valid overflow policy" ) unless OVERFLOW_POLICIES . include? ( overflow_policy )
39
- end
51
+ raise ArgumentError . new ( "#{ overflow_policy } is not a valid overflow policy" ) unless OVERFLOW_POLICIES . include? ( @overflow_policy )
40
52
41
- def min_length
42
- end
53
+ @state = :running
54
+ @pool = [ ]
55
+ @terminator = Event . new
56
+ @queue = Queue . new
57
+ @mutex = Mutex . new
58
+ @scheduled_task_count = 0
59
+ @completed_task_count = 0
60
+ @largest_length = 0
43
61
44
- def max_length
62
+ #@busy = 0
63
+ @prune_interval = 1
64
+ @last_prune_time = Time . now . to_i - ( @prune_interval * 2 )
45
65
end
46
66
47
67
def length
68
+ @mutex . synchronize do
69
+ @state != :shutdown ? @pool . length : 0
70
+ end
48
71
end
49
72
alias_method :current_length , :length
50
73
51
- def largest_length
52
- end
53
-
54
- def scheduled_task_count
55
- end
56
-
57
- def completed_task_count
58
- end
59
-
60
- def idletime
61
- end
62
-
63
74
def queue_length
64
75
end
65
76
@@ -70,18 +81,14 @@ def remaining_capacity
70
81
#
71
82
# @return [Boolean] +true+ when running, +false+ when shutting down or shutdown
72
83
def running?
84
+ @mutex . synchronize { @state == :running }
73
85
end
74
86
75
87
# Is the thread pool shutdown?
76
88
#
77
89
# @return [Boolean] +true+ when shutdown, +false+ when shutting down or running
78
90
def shutdown?
79
- end
80
-
81
- # Were all tasks completed before shutdown?
82
- #
83
- # @return [Boolean] +true+ if shutdown and all tasks completed else +false+
84
- def terminated?
91
+ @mutex . synchronize { @state != :running }
85
92
end
86
93
87
94
# Block until thread pool shutdown is complete or until +timeout+ seconds have
@@ -94,6 +101,7 @@ def terminated?
94
101
#
95
102
# @return [Boolean] +true+ if shutdown complete or false on +timeout+
96
103
def wait_for_termination ( timeout )
104
+ return @terminator . wait ( timeout . to_i )
97
105
end
98
106
99
107
# Submit a task to the thread pool for asynchronous processing.
@@ -106,7 +114,19 @@ def wait_for_termination(timeout)
106
114
# is not running
107
115
#
108
116
# @raise [ArgumentError] if no task is given
109
- def post ( *args )
117
+ def post ( *args , &task )
118
+ raise ArgumentError . new ( 'no block given' ) unless block_given?
119
+ @mutex . synchronize do
120
+ break false unless @state == :running
121
+ @scheduled_task_count += 1
122
+ @queue << [ args , task ]
123
+ #if Time.now.to_i - @prune_interval > @last_prune_time
124
+ prune_pool
125
+ grow_pool
126
+ #@last_prune_time = Time.now.to_i
127
+ #end
128
+ true
129
+ end
110
130
end
111
131
112
132
# Submit a task to the thread pool for asynchronous processing.
@@ -115,19 +135,106 @@ def post(*args)
115
135
#
116
136
# @return [self] returns itself
117
137
def <<( task )
138
+ self . post ( &task )
139
+ return self
118
140
end
119
141
120
142
# Begin an orderly shutdown. Tasks already in the queue will be executed,
121
143
# but no new tasks will be accepted. Has no additional effect if the
122
144
# thread pool is not running.
123
145
def shutdown
146
+ @mutex . synchronize do
147
+ break unless @state == :running
148
+ if @pool . empty?
149
+ @state = :shutdown
150
+ @terminator . set
151
+ else
152
+ @state = :shuttingdown
153
+ @pool . length . times { @queue << :stop }
154
+ end
155
+ end
124
156
end
125
157
126
158
# Begin an immediate shutdown. In-progress tasks will be allowed to
127
159
# complete but enqueued tasks will be dismissed and no new tasks
128
160
# will be accepted. Has no additional effect if the thread pool is
129
161
# not running.
130
162
def kill
163
+ @mutex . synchronize do
164
+ break if @state == :shutdown
165
+ @state = :shutdown
166
+ @queue . clear
167
+ drain_pool
168
+ @terminator . set
169
+ end
170
+ end
171
+
172
+ ## @!visibility private
173
+ #def on_start_task # :nodoc:
174
+ #@mutex.synchronize do
175
+ #@busy += 1
176
+ #end
177
+ #end
178
+
179
+ # @!visibility private
180
+ def on_end_task # :nodoc:
181
+ @mutex . synchronize do
182
+ #@busy -= 1
183
+ @completed_task_count += 1 #if success
184
+ break unless @state == :running
185
+ end
186
+ end
187
+
188
+ # @!visibility private
189
+ def on_worker_exit ( worker ) # :nodoc:
190
+ @mutex . synchronize do
191
+ @pool . delete ( worker )
192
+ if @pool . empty? && @state != :running
193
+ @state = :shutdown
194
+ @terminator . set
195
+ end
196
+ end
197
+ end
198
+
199
+ protected
200
+
201
+ # @!visibility private
202
+ def prune_pool # :nodoc:
203
+ @pool . delete_if do |worker |
204
+ worker . dead? ||
205
+ ( @idletime == 0 ? false : Time . now . to_i - @idletime > worker . last_activity )
206
+ end
207
+ end
208
+
209
+ # @!visibility private
210
+ def grow_pool # :nodoc:
211
+ if @min_length > @pool . length
212
+ additional = @min_length - @pool . length
213
+ elsif @pool . length < @max_length && ! @queue . empty?
214
+ # NOTE: does not take into account idle threads
215
+ additional = 1
216
+ else
217
+ additional = 0
218
+ end
219
+ additional . times { @pool << create_worker_thread }
220
+ @largest_length = [ @largest_length , @pool . length ] . max
221
+ end
222
+
223
+ # @!visibility private
224
+ def drain_pool # :nodoc:
225
+ @pool . each { |worker | worker . kill }
226
+ @pool . clear
227
+ end
228
+
229
+ # @!visibility private
230
+ def create_worker_thread # :nodoc:
231
+ wrkr = Worker . new ( @queue , self )
232
+ Thread . new ( wrkr , self ) do |worker , parent |
233
+ Thread . current . abort_on_exception = false
234
+ worker . run
235
+ parent . on_worker_exit ( worker )
236
+ end
237
+ return wrkr
131
238
end
132
239
end
133
240
end
0 commit comments