preliminary NeverBlock support with EventMachine
[rainbows.git] / lib / rainbows / app_pool.rb
blob036fe9c30cdaae2a5b46071aaae5172a2778dd79
1 # -*- encoding: binary -*-
3 require 'thread'
5 module Rainbows
7   # Rack middleware to limit application-level concurrency independently
8   # of network conncurrency in \Rainbows!   Since the +worker_connections+
9   # option in \Rainbows! is only intended to limit the number of
10   # simultaneous clients, this middleware may be used to limit the
11   # number of concurrent application dispatches independently of
12   # concurrent clients.
13   #
14   # Instead of using M:N concurrency in \Rainbows!, this middleware
15   # allows M:N:P concurrency where +P+ is the AppPool +:size+ while
16   # +M+ remains the number of +worker_processes+ and +N+ remains the
17   # number of +worker_connections+.
18   #
19   #   rainbows master
20   #    \_ rainbows worker[0]
21   #    |  \_ client[0,0]------\      ___app[0]
22   #    |  \_ client[0,1]-------\    /___app[1]
23   #    |  \_ client[0,2]-------->--<       ...
24   #    |  ...                __/    `---app[P]
25   #    |  \_ client[0,N]----/
26   #    \_ rainbows worker[1]
27   #    |  \_ client[1,0]------\      ___app[0]
28   #    |  \_ client[1,1]-------\    /___app[1]
29   #    |  \_ client[1,2]-------->--<       ...
30   #    |  ...                __/    `---app[P]
31   #    |  \_ client[1,N]----/
32   #    \_ rainbows worker[M]
33   #       \_ client[M,0]------\      ___app[0]
34   #       \_ client[M,1]-------\    /___app[1]
35   #       \_ client[M,2]-------->--<       ...
36   #       ...                __/    `---app[P]
37   #       \_ client[M,N]----/
38   #
39   # AppPool should be used if you want to enforce a lower value of +P+
40   # than +N+.
41   #
42   # AppPool has no effect on the Rev or EventMachine concurrency models
43   # as those are single-threaded/single-instance as far as application
44   # concurrency goes.  In other words, +P+ is always +one+ when using
45   # Rev or EventMachine.  As of \Rainbows! 0.7.0, it is safe to use with
46   # Revactor and the new FiberSpawn and FiberPool concurrency models.
47   #
48   # Since this is Rack middleware, you may load this in your Rack
49   # config.ru file and even use it in threaded servers other than
50   # \Rainbows!
51   #
52   #   use Rainbows::AppPool, :size => 30
53   #   map "/lobster" do
54   #     run Rack::Lobster.new
55   #   end
56   #
57   # You may to load this earlier or later in your middleware chain
58   # depending on the concurrency/copy-friendliness of your middleware(s).
60   class AppPool < Struct.new(:pool, :re)
62     # +opt+ is a hash, +:size+ is the size of the pool (default: 6)
63     # meaning you can have up to 6 concurrent instances of +app+
64     # within one \Rainbows! worker process.  We support various
65     # methods of the +:copy+ option: +dup+, +clone+, +deep+ and +none+.
66     # Depending on your +app+, one of these options should be set.
67     # The default +:copy+ is +:dup+ as is commonly seen in existing
68     # Rack middleware.
69     def initialize(app, opt = {})
70       self.pool = Queue.new
71       (1...(opt[:size] || 6)).each do
72         pool << case (opt[:copy] || :dup)
73         when :none then app
74         when :dup then app.dup
75         when :clone then app.clone
76         when :deep then Marshal.load(Marshal.dump(app)) # unlikely...
77         else
78           raise ArgumentError, "unsupported copy method: #{opt[:copy].inspect}"
79         end
80       end
81       pool << app # the original
82     end
84     # Rack application endpoint, +env+ is the Rack environment
85     def call(env)
87       # we have to do this check at call time (and not initialize)
88       # because of preload_app=true and models being changeable with SIGHUP
89       # fortunately this is safe for all the reentrant (but not multithreaded)
90       # classes that depend on it and a safe no-op for multithreaded
91       # concurrency models
92       self.re ||= begin
93         case env["rainbows.model"]
94         when :FiberSpawn, :FiberPool, :Revactor, :NeverBlock
95           self.pool = Rainbows::Fiber::Queue.new(pool)
96         end
97         true
98       end
100       app = pool.shift
101       app.call(env)
102       ensure
103         pool << app
104     end
105   end