3
3
require 'concurrent/concern/logging'
4
4
require 'concurrent/executor/ruby_executor_service'
5
5
require 'concurrent/utility/monotonic_time'
6
+ require 'concurrent/collection/timeout_queue'
6
7
7
8
module Concurrent
8
9
9
10
# @!macro thread_pool_executor
10
11
# @!macro thread_pool_options
11
12
# @!visibility private
12
13
class RubyThreadPoolExecutor < RubyExecutorService
14
+ include Concern ::Deprecation
13
15
14
16
# @!macro thread_pool_executor_constant_default_max_pool_size
15
17
DEFAULT_MAX_POOL_SIZE = 2_147_483_647 # java.lang.Integer::MAX_VALUE
@@ -94,9 +96,28 @@ def remaining_capacity
94
96
end
95
97
end
96
98
99
+ # removes the worker if it can be pruned
100
+ #
101
+ # @return [true, false] if the worker was pruned
102
+ #
97
103
# @!visibility private
98
- def remove_busy_worker ( worker )
99
- synchronize { ns_remove_busy_worker worker }
104
+ def prune_worker ( worker )
105
+ synchronize do
106
+ if ns_prunable_capacity > 0
107
+ remove_worker worker
108
+ true
109
+ else
110
+ false
111
+ end
112
+ end
113
+ end
114
+
115
+ # @!visibility private
116
+ def remove_worker ( worker )
117
+ synchronize do
118
+ ns_remove_ready_worker worker
119
+ ns_remove_busy_worker worker
120
+ end
100
121
end
101
122
102
123
# @!visibility private
@@ -116,7 +137,7 @@ def worker_task_completed
116
137
117
138
# @!macro thread_pool_executor_method_prune_pool
118
139
def prune_pool
119
- synchronize { ns_prune_pool }
140
+ deprecated "#prune_pool has no effect and will be removed in next the release, see https://github.com/ruby-concurrency/concurrent-ruby/pull/1082."
120
141
end
121
142
122
143
private
@@ -146,9 +167,6 @@ def ns_initialize(opts)
146
167
@largest_length = 0
147
168
@workers_counter = 0
148
169
@ruby_pid = $$ # detects if Ruby has forked
149
-
150
- @gc_interval = opts . fetch ( :gc_interval , @idletime / 2.0 ) . to_i # undocumented
151
- @next_gc_time = Concurrent . monotonic_time + @gc_interval
152
170
end
153
171
154
172
# @!visibility private
@@ -162,12 +180,10 @@ def ns_execute(*args, &task)
162
180
163
181
if ns_assign_worker ( *args , &task ) || ns_enqueue ( *args , &task )
164
182
@scheduled_task_count += 1
183
+ nil
165
184
else
166
- return fallback_action ( *args , &task )
185
+ fallback_action ( *args , &task )
167
186
end
168
-
169
- ns_prune_pool if @next_gc_time < Concurrent . monotonic_time
170
- nil
171
187
end
172
188
173
189
# @!visibility private
@@ -218,7 +234,7 @@ def ns_assign_worker(*args, &task)
218
234
# @!visibility private
219
235
def ns_enqueue ( *args , &task )
220
236
return false if @synchronous
221
-
237
+
222
238
if !ns_limited_queue? || @queue . size < @max_queue
223
239
@queue << [ task , args ]
224
240
true
@@ -265,7 +281,7 @@ def ns_ready_worker(worker, last_message, success = true)
265
281
end
266
282
end
267
283
268
- # removes a worker which is not in not tracked in @ready
284
+ # removes a worker which is not tracked in @ready
269
285
#
270
286
# @!visibility private
271
287
def ns_remove_busy_worker ( worker )
@@ -274,25 +290,27 @@ def ns_remove_busy_worker(worker)
274
290
true
275
291
end
276
292
277
- # try oldest worker if it is idle for enough time, it's returned back at the start
278
- #
279
293
# @!visibility private
280
- def ns_prune_pool
281
- now = Concurrent . monotonic_time
282
- stopped_workers = 0
283
- while !@ready . empty? && ( @pool . size - stopped_workers > @min_length )
284
- worker , last_message = @ready . first
285
- if now - last_message > self . idletime
286
- stopped_workers += 1
287
- @ready . shift
288
- worker << :stop
289
- else break
290
- end
294
+ def ns_remove_ready_worker ( worker )
295
+ if index = @ready . index { |rw , _ | rw == worker }
296
+ @ready . delete_at ( index )
291
297
end
298
+ true
299
+ end
292
300
293
- @next_gc_time = Concurrent . monotonic_time + @gc_interval
301
+ # @return [Integer] number of excess idle workers which can be removed without
302
+ # going below min_length, or all workers if not running
303
+ #
304
+ # @!visibility private
305
+ def ns_prunable_capacity
306
+ if running?
307
+ [ @pool . size - @min_length , @ready . size ] . min
308
+ else
309
+ @pool . size
310
+ end
294
311
end
295
312
313
+ # @!visibility private
296
314
def ns_reset_if_forked
297
315
if $$ != @ruby_pid
298
316
@queue . clear
@@ -312,7 +330,7 @@ class Worker
312
330
313
331
def initialize ( pool , id )
314
332
# instance variables accessed only under pool's lock so no need to sync here again
315
- @queue = Queue . new
333
+ @queue = Collection :: TimeoutQueue . new
316
334
@pool = pool
317
335
@thread = create_worker @queue , pool , pool . idletime
318
336
@@ -338,17 +356,22 @@ def kill
338
356
def create_worker ( queue , pool , idletime )
339
357
Thread . new ( queue , pool , idletime ) do |my_queue , my_pool , my_idletime |
340
358
catch ( :stop ) do
341
- loop do
359
+ prunable = true
342
360
343
- case message = my_queue . pop
361
+ loop do
362
+ timeout = prunable && my_pool . running? ? my_idletime : nil
363
+ case message = my_queue . pop ( timeout : timeout )
364
+ when nil
365
+ throw :stop if my_pool . prune_worker ( self )
366
+ prunable = false
344
367
when :stop
345
- my_pool . remove_busy_worker ( self )
368
+ my_pool . remove_worker ( self )
346
369
throw :stop
347
-
348
370
else
349
371
task , args = message
350
372
run_task my_pool , task , args
351
373
my_pool . ready_worker ( self , Concurrent . monotonic_time )
374
+ prunable = true
352
375
end
353
376
end
354
377
end
0 commit comments