rainbows.git  about / heads / tags
Unicorn for sleepy apps and slow clients
blob 2d71604667f683745109daf0bbbc150986fced17 3788 bytes (raw)
$ git show HEAD:lib/rainbows/app_pool.rb	# shows this blob on the CLI

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
 
# -*- encoding: binary -*-

require 'thread'

# 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 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.  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
# \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 Rainbows::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+
  # 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) # :nodoc:

    # 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, :NeverBlock,
           :RevFiberSpawn, :CoolioFiberSpawn
        self.pool = Rainbows::Fiber::Queue.new(pool)
      end
      true
    end

    app = pool.shift
    app.call(env)
  ensure
    pool << app
  end
end

git clone https://yhbt.net/rainbows.git