From dd2b2274ef0cd8a121cf7655ade939a1f63bc971 Mon Sep 17 00:00:00 2001 From: Eric Wong Date: Wed, 25 Nov 2009 19:01:34 -0800 Subject: AppPool middleware now compatible with Fibers This enables the safe use of Rainbows::AppPool with all concurrency models, not just threaded ones. AppPool is now effective with *all* Fiber-based concurrency models including Revactor (and of course the new Fiber{Pool,Spawn} ones). --- lib/rainbows.rb | 1 + lib/rainbows/app_pool.rb | 23 +++++++++++++++++------ lib/rainbows/fiber.rb | 2 ++ lib/rainbows/fiber/queue.rb | 33 +++++++++++++++++++++++++++++++++ t/t9000-rack-app-pool.sh | 2 +- t/t9000.ru | 13 ++++++++----- 6 files changed, 62 insertions(+), 12 deletions(-) create mode 100644 lib/rainbows/fiber/queue.rb diff --git a/lib/rainbows.rb b/lib/rainbows.rb index 27f2df2..93e9fd4 100644 --- a/lib/rainbows.rb +++ b/lib/rainbows.rb @@ -82,6 +82,7 @@ module Rainbows u = model.to_s.gsub(/([a-z0-9])([A-Z0-9])/) { "#{$1}_#{$2.downcase!}" } autoload model, "rainbows/#{u.downcase!}" end + autoload :Fiber, 'rainbows/fiber' # core class end diff --git a/lib/rainbows/app_pool.rb b/lib/rainbows/app_pool.rb index f4c22a2..5c85e0a 100644 --- a/lib/rainbows/app_pool.rb +++ b/lib/rainbows/app_pool.rb @@ -42,11 +42,8 @@ module Rainbows # AppPool has no effect on the Rev or EventMachine concurrency models # as those are single-threaded/single-instance as far as application # concurrency goes. In other words, +P+ is always +one+ when using - # Rev or EventMachine. AppPool currently only works with the - # ThreadSpawn and ThreadPool models. It does not yet work reliably - # with the Revactor model, but actors are far more lightweight and - # probably better suited for lightweight applications that would - # not benefit from AppPool. + # Rev or EventMachine. As of \Rainbows! 0.7.0, it is safe to use with + # Revactor and the new FiberSpawn and FiberPool concurrency models. # # Since this is Rack middleware, you may load this in your Rack # config.ru file and even use it in threaded servers other than @@ -60,7 +57,7 @@ module Rainbows # You may to load this earlier or later in your middleware chain # depending on the concurrency/copy-friendliness of your middleware(s). - class AppPool < Struct.new(:pool) + class AppPool < Struct.new(:pool, :re) # +opt+ is a hash, +:size+ is the size of the pool (default: 6) # meaning you can have up to 6 concurrent instances of +app+ @@ -86,6 +83,20 @@ module Rainbows # Rack application endpoint, +env+ is the Rack environment def call(env) + + # we have to do this check at call time (and not initialize) + # because of preload_app=true and models being changeable with SIGHUP + # fortunately this is safe for all the reentrant (but not multithreaded) + # classes that depend on it and a safe no-op for multithreaded + # concurrency models + self.re ||= begin + case env["rainbows.model"] + when :FiberSpawn, :FiberPool, :Revactor + self.pool = Rainbows::Fiber::Queue.new(pool) + end + true + end + app = pool.shift app.call(env) ensure diff --git a/lib/rainbows/fiber.rb b/lib/rainbows/fiber.rb index 94502a3..f0755aa 100644 --- a/lib/rainbows/fiber.rb +++ b/lib/rainbows/fiber.rb @@ -4,6 +4,8 @@ require 'rainbows/fiber/io' module Rainbows module Fiber + autoload :Queue, 'rainbows/fiber/queue' + RD = {} WR = {} ZZ = {} diff --git a/lib/rainbows/fiber/queue.rb b/lib/rainbows/fiber/queue.rb new file mode 100644 index 0000000..4c14f19 --- /dev/null +++ b/lib/rainbows/fiber/queue.rb @@ -0,0 +1,33 @@ +module Rainbows + module Fiber + + # a self-sufficient Queue implmentation for Fiber-based concurrency + # models + class Queue < Struct.new(:queue, :waiters) + + def initialize(queue = [], waiters = []) + # move elements of the Queue into an Array + if queue.class.name == "Queue" + queue = queue.length.times.map { queue.pop } + end + super queue, waiters + end + + def shift + # ah the joys of not having to deal with race conditions + if queue.empty? + waiters << ::Fiber.current + ::Fiber.yield + end + queue.shift + end + + def <<(obj) + queue << obj + blocked = waiters.shift and blocked.resume + queue # not quite 100% compatible but no-one's looking :> + end + + end + end +end diff --git a/t/t9000-rack-app-pool.sh b/t/t9000-rack-app-pool.sh index cd08724..544532b 100755 --- a/t/t9000-rack-app-pool.sh +++ b/t/t9000-rack-app-pool.sh @@ -1,7 +1,7 @@ #!/bin/sh . ./test-lib.sh case $model in -*Thread*) ;; +*Thread*|*Fiber*|Revactor) ;; *) t_info "skipping $T since it's not compatible with $model" exit 0 diff --git a/t/t9000.ru b/t/t9000.ru index af6b4fc..4ca36c1 100644 --- a/t/t9000.ru +++ b/t/t9000.ru @@ -1,13 +1,16 @@ use Rack::ContentLength use Rack::ContentType use Rainbows::AppPool, :size => ENV['APP_POOL_SIZE'].to_i -sleep_class = ENV['SLEEP_CLASS'] -sleep_class = sleep_class ? Object.const_get(sleep_class) : Kernel class Sleeper def call(env) - sleep_class = ENV['SLEEP_CLASS'] - sleep_class = sleep_class ? Object.const_get(sleep_class) : Kernel - sleep_class.sleep 1 + (case env['rainbows.model'] + when :FiberPool, :FiberSpawn + Rainbows::Fiber + when :Revactor + Actor + else + Kernel + end).sleep(1) [ 200, {}, [ "#{object_id}\n" ] ] end end -- cgit v1.2.3-24-ge0c7