From cbaa604582a9f80eba3fc0c2423234f497f21726 Mon Sep 17 00:00:00 2001 From: Eric Wong Date: Thu, 15 Oct 2009 00:37:59 -0700 Subject: Add Rainbows::AppPool Rack middleware --- lib/rainbows.rb | 1 + lib/rainbows/app_pool.rb | 90 ++++++++++++++++++++++++++++++++++++++++++++++++ t/t9000-rack-app-pool.sh | 37 ++++++++++++++++++++ t/t9000.ru | 14 ++++++++ 4 files changed, 142 insertions(+) create mode 100644 lib/rainbows/app_pool.rb create mode 100755 t/t9000-rack-app-pool.sh create mode 100644 t/t9000.ru diff --git a/lib/rainbows.rb b/lib/rainbows.rb index c2813a5..7978288 100644 --- a/lib/rainbows.rb +++ b/lib/rainbows.rb @@ -7,6 +7,7 @@ module Rainbows require 'rainbows/http_server' require 'rainbows/http_response' require 'rainbows/base' + autoload :AppPool, 'rainbows/app_pool' class << self diff --git a/lib/rainbows/app_pool.rb b/lib/rainbows/app_pool.rb new file mode 100644 index 0000000..c0e1df3 --- /dev/null +++ b/lib/rainbows/app_pool.rb @@ -0,0 +1,90 @@ +# -*- encoding: binary -*- + +require 'thread' + +module Rainbows + + # Rack middleware to limit application-level concurrency independently + # of network conncurrency in \Rainbows! Since the +worker_connections+ + # option in \Rainbows! is only intended to limit the number of + # simultaneous clients, this middleware may be used to limit the + # number of concurrent application dispatches independently of + # concurrent clients. + # + # Instead of using M:N concurrency in \Rainbows!, this middleware + # allows M:N:P concurrency where +P+ is the AppPool +:size+ while + # +M+ remains the number of +worker_processes+ and +N+ remains the + # number of +worker_connections+. + # + # rainbows master + # \_ rainbows worker[0] + # | \_ client[0,0]------\ ___app[0] + # | \_ client[0,1]-------\ /___app[1] + # | \_ client[0,2]-------->--< ... + # | ... __/ `---app[P] + # | \_ client[0,N]----/ + # \_ rainbows worker[1] + # | \_ client[1,0]------\ ___app[0] + # | \_ client[1,1]-------\ /___app[1] + # | \_ client[1,2]-------->--< ... + # | ... __/ `---app[P] + # | \_ client[1,N]----/ + # \_ rainbows worker[M] + # \_ client[M,0]------\ ___app[0] + # \_ client[M,1]-------\ /___app[1] + # \_ client[M,2]-------->--< ... + # ... __/ `---app[P] + # \_ client[M,N]----/ + # + # AppPool should be used if you want to enforce a lower value of +P+ + # than +N+. + # + # AppPool has no effect on the Rainbows::Rev concurrency model as that is + # single-threaded/single-instance as far as application concurrency goes. + # In other words, +P+ is always +one+ when using \Rev (but not + # \Revactor) regardless of (or even if) this middleware is loaded. + # + # Since this is Rack middleware, you may load this in your Rack + # config.ru file and even use it in servers other than \Rainbows! + # + # use Rainbows::AppPool, :size => 30 + # map "/lobster" do + # run Rack::Lobster.new + # end + # + # 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) + + # +opt+ is a hash, +:size+ is the size of the pool (default: 6) + # meaning you can have up to 6 concurrent instances of +app+ + # within one \Rainbows! worker process. We support various + # methods of the +:copy+ option: +dup+, +clone+, +deep+ and +none+. + # Depending on your +app+, one of these options should be set. + # The default +:copy+ is +:dup+ as is commonly seen in existing + # Rack middleware. + def initialize(app, opt = {}) + self.pool = Queue.new + (1...(opt[:size] || 6)).each do + pool << case (opt[:copy] || :dup) + when :none then app + when :dup then app.dup + when :clone then app.clone + when :deep then Marshal.load(Marshal.dump(app)) # unlikely... + else + raise ArgumentError, "unsupported copy method: #{opt[:copy].inspect}" + end + end + pool << app # the original + end + + # Rack application endpoint, +env+ is the Rack environment + def call(env) + app = pool.shift + app.call(env) + ensure + pool << app + end + end +end diff --git a/t/t9000-rack-app-pool.sh b/t/t9000-rack-app-pool.sh new file mode 100755 index 0000000..41402d9 --- /dev/null +++ b/t/t9000-rack-app-pool.sh @@ -0,0 +1,37 @@ +#!/bin/sh +. ./test-lib.sh + +eval $(unused_listen) +rtmpfiles unicorn_config pid r_err r_out curl_out curl_err + +nr_client=30 + +cat > $unicorn_config <> $curl_out 2>> $curl_err ) & +done +wait +echo elapsed=$(( $(date +%s) - $start )) +kill $(cat $pid) + +test $APP_POOL_SIZE -eq $(sort < $curl_out | uniq | wc -l) +! test -s $curl_err + +! grep Error $r_err +# diff --git a/t/t9000.ru b/t/t9000.ru new file mode 100644 index 0000000..af6b4fc --- /dev/null +++ b/t/t9000.ru @@ -0,0 +1,14 @@ +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 + [ 200, {}, [ "#{object_id}\n" ] ] + end +end +run Sleeper.new -- cgit v1.2.3-24-ge0c7