rainbows.git  about / heads / tags
Unicorn for sleepy apps and slow clients
blob a1a31198d708010e527ed7bad6f9c8fc42fb469a 3952 bytes (raw)
$ git show v0.91.0: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
104
105
106
 
# -*- 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 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 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)

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

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

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