rainbows 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- data/.document +11 -0
- data/.gitignore +18 -0
- data/COPYING +339 -0
- data/DEPLOY +29 -0
- data/Documentation/.gitignore +5 -0
- data/Documentation/GNUmakefile +30 -0
- data/Documentation/rainbows.1.txt +159 -0
- data/FAQ +50 -0
- data/GIT-VERSION-GEN +41 -0
- data/GNUmakefile +156 -0
- data/LICENSE +55 -0
- data/README +122 -0
- data/Rakefile +103 -0
- data/SIGNALS +94 -0
- data/TODO +20 -0
- data/TUNING +31 -0
- data/bin/rainbows +166 -0
- data/lib/rainbows.rb +53 -0
- data/lib/rainbows/base.rb +69 -0
- data/lib/rainbows/const.rb +24 -0
- data/lib/rainbows/http_response.rb +35 -0
- data/lib/rainbows/http_server.rb +47 -0
- data/lib/rainbows/revactor.rb +158 -0
- data/lib/rainbows/revactor/tee_input.rb +44 -0
- data/lib/rainbows/thread_pool.rb +96 -0
- data/lib/rainbows/thread_spawn.rb +79 -0
- data/local.mk.sample +54 -0
- data/rainbows.gemspec +47 -0
- data/setup.rb +1586 -0
- data/t/.gitignore +4 -0
- data/t/GNUmakefile +64 -0
- data/t/bin/unused_listen +39 -0
- data/t/sha1.ru +17 -0
- data/t/t0000-basic.sh +18 -0
- data/t/t1000-thread-pool-basic.sh +53 -0
- data/t/t2000-thread-spawn-basic.sh +50 -0
- data/t/t3000-revactor-basic.sh +52 -0
- data/t/t3100-revactor-tee-input.sh +49 -0
- data/t/test-lib.sh +41 -0
- data/vs_Unicorn +48 -0
- metadata +135 -0
data/SIGNALS
ADDED
@@ -0,0 +1,94 @@
|
|
1
|
+
== Signal handling
|
2
|
+
|
3
|
+
In general, signals need only be sent to the master process. However,
|
4
|
+
the signals Rainbows! uses internally to communicate with the worker
|
5
|
+
processes are documented here as well.
|
6
|
+
|
7
|
+
=== Master Process
|
8
|
+
|
9
|
+
* HUP - reload config file, app, and gracefully restart all workers
|
10
|
+
|
11
|
+
* INT/TERM - quick shutdown, kills all workers immediately
|
12
|
+
|
13
|
+
* QUIT - graceful shutdown, waits for workers to finish their
|
14
|
+
current request before finishing.
|
15
|
+
|
16
|
+
* USR1 - reopen all logs owned by the master and all workers
|
17
|
+
See Unicorn::Util.reopen_logs for what is considered a log.
|
18
|
+
|
19
|
+
* USR2 - reexecute the running binary. A separate QUIT
|
20
|
+
should be sent to the original process once the child is verified to
|
21
|
+
be up and running.
|
22
|
+
|
23
|
+
* WINCH - gracefully stops workers but keep the master running.
|
24
|
+
This will only work for daemonized processes.
|
25
|
+
|
26
|
+
* TTIN - increment the number of worker processes by one
|
27
|
+
|
28
|
+
* TTOU - decrement the number of worker processes by one
|
29
|
+
|
30
|
+
=== Worker Processes
|
31
|
+
|
32
|
+
Sending signals directly to the worker processes should not normally be
|
33
|
+
needed. If the master process is running, any exited worker will be
|
34
|
+
automatically respawned.
|
35
|
+
|
36
|
+
* INT/TERM - Quick shutdown, immediately exit.
|
37
|
+
Unless WINCH has been sent to the master (or the master is killed),
|
38
|
+
the master process will respawn a worker to replace this one.
|
39
|
+
|
40
|
+
* QUIT - Gracefully exit after finishing the current request.
|
41
|
+
Unless WINCH has been sent to the master (or the master is killed),
|
42
|
+
the master process will respawn a worker to replace this one.
|
43
|
+
|
44
|
+
* USR1 - Reopen all logs owned by the worker process.
|
45
|
+
See Unicorn::Util.reopen_logs for what is considered a log.
|
46
|
+
Log files are not reopened until it is done processing
|
47
|
+
the current request, so multiple log lines for one request
|
48
|
+
(as done by Rails) will not be split across multiple logs.
|
49
|
+
|
50
|
+
=== Procedure to replace a running rainbows executable
|
51
|
+
|
52
|
+
You may replace a running instance of unicorn with a new one without
|
53
|
+
losing any incoming connections. Doing so will reload all of your
|
54
|
+
application code, Unicorn config, Ruby executable, and all libraries.
|
55
|
+
The only things that will not change (due to OS limitations) are:
|
56
|
+
|
57
|
+
1. The path to the rainbows executable script. If you want to change to
|
58
|
+
a different installation of Ruby, you can modify the shebang
|
59
|
+
line to point to your alternative interpreter.
|
60
|
+
|
61
|
+
The procedure is exactly like that of nginx:
|
62
|
+
|
63
|
+
1. Send USR2 to the master process
|
64
|
+
|
65
|
+
2. Check your process manager or pid files to see if a new master spawned
|
66
|
+
successfully. If you're using a pid file, the old process will have
|
67
|
+
".oldbin" appended to its path. You should have two master instances
|
68
|
+
of rainbows running now, both of which will have workers servicing
|
69
|
+
requests. Your process tree should look something like this:
|
70
|
+
|
71
|
+
rainbows master (old)
|
72
|
+
\_ rainbows worker[0]
|
73
|
+
\_ rainbows worker[1]
|
74
|
+
\_ rainbows worker[2]
|
75
|
+
\_ rainbows worker[3]
|
76
|
+
\_ rainbows master
|
77
|
+
\_ rainbows worker[0]
|
78
|
+
\_ rainbows worker[1]
|
79
|
+
\_ rainbows worker[2]
|
80
|
+
\_ rainbows worker[3]
|
81
|
+
|
82
|
+
3. You can now send WINCH to the old master process so only the new workers
|
83
|
+
serve requests. If your rainbows process is bound to an
|
84
|
+
interactive terminal, you can skip this step. Step 5 will be more
|
85
|
+
difficult but you can also skip it if your process is not daemonized.
|
86
|
+
|
87
|
+
4. You should now ensure that everything is running correctly with the
|
88
|
+
new workers as the old workers die off.
|
89
|
+
|
90
|
+
5. If everything seems ok, then send QUIT to the old master. You're done!
|
91
|
+
|
92
|
+
If something is broken, then send HUP to the old master to reload
|
93
|
+
the config and restart its workers. Then send QUIT to the new master
|
94
|
+
process.
|
data/TODO
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
= TODO items for Rainbows!
|
2
|
+
|
3
|
+
We're lazy and pick the easy items to do first, then the ones people
|
4
|
+
care about.
|
5
|
+
|
6
|
+
* Rev (without Revactor) - since we already use Revactor we might as
|
7
|
+
well support this one so 1.8 users won't be left out. Doing TeeInput
|
8
|
+
is probably going to get ugly, though...
|
9
|
+
|
10
|
+
* EventMachine - much like Rev, but we haven't looked at this one much
|
11
|
+
(our benevolent dictator doesn't like C++). If we can figure out how
|
12
|
+
to do Rev without Revactor, then this should be pretty easy.
|
13
|
+
|
14
|
+
* Fiber support - Revactor already uses these with Ruby 1.9, also not
|
15
|
+
sure how TeeInput can be done with this.
|
16
|
+
|
17
|
+
* Omnibus - haven't looked into it, probably like Revactor with 1.8?
|
18
|
+
|
19
|
+
* Rubinius Actors - should be like Revactor and easily doable once
|
20
|
+
Rubinius gets more mature.
|
data/TUNING
ADDED
@@ -0,0 +1,31 @@
|
|
1
|
+
= Tuning \Rainbows!
|
2
|
+
|
3
|
+
Most of the {tuning notes}[http://unicorn.bogomips.org/TUNING.html]
|
4
|
+
apply to \Rainbows! as well. \Rainbows! is not particularly optimized
|
5
|
+
at the moment and is designed for applications that spend large amounts
|
6
|
+
of the time waiting on network activity. Thus memory usage and memory
|
7
|
+
bandwidth for keeping connections open are often limiting factors as
|
8
|
+
well.
|
9
|
+
|
10
|
+
== \Rainbows! configuration
|
11
|
+
|
12
|
+
* Don't set +worker_connections+ too high. It is often better to start
|
13
|
+
denying requests and only serve the clients you can than to be
|
14
|
+
completely bogged down and be unusable for everybody.
|
15
|
+
|
16
|
+
* Increase +worker_processes+ if you have resources (RAM/DB connections)
|
17
|
+
available. Additional worker processes can better utilize SMP, are more
|
18
|
+
robust against crashes and are more likely to be fairly scheduled by
|
19
|
+
the kernel.
|
20
|
+
|
21
|
+
== nginx configuration
|
22
|
+
|
23
|
+
If you intend to use nginx as a reverse-proxy in front of \Rainbows! to
|
24
|
+
handle Comet applications, make sure you disable proxy response
|
25
|
+
buffering in nginx:
|
26
|
+
|
27
|
+
proxy_buffering off;
|
28
|
+
|
29
|
+
This can be disabled on a per-backend basis in nginx, so under no
|
30
|
+
circumstances should you disable response buffering to Unicorn
|
31
|
+
backends, only to \Rainbows! backends.
|
data/bin/rainbows
ADDED
@@ -0,0 +1,166 @@
|
|
1
|
+
#!/home/ew/bin/ruby
|
2
|
+
# -*- encoding: binary -*-
|
3
|
+
require 'unicorn/launcher'
|
4
|
+
require 'rainbows'
|
5
|
+
require 'optparse'
|
6
|
+
|
7
|
+
env = "development"
|
8
|
+
daemonize = false
|
9
|
+
listeners = []
|
10
|
+
options = { :listeners => listeners }
|
11
|
+
host, port = Unicorn::Const::DEFAULT_HOST, Unicorn::Const::DEFAULT_PORT
|
12
|
+
set_listener = false
|
13
|
+
|
14
|
+
opts = OptionParser.new("", 24, ' ') do |opts|
|
15
|
+
opts.banner = "Usage: #{File.basename($0)} " \
|
16
|
+
"[ruby options] [unicorn options] [rackup config file]"
|
17
|
+
|
18
|
+
opts.separator "Ruby options:"
|
19
|
+
|
20
|
+
lineno = 1
|
21
|
+
opts.on("-e", "--eval LINE", "evaluate a LINE of code") do |line|
|
22
|
+
eval line, TOPLEVEL_BINDING, "-e", lineno
|
23
|
+
lineno += 1
|
24
|
+
end
|
25
|
+
|
26
|
+
opts.on("-d", "--debug", "set debugging flags (set $DEBUG to true)") do
|
27
|
+
$DEBUG = true
|
28
|
+
end
|
29
|
+
|
30
|
+
opts.on("-w", "--warn", "turn warnings on for your script") do
|
31
|
+
$-w = true
|
32
|
+
end
|
33
|
+
|
34
|
+
opts.on("-I", "--include PATH",
|
35
|
+
"specify $LOAD_PATH (may be used more than once)") do |path|
|
36
|
+
$LOAD_PATH.unshift(*path.split(/:/))
|
37
|
+
end
|
38
|
+
|
39
|
+
opts.on("-r", "--require LIBRARY",
|
40
|
+
"require the library, before executing your script") do |library|
|
41
|
+
require library
|
42
|
+
end
|
43
|
+
|
44
|
+
opts.separator "Unicorn options:"
|
45
|
+
|
46
|
+
# some of these switches exist for rackup command-line compatibility,
|
47
|
+
|
48
|
+
opts.on("-o", "--host HOST",
|
49
|
+
"listen on HOST (default: #{Unicorn::Const::DEFAULT_HOST})") do |h|
|
50
|
+
host = h
|
51
|
+
set_listener = true
|
52
|
+
end
|
53
|
+
|
54
|
+
opts.on("-p", "--port PORT",
|
55
|
+
"use PORT (default: #{Unicorn::Const::DEFAULT_PORT})") do |p|
|
56
|
+
port = p.to_i
|
57
|
+
set_listener = true
|
58
|
+
end
|
59
|
+
|
60
|
+
opts.on("-E", "--env ENVIRONMENT",
|
61
|
+
"use ENVIRONMENT for defaults (default: development)") do |e|
|
62
|
+
env = e
|
63
|
+
end
|
64
|
+
|
65
|
+
opts.on("-D", "--daemonize", "run daemonized in the background") do |d|
|
66
|
+
daemonize = d ? true : false
|
67
|
+
end
|
68
|
+
|
69
|
+
opts.on("-P", "--pid FILE", "DEPRECATED") do |f|
|
70
|
+
warn %q{Use of --pid/-P is strongly discouraged}
|
71
|
+
warn %q{Use the 'pid' directive in the Unicorn config file instead}
|
72
|
+
options[:pid] = File.expand_path(f)
|
73
|
+
end
|
74
|
+
|
75
|
+
opts.on("-s", "--server SERVER",
|
76
|
+
"this flag only exists for compatibility") do |s|
|
77
|
+
warn "-s/--server only exists for compatibility with rackup"
|
78
|
+
end
|
79
|
+
|
80
|
+
# Unicorn-specific stuff
|
81
|
+
opts.on("-l", "--listen {HOST:PORT|PATH}",
|
82
|
+
"listen on HOST:PORT or PATH",
|
83
|
+
"this may be specified multiple times",
|
84
|
+
"(default: #{Unicorn::Const::DEFAULT_LISTEN})") do |address|
|
85
|
+
listeners << address
|
86
|
+
end
|
87
|
+
|
88
|
+
opts.on("-c", "--config-file FILE", "Unicorn-specific config file") do |f|
|
89
|
+
options[:config_file] = File.expand_path(f)
|
90
|
+
end
|
91
|
+
|
92
|
+
# I'm avoiding Unicorn-specific config options on the command-line.
|
93
|
+
# IMNSHO, config options on the command-line are redundant given
|
94
|
+
# config files and make things unnecessarily complicated with multiple
|
95
|
+
# places to look for a config option.
|
96
|
+
|
97
|
+
opts.separator "Common options:"
|
98
|
+
|
99
|
+
opts.on_tail("-h", "--help", "Show this message") do
|
100
|
+
puts opts.to_s.gsub(/^.*DEPRECATED.*$/s, '')
|
101
|
+
exit
|
102
|
+
end
|
103
|
+
|
104
|
+
opts.on_tail("-v", "--version", "Show version") do
|
105
|
+
puts "unicorn v#{Unicorn::Const::UNICORN_VERSION}"
|
106
|
+
exit
|
107
|
+
end
|
108
|
+
|
109
|
+
opts.parse! ARGV
|
110
|
+
end
|
111
|
+
|
112
|
+
config = ARGV[0] || "config.ru"
|
113
|
+
abort "configuration file #{config} not found" unless File.exist?(config)
|
114
|
+
|
115
|
+
if config =~ /\.ru$/
|
116
|
+
# parse embedded command-line options in config.ru comments
|
117
|
+
if File.open(config, "rb") { |fp| fp.sysread(fp.stat.size) } =~ /^#\\(.*)/
|
118
|
+
opts.parse! $1.split(/\s+/)
|
119
|
+
end
|
120
|
+
end
|
121
|
+
|
122
|
+
require 'pp' if $DEBUG
|
123
|
+
|
124
|
+
app = lambda do ||
|
125
|
+
# require Rack as late as possible in case $LOAD_PATH is modified
|
126
|
+
# in config.ru or command-line
|
127
|
+
inner_app = case config
|
128
|
+
when /\.ru$/
|
129
|
+
raw = File.open(config, "rb") { |fp| fp.sysread(fp.stat.size) }
|
130
|
+
raw.sub!(/^__END__\n.*/, '')
|
131
|
+
eval("Rack::Builder.new {(#{raw}\n)}.to_app", nil, config)
|
132
|
+
else
|
133
|
+
require config
|
134
|
+
Object.const_get(File.basename(config, '.rb').capitalize)
|
135
|
+
end
|
136
|
+
pp({ :inner_app => inner_app }) if $DEBUG
|
137
|
+
case env
|
138
|
+
when "development"
|
139
|
+
Rack::Builder.new do
|
140
|
+
use Rack::CommonLogger, $stderr
|
141
|
+
use Rack::ShowExceptions
|
142
|
+
use Rack::Lint
|
143
|
+
run inner_app
|
144
|
+
end.to_app
|
145
|
+
when "deployment"
|
146
|
+
Rack::Builder.new do
|
147
|
+
use Rack::CommonLogger, $stderr
|
148
|
+
run inner_app
|
149
|
+
end.to_app
|
150
|
+
else
|
151
|
+
inner_app
|
152
|
+
end
|
153
|
+
end
|
154
|
+
|
155
|
+
listeners << "#{host}:#{port}" if set_listener
|
156
|
+
|
157
|
+
if $DEBUG
|
158
|
+
pp({
|
159
|
+
:unicorn_options => options,
|
160
|
+
:app => app,
|
161
|
+
:daemonize => daemonize,
|
162
|
+
})
|
163
|
+
end
|
164
|
+
|
165
|
+
Unicorn::Launcher.daemonize! if daemonize
|
166
|
+
Rainbows.run(app, options)
|
data/lib/rainbows.rb
ADDED
@@ -0,0 +1,53 @@
|
|
1
|
+
# -*- encoding: binary -*-
|
2
|
+
require 'unicorn'
|
3
|
+
|
4
|
+
module Rainbows
|
5
|
+
|
6
|
+
require 'rainbows/const'
|
7
|
+
require 'rainbows/http_server'
|
8
|
+
require 'rainbows/http_response'
|
9
|
+
require 'rainbows/base'
|
10
|
+
|
11
|
+
class << self
|
12
|
+
|
13
|
+
# runs the Rainbows! HttpServer with +app+ and +options+ and does
|
14
|
+
# not return until the server has exited.
|
15
|
+
def run(app, options = {})
|
16
|
+
HttpServer.new(app, options).start.join
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
# configures Rainbows! with a given concurrency model to +use+ and
|
21
|
+
# a +worker_connections+ upper-bound. This method may be called
|
22
|
+
# inside a Unicorn/Rainbows configuration file:
|
23
|
+
#
|
24
|
+
# Rainbows! do
|
25
|
+
# use :Revactor # this may also be :ThreadSpawn or :ThreadPool
|
26
|
+
# worker_connections 128
|
27
|
+
# end
|
28
|
+
#
|
29
|
+
# See the documentation for the respective Revactor, ThreadSpawn,
|
30
|
+
# and ThreadPool classes for descriptions and recommendations for
|
31
|
+
# each of them.
|
32
|
+
def Rainbows!(&block)
|
33
|
+
block_given? or raise ArgumentError, "Rainbows! requires a block"
|
34
|
+
HttpServer.setup(block)
|
35
|
+
end
|
36
|
+
|
37
|
+
# maps models to default worker counts, default worker count numbers are
|
38
|
+
# pretty arbitrary and tuning them to your application and hardware is
|
39
|
+
# highly recommended
|
40
|
+
MODEL_WORKER_CONNECTIONS = {
|
41
|
+
:Base => 1, # this one can't change
|
42
|
+
:Revactor => 50,
|
43
|
+
:ThreadSpawn => 30,
|
44
|
+
:ThreadPool => 10,
|
45
|
+
}.each do |model, _|
|
46
|
+
u = model.to_s.gsub(/([a-z0-9])([A-Z0-9])/) { "#{$1}_#{$2.downcase!}" }
|
47
|
+
autoload model, "rainbows/#{u.downcase!}"
|
48
|
+
end
|
49
|
+
|
50
|
+
end
|
51
|
+
|
52
|
+
# inject the Rainbows! method into Unicorn::Configurator
|
53
|
+
Unicorn::Configurator.class_eval { include Rainbows }
|
@@ -0,0 +1,69 @@
|
|
1
|
+
# -*- encoding: binary -*-
|
2
|
+
|
3
|
+
module Rainbows
|
4
|
+
|
5
|
+
# base class for Rainbows concurrency models
|
6
|
+
module Base
|
7
|
+
|
8
|
+
include Unicorn
|
9
|
+
include Rainbows::Const
|
10
|
+
|
11
|
+
# write a response without caring if it went out or not for error
|
12
|
+
# messages.
|
13
|
+
# TODO: merge into Unicorn::HttpServer
|
14
|
+
def emergency_response(client, response_str)
|
15
|
+
client.write_nonblock(response_str) rescue nil
|
16
|
+
client.close rescue nil
|
17
|
+
end
|
18
|
+
|
19
|
+
# once a client is accepted, it is processed in its entirety here
|
20
|
+
# in 3 easy steps: read request, call app, write app response
|
21
|
+
def process_client(client)
|
22
|
+
buf = client.readpartial(CHUNK_SIZE)
|
23
|
+
hp = HttpParser.new
|
24
|
+
env = {}
|
25
|
+
remote_addr = TCPSocket === client ? client.peeraddr.last : LOCALHOST
|
26
|
+
|
27
|
+
begin
|
28
|
+
while ! hp.headers(env, buf)
|
29
|
+
buf << client.readpartial(CHUNK_SIZE)
|
30
|
+
end
|
31
|
+
|
32
|
+
env[RACK_INPUT] = 0 == hp.content_length ?
|
33
|
+
HttpRequest::NULL_IO :
|
34
|
+
Unicorn::TeeInput.new(client, env, hp, buf)
|
35
|
+
env[REMOTE_ADDR] = remote_addr
|
36
|
+
response = app.call(env.update(RACK_DEFAULTS))
|
37
|
+
|
38
|
+
if 100 == response.first.to_i
|
39
|
+
client.write(EXPECT_100_RESPONSE)
|
40
|
+
env.delete(HTTP_EXPECT)
|
41
|
+
response = app.call(env)
|
42
|
+
end
|
43
|
+
|
44
|
+
out = [ hp.keepalive? ? CONN_ALIVE : CONN_CLOSE ] if hp.headers?
|
45
|
+
HttpResponse.write(client, response, out)
|
46
|
+
end while hp.keepalive? and hp.reset.nil? and env.clear
|
47
|
+
client.close
|
48
|
+
# if we get any error, try to write something back to the client
|
49
|
+
# assuming we haven't closed the socket, but don't get hung up
|
50
|
+
# if the socket is already closed or broken. We'll always ensure
|
51
|
+
# the socket is closed at the end of this function
|
52
|
+
rescue EOFError,Errno::ECONNRESET,Errno::EPIPE,Errno::EINVAL,Errno::EBADF
|
53
|
+
emergency_response(client, ERROR_500_RESPONSE)
|
54
|
+
rescue HttpParserError # try to tell the client they're bad
|
55
|
+
buf.empty? or emergency_response(client, ERROR_400_RESPONSE)
|
56
|
+
rescue Object => e
|
57
|
+
emergency_response(client, ERROR_500_RESPONSE)
|
58
|
+
logger.error "Read error: #{e.inspect}"
|
59
|
+
logger.error e.backtrace.join("\n")
|
60
|
+
end
|
61
|
+
|
62
|
+
def self.included(klass)
|
63
|
+
HttpServer.constants.each do |x|
|
64
|
+
klass.const_set(x, HttpServer.const_get(x))
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
68
|
+
end
|
69
|
+
end
|
@@ -0,0 +1,24 @@
|
|
1
|
+
# -*- encoding: binary -*-
|
2
|
+
|
3
|
+
module Rainbows
|
4
|
+
|
5
|
+
module Const
|
6
|
+
RAINBOWS_VERSION = '0.1.0'
|
7
|
+
|
8
|
+
include Unicorn::Const
|
9
|
+
|
10
|
+
RACK_DEFAULTS = ::Unicorn::HttpRequest::DEFAULTS.merge({
|
11
|
+
|
12
|
+
# we need to observe many of the rules for thread-safety even
|
13
|
+
# with Revactor or Rev, so we're considered multithread-ed even
|
14
|
+
# when we're not technically...
|
15
|
+
"rack.multithread" => true,
|
16
|
+
"SERVER_SOFTWARE" => "Rainbows! #{RAINBOWS_VERSION}",
|
17
|
+
})
|
18
|
+
|
19
|
+
CONN_CLOSE = "Connection: close\r\n"
|
20
|
+
CONN_ALIVE = "Connection: keep-alive\r\n"
|
21
|
+
LOCALHOST = "127.0.0.1"
|
22
|
+
|
23
|
+
end
|
24
|
+
end
|