mirror of
https://github.com/puma/puma.git
synced 2022-11-09 13:48:40 -05:00
Rearch how trimming takes place to not starve. Fixes #39
Using the work queue to communicate trimming doesn't work, it's far too easy to starve the system doing that. Instead we now detect trimming and work as seperate actions.
This commit is contained in:
parent
c2e6206e59
commit
256970e048
2 changed files with 90 additions and 33 deletions
|
@ -12,21 +12,29 @@ module Puma
|
||||||
# thread.
|
# thread.
|
||||||
#
|
#
|
||||||
def initialize(min, max, &blk)
|
def initialize(min, max, &blk)
|
||||||
@todo = Queue.new
|
@cond = ConditionVariable.new
|
||||||
@mutex = Mutex.new
|
@mutex = Mutex.new
|
||||||
|
|
||||||
|
@todo = []
|
||||||
|
|
||||||
@spawned = 0
|
@spawned = 0
|
||||||
|
@waiting = 0
|
||||||
|
|
||||||
@min = min
|
@min = min
|
||||||
@max = max
|
@max = max
|
||||||
@block = blk
|
@block = blk
|
||||||
|
|
||||||
|
@shutdown = false
|
||||||
|
|
||||||
@trim_requested = 0
|
@trim_requested = 0
|
||||||
|
|
||||||
@workers = []
|
@workers = []
|
||||||
|
|
||||||
@auto_trim = nil
|
@auto_trim = nil
|
||||||
|
|
||||||
min.times { spawn_thread }
|
@mutex.synchronize do
|
||||||
|
min.times { spawn_thread }
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
attr_reader :spawned
|
attr_reader :spawned
|
||||||
|
@ -34,37 +42,54 @@ module Puma
|
||||||
# How many objects have yet to be processed by the pool?
|
# How many objects have yet to be processed by the pool?
|
||||||
#
|
#
|
||||||
def backlog
|
def backlog
|
||||||
@todo.size
|
@mutex.synchronize { @todo.size }
|
||||||
end
|
end
|
||||||
|
|
||||||
Stop = Object.new
|
|
||||||
Trim = Object.new
|
|
||||||
|
|
||||||
# :nodoc:
|
# :nodoc:
|
||||||
|
#
|
||||||
|
# Must be called with @mutex held!
|
||||||
|
#
|
||||||
def spawn_thread
|
def spawn_thread
|
||||||
@mutex.synchronize do
|
@spawned += 1
|
||||||
@spawned += 1
|
|
||||||
end
|
|
||||||
|
|
||||||
th = Thread.new do
|
th = Thread.new do
|
||||||
todo = @todo
|
todo = @todo
|
||||||
block = @block
|
block = @block
|
||||||
|
|
||||||
while true
|
while true
|
||||||
work = todo.pop
|
work = nil
|
||||||
|
|
||||||
case work
|
continue = true
|
||||||
when Stop
|
|
||||||
break
|
@mutex.synchronize do
|
||||||
when Trim
|
while todo.empty?
|
||||||
@mutex.synchronize do
|
if @trim_requested > 0
|
||||||
@trim_requested -= 1
|
@trim_requested -= 1
|
||||||
|
continue = false
|
||||||
|
break
|
||||||
|
end
|
||||||
|
|
||||||
|
if @shutdown
|
||||||
|
continue = false
|
||||||
|
break
|
||||||
|
end
|
||||||
|
|
||||||
|
@waiting += 1
|
||||||
|
@cond.wait @mutex
|
||||||
|
@waiting -= 1
|
||||||
|
|
||||||
|
if @shutdown
|
||||||
|
continue = false
|
||||||
|
break
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
break
|
work = todo.pop if continue
|
||||||
else
|
|
||||||
block.call work
|
|
||||||
end
|
end
|
||||||
|
|
||||||
|
break unless continue
|
||||||
|
|
||||||
|
block.call work
|
||||||
end
|
end
|
||||||
|
|
||||||
@mutex.synchronize do
|
@mutex.synchronize do
|
||||||
|
@ -73,28 +98,39 @@ module Puma
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
@mutex.synchronize { @workers << th }
|
@workers << th
|
||||||
|
|
||||||
th
|
th
|
||||||
end
|
end
|
||||||
|
|
||||||
|
private :spawn_thread
|
||||||
|
|
||||||
# Add +work+ to the todo list for a Thread to pickup and process.
|
# Add +work+ to the todo list for a Thread to pickup and process.
|
||||||
def <<(work)
|
def <<(work)
|
||||||
if @todo.num_waiting == 0 and @spawned < @max
|
@mutex.synchronize do
|
||||||
spawn_thread
|
if @shutdown
|
||||||
end
|
raise "Unable to add work while shutting down"
|
||||||
|
end
|
||||||
|
|
||||||
@todo << work
|
@todo << work
|
||||||
|
|
||||||
|
if @waiting == 0 and @spawned < @max
|
||||||
|
spawn_thread
|
||||||
|
end
|
||||||
|
|
||||||
|
@cond.signal
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
# If too many threads are in the pool, tell one to finish go ahead
|
# If too many threads are in the pool, tell one to finish go ahead
|
||||||
# and exit.
|
# and exit. If +force+ is true, then a trim request is requested
|
||||||
|
# even if all threads are being utilized.
|
||||||
#
|
#
|
||||||
def trim
|
def trim(force=false)
|
||||||
@mutex.synchronize do
|
@mutex.synchronize do
|
||||||
if @spawned - @trim_requested > @min
|
if (force or @waiting > 0) and @spawned - @trim_requested > @min
|
||||||
@trim_requested += 1
|
@trim_requested += 1
|
||||||
@todo << Trim
|
@cond.signal
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
@ -131,10 +167,11 @@ module Puma
|
||||||
# Tell all threads in the pool to exit and wait for them to finish.
|
# Tell all threads in the pool to exit and wait for them to finish.
|
||||||
#
|
#
|
||||||
def shutdown
|
def shutdown
|
||||||
@auto_trim.stop if @auto_trim
|
@mutex.synchronize do
|
||||||
|
@shutdown = true
|
||||||
|
@cond.broadcast
|
||||||
|
|
||||||
@spawned.times do
|
@auto_trim.stop if @auto_trim
|
||||||
@todo << Stop
|
|
||||||
end
|
end
|
||||||
|
|
||||||
# Use this instead of #each so that we don't stop in the middle
|
# Use this instead of #each so that we don't stop in the middle
|
||||||
|
|
|
@ -70,6 +70,8 @@ class TestThreadPool < Test::Unit::TestCase
|
||||||
|
|
||||||
finish = true
|
finish = true
|
||||||
|
|
||||||
|
pause
|
||||||
|
|
||||||
assert_equal 2, pool.spawned
|
assert_equal 2, pool.spawned
|
||||||
pool.trim
|
pool.trim
|
||||||
pause
|
pause
|
||||||
|
@ -82,7 +84,25 @@ class TestThreadPool < Test::Unit::TestCase
|
||||||
|
|
||||||
end
|
end
|
||||||
|
|
||||||
def test_trim_doesnt_overtrim
|
def test_force_trim_doesnt_overtrim
|
||||||
|
finish = false
|
||||||
|
pool = new_pool(1, 2) { Thread.pass until finish }
|
||||||
|
|
||||||
|
pool << 1
|
||||||
|
pool << 2
|
||||||
|
|
||||||
|
assert_equal 2, pool.spawned
|
||||||
|
pool.trim true
|
||||||
|
pool.trim true
|
||||||
|
|
||||||
|
finish = true
|
||||||
|
|
||||||
|
pause
|
||||||
|
|
||||||
|
assert_equal 1, pool.spawned
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_trim_is_ignored_if_no_waiting_threads
|
||||||
finish = false
|
finish = false
|
||||||
pool = new_pool(1, 2) { Thread.pass until finish }
|
pool = new_pool(1, 2) { Thread.pass until finish }
|
||||||
|
|
||||||
|
@ -97,7 +117,7 @@ class TestThreadPool < Test::Unit::TestCase
|
||||||
|
|
||||||
pause
|
pause
|
||||||
|
|
||||||
assert_equal 1, pool.spawned
|
assert_equal 2, pool.spawned
|
||||||
end
|
end
|
||||||
|
|
||||||
def test_autotrim
|
def test_autotrim
|
||||||
|
|
Loading…
Reference in a new issue