rainbows 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
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