2011-09-20 01:16:31 -04:00
|
|
|
require 'thread'
|
|
|
|
|
2011-09-22 22:24:43 -04:00
|
|
|
module Puma
|
2011-12-01 18:23:14 -05:00
|
|
|
# A simple thread pool management object.
|
|
|
|
#
|
2011-09-20 01:16:31 -04:00
|
|
|
class ThreadPool
|
2011-12-01 18:23:14 -05:00
|
|
|
|
|
|
|
# Maintain a minimum of +min+ and maximum of +max+ threads
|
|
|
|
# in the pool.
|
|
|
|
#
|
|
|
|
# The block passed is the work that will be performed in each
|
|
|
|
# thread.
|
|
|
|
#
|
2011-09-20 01:16:31 -04:00
|
|
|
def initialize(min, max, &blk)
|
2012-02-07 12:49:23 -05:00
|
|
|
@cond = ConditionVariable.new
|
2011-09-20 01:16:31 -04:00
|
|
|
@mutex = Mutex.new
|
|
|
|
|
2012-02-07 12:49:23 -05:00
|
|
|
@todo = []
|
|
|
|
|
2011-09-20 01:16:31 -04:00
|
|
|
@spawned = 0
|
2012-02-07 12:49:23 -05:00
|
|
|
@waiting = 0
|
|
|
|
|
2011-09-20 01:16:31 -04:00
|
|
|
@min = min
|
|
|
|
@max = max
|
|
|
|
@block = blk
|
|
|
|
|
2012-02-07 12:49:23 -05:00
|
|
|
@shutdown = false
|
|
|
|
|
2011-09-20 01:16:31 -04:00
|
|
|
@trim_requested = 0
|
|
|
|
|
|
|
|
@workers = []
|
|
|
|
|
2011-12-05 13:07:01 -05:00
|
|
|
@auto_trim = nil
|
|
|
|
|
2012-02-07 12:49:23 -05:00
|
|
|
@mutex.synchronize do
|
|
|
|
min.times { spawn_thread }
|
|
|
|
end
|
2011-09-20 01:16:31 -04:00
|
|
|
end
|
|
|
|
|
|
|
|
attr_reader :spawned
|
|
|
|
|
2011-12-01 18:23:14 -05:00
|
|
|
# How many objects have yet to be processed by the pool?
|
|
|
|
#
|
2011-09-20 01:16:31 -04:00
|
|
|
def backlog
|
2012-02-07 12:49:23 -05:00
|
|
|
@mutex.synchronize { @todo.size }
|
2011-09-20 01:16:31 -04:00
|
|
|
end
|
|
|
|
|
2011-12-01 18:23:14 -05:00
|
|
|
# :nodoc:
|
2012-02-07 12:49:23 -05:00
|
|
|
#
|
|
|
|
# Must be called with @mutex held!
|
|
|
|
#
|
2011-09-20 01:16:31 -04:00
|
|
|
def spawn_thread
|
2012-02-07 12:49:23 -05:00
|
|
|
@spawned += 1
|
2011-09-20 01:16:31 -04:00
|
|
|
|
|
|
|
th = Thread.new do
|
|
|
|
todo = @todo
|
|
|
|
block = @block
|
|
|
|
|
|
|
|
while true
|
2012-02-07 12:49:23 -05:00
|
|
|
work = nil
|
|
|
|
|
|
|
|
continue = true
|
|
|
|
|
|
|
|
@mutex.synchronize do
|
|
|
|
while todo.empty?
|
|
|
|
if @trim_requested > 0
|
|
|
|
@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
|
2011-09-20 01:16:31 -04:00
|
|
|
end
|
|
|
|
|
2012-02-07 12:49:23 -05:00
|
|
|
work = todo.pop if continue
|
2011-09-20 01:16:31 -04:00
|
|
|
end
|
2012-02-07 12:49:23 -05:00
|
|
|
|
|
|
|
break unless continue
|
|
|
|
|
|
|
|
block.call work
|
2011-09-20 01:16:31 -04:00
|
|
|
end
|
|
|
|
|
|
|
|
@mutex.synchronize do
|
|
|
|
@spawned -= 1
|
|
|
|
@workers.delete th
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2012-02-07 12:49:23 -05:00
|
|
|
@workers << th
|
2011-09-20 01:16:31 -04:00
|
|
|
|
|
|
|
th
|
|
|
|
end
|
|
|
|
|
2012-02-07 12:49:23 -05:00
|
|
|
private :spawn_thread
|
|
|
|
|
2011-12-01 18:23:14 -05:00
|
|
|
# Add +work+ to the todo list for a Thread to pickup and process.
|
2011-09-20 01:16:31 -04:00
|
|
|
def <<(work)
|
2012-02-07 12:49:23 -05:00
|
|
|
@mutex.synchronize do
|
|
|
|
if @shutdown
|
|
|
|
raise "Unable to add work while shutting down"
|
|
|
|
end
|
2011-09-20 01:16:31 -04:00
|
|
|
|
2012-02-07 12:49:23 -05:00
|
|
|
@todo << work
|
|
|
|
|
|
|
|
if @waiting == 0 and @spawned < @max
|
|
|
|
spawn_thread
|
|
|
|
end
|
|
|
|
|
|
|
|
@cond.signal
|
|
|
|
end
|
2011-09-20 01:16:31 -04:00
|
|
|
end
|
|
|
|
|
2011-12-01 18:23:14 -05:00
|
|
|
# If too many threads are in the pool, tell one to finish go ahead
|
2012-02-07 12:49:23 -05:00
|
|
|
# and exit. If +force+ is true, then a trim request is requested
|
|
|
|
# even if all threads are being utilized.
|
2011-12-01 18:23:14 -05:00
|
|
|
#
|
2012-02-07 12:49:23 -05:00
|
|
|
def trim(force=false)
|
2011-09-20 01:16:31 -04:00
|
|
|
@mutex.synchronize do
|
2012-02-07 12:49:23 -05:00
|
|
|
if (force or @waiting > 0) and @spawned - @trim_requested > @min
|
2011-09-20 01:16:31 -04:00
|
|
|
@trim_requested += 1
|
2012-02-07 12:49:23 -05:00
|
|
|
@cond.signal
|
2011-09-20 01:16:31 -04:00
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2011-12-05 13:07:01 -05:00
|
|
|
class AutoTrim
|
|
|
|
def initialize(pool, timeout)
|
|
|
|
@pool = pool
|
|
|
|
@timeout = timeout
|
|
|
|
@running = false
|
|
|
|
end
|
|
|
|
|
|
|
|
def start!
|
|
|
|
@running = true
|
|
|
|
|
|
|
|
@thread = Thread.new do
|
|
|
|
while @running
|
|
|
|
@pool.trim
|
|
|
|
sleep @timeout
|
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
def stop
|
|
|
|
@running = false
|
|
|
|
@thread.wakeup
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
def auto_trim!(timeout=5)
|
|
|
|
@auto_trim = AutoTrim.new(self, timeout)
|
|
|
|
@auto_trim.start!
|
|
|
|
end
|
|
|
|
|
2011-12-01 18:23:14 -05:00
|
|
|
# Tell all threads in the pool to exit and wait for them to finish.
|
|
|
|
#
|
2011-09-20 01:16:31 -04:00
|
|
|
def shutdown
|
2012-02-07 12:49:23 -05:00
|
|
|
@mutex.synchronize do
|
|
|
|
@shutdown = true
|
|
|
|
@cond.broadcast
|
2011-12-05 13:07:01 -05:00
|
|
|
|
2012-02-07 12:49:23 -05:00
|
|
|
@auto_trim.stop if @auto_trim
|
2011-09-20 01:16:31 -04:00
|
|
|
end
|
|
|
|
|
2011-12-06 17:56:38 -05:00
|
|
|
# Use this instead of #each so that we don't stop in the middle
|
|
|
|
# of each and see a mutated object mid #each
|
|
|
|
@workers.first.join until @workers.empty?
|
2011-09-20 01:16:31 -04:00
|
|
|
|
|
|
|
@spawned = 0
|
|
|
|
@workers = []
|
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|