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
|