rainbows 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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