Skip to content

Commit 5e95791

Browse files
committed
Add thread reaping to thread pool
1 parent 6479e6b commit 5e95791

File tree

3 files changed

+99
-0
lines changed

3 files changed

+99
-0
lines changed

lib/puma/server.rb

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@ class Server
3939
attr_accessor :max_threads
4040
attr_accessor :persistent_timeout
4141
attr_accessor :auto_trim_time
42+
attr_accessor :reaping_time
4243
attr_accessor :first_data_timeout
4344

4445
# Create a server for the rack app +app+.
@@ -60,6 +61,7 @@ def initialize(app, events=Events.stdio, options={})
6061
@min_threads = 0
6162
@max_threads = 16
6263
@auto_trim_time = 1
64+
@reaping_time = 1
6365

6466
@thread = nil
6567
@thread_pool = nil
@@ -274,6 +276,10 @@ def run(background=true)
274276
@reactor.run_in_thread
275277
end
276278

279+
if @reaping_time
280+
@thread_pool.auto_reap!(@reaping_time)
281+
end
282+
277283
if @auto_trim_time
278284
@thread_pool.auto_trim!(@auto_trim_time)
279285
end

lib/puma/thread_pool.rb

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@ def initialize(min, max, *extra, &block)
3333
@workers = []
3434

3535
@auto_trim = nil
36+
@reaper = nil
3637

3738
@mutex.synchronize do
3839
@min.times { spawn_thread }
@@ -155,6 +156,21 @@ def trim(force=false)
155156
end
156157
end
157158

159+
# If there are dead threads in the pool make them go away while decreasing
160+
# spwaned counter so that new healty threads could be created again.
161+
def reap
162+
@mutex.synchronize do
163+
dead_workers = @workers.reject(&:alive?)
164+
165+
dead_workers.each do |worker|
166+
worker.kill
167+
@spawned -= 1
168+
end
169+
170+
@workers -= dead_workers
171+
end
172+
end
173+
158174
class AutoTrim
159175
def initialize(pool, timeout)
160176
@pool = pool
@@ -184,6 +200,35 @@ def auto_trim!(timeout=5)
184200
@auto_trim.start!
185201
end
186202

203+
class Reaper
204+
def initialize(pool, timeout)
205+
@pool = pool
206+
@timeout = timeout
207+
@running = false
208+
end
209+
210+
def start!
211+
@running = true
212+
213+
@thread = Thread.new do
214+
while @running
215+
@pool.reap
216+
sleep @timeout
217+
end
218+
end
219+
end
220+
221+
def stop
222+
@running = false
223+
@thread.wakeup
224+
end
225+
end
226+
227+
def auto_reap!(timeout=5)
228+
@reaper = Reaper.new(self, timeout)
229+
@reaper.start!
230+
end
231+
187232
# Tell all threads in the pool to exit and wait for them to finish.
188233
#
189234
def shutdown
@@ -193,6 +238,7 @@ def shutdown
193238
@not_full.broadcast
194239

195240
@auto_trim.stop if @auto_trim
241+
@reaper.stop if @reaper
196242
end
197243

198244
# Use this instead of #each so that we don't stop in the middle

test/test_thread_pool.rb

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -179,4 +179,51 @@ def test_cleanliness
179179

180180
assert_equal [], values.compact
181181
end
182+
183+
def test_reap_only_dead_threads
184+
pool = new_pool(2,2) { Thread.current.kill }
185+
186+
assert_equal 2, pool.spawned
187+
188+
pool << 1
189+
190+
pause
191+
192+
assert_equal 2, pool.spawned
193+
194+
pool.reap
195+
196+
assert_equal 1, pool.spawned
197+
198+
pool << 2
199+
200+
pause
201+
202+
assert_equal 1, pool.spawned
203+
204+
pool.reap
205+
206+
assert_equal 0, pool.spawned
207+
end
208+
209+
def test_auto_reap_dead_threads
210+
pool = new_pool(2,2) { Thread.current.kill }
211+
212+
assert_equal 2, pool.spawned
213+
214+
pool << 1
215+
pool << 2
216+
217+
pause
218+
219+
assert_equal 2, pool.spawned
220+
221+
pool.auto_reap! 1
222+
223+
sleep 1
224+
225+
pause
226+
227+
assert_equal 0, pool.spawned
228+
end
182229
end

0 commit comments

Comments
 (0)