rainbows 0.5.0 → 0.6.0

Sign up to get free protection for your applications and to get access to all the features.
data/FAQ CHANGED
@@ -48,3 +48,42 @@ It depends on the size and amount of static files you're serving. If
48
48
  you're serving a lot of static files (especially large ones), then by
49
49
  all means use nginx. If not, then \Rainbows! is likely a "good enough"
50
50
  solution even if nginx will always outperform it in raw throughput.
51
+
52
+
53
+ === How do I support SSL?
54
+
55
+ If you need a streaming "rack.input" to do upload processing within your
56
+ Rack application, then {stunnel}[http://stunnel.org/] is required.
57
+ Otherwise, nginx is a perfectly good reverse proxy.
58
+
59
+ Refer to the {Unicorn FAQ}[http://unicorn.bogomips.org/FAQ.html] on how
60
+ to ensure redirects go to "https://" URLs.
61
+
62
+
63
+ === Is there a "rainbows_rails" command like there is "unicorn_rails"?
64
+
65
+ Only if you write one and plan to support it.
66
+
67
+ "unicorn_rails" was written primarily to support older versions of
68
+ Rails. Since \Rainbows! is designed for newer Rails, it can just use
69
+ a "config.ru" file like other Rack frameworks and applications.
70
+
71
+ For Rails 2.3.x and later, the following config.ru will work for you:
72
+
73
+ ENV["RAILS_ENV"] ||= ENV["RACK_ENV"]
74
+ require "config/environment"
75
+ use Rails::Rack::Static
76
+ run ActionController::Dispatcher.new
77
+
78
+ For older versions of Rails, the following config.ru will work:
79
+
80
+ ENV["RAILS_ENV"] ||= ENV["RACK_ENV"]
81
+ require 'config/boot'
82
+ require 'config/environment'
83
+ require 'unicorn/app/old_rails'
84
+ require 'unicorn/app/old_rails/static' # not needed with Unicorn 0.95+
85
+ use Unicorn::App::OldRails::Static
86
+ run Unicorn::App::OldRails.new
87
+
88
+ One thing to watch out for is that RAILS_ENV will not be set in the
89
+ environment for you, thus we set it to match RACK_ENV.
data/GIT-VERSION-GEN CHANGED
@@ -1,7 +1,7 @@
1
1
  #!/bin/sh
2
2
 
3
3
  GVF=GIT-VERSION-FILE
4
- DEF_VER=v0.5.0.GIT
4
+ DEF_VER=v0.6.0.GIT
5
5
 
6
6
  LF='
7
7
  '
data/GNUmakefile CHANGED
@@ -64,7 +64,7 @@ NEWS: GIT-VERSION-FILE
64
64
  $(rake) -s news_rdoc > $@+
65
65
  mv $@+ $@
66
66
 
67
- SINCE = 0.4.0
67
+ SINCE = 0.5.0
68
68
  ChangeLog: log_range = $(shell test -n "$(SINCE)" && echo v$(SINCE)..)
69
69
  ChangeLog: GIT-VERSION-FILE
70
70
  @echo "ChangeLog from $(GIT_URL) ($(SINCE)..$(GIT_VERSION))" > $@+
data/Rakefile CHANGED
@@ -24,6 +24,7 @@ def tags
24
24
  end
25
25
 
26
26
  cgit_url = "http://git.bogomips.org/cgit/rainbows.git"
27
+ git_url = ENV['GIT_URL'] || 'git://git.bogomips.org/rainbows.git'
27
28
 
28
29
  desc 'prints news as an Atom feed'
29
30
  task :news_atom do
@@ -88,8 +89,6 @@ desc "print release notes for Rubyforge"
88
89
  task :release_notes do
89
90
  require 'rubygems'
90
91
 
91
- git_url = ENV['GIT_URL'] || 'git://git.bogomips.org/rainbows.git'
92
-
93
92
  spec = Gem::Specification.load('rainbows.gemspec')
94
93
  puts spec.description.strip
95
94
  puts ""
@@ -117,3 +116,41 @@ task :publish_news do
117
116
  rf.login
118
117
  rf.post_news('rainbows', subject, body)
119
118
  end
119
+
120
+ desc "post to RAA"
121
+ task :raa_update do
122
+ require 'rubygems'
123
+ require 'net/http'
124
+ require 'net/netrc'
125
+ rc = Net::Netrc.locate('rainbows-raa') or abort "~/.netrc not found"
126
+ password = rc.password
127
+
128
+ s = Gem::Specification.load('rainbows.gemspec')
129
+ desc = [ s.description.strip ]
130
+ desc << ""
131
+ desc << "* #{s.email}"
132
+ desc << "* #{git_url}"
133
+ desc << "* #{cgit_url}"
134
+ desc = desc.join("\n")
135
+ uri = URI.parse('http://raa.ruby-lang.org/regist.rhtml')
136
+ form = {
137
+ :name => s.name,
138
+ :short_description => s.summary,
139
+ :version => s.version.to_s,
140
+ :status => 'experimental',
141
+ :owner => s.authors.first,
142
+ :email => s.email,
143
+ :category_major => 'Library',
144
+ :category_minor => 'Web',
145
+ :url => s.homepage,
146
+ :download => "http://rubyforge.org/frs/?group_id=8977",
147
+ :license => "Ruby's",
148
+ :description_style => 'Plain',
149
+ :description => desc,
150
+ :pass => password,
151
+ :submit => "Update",
152
+ }
153
+ res = Net::HTTP.post_form(uri, form)
154
+ p res
155
+ puts res.body
156
+ end
data/lib/rainbows.rb CHANGED
@@ -5,9 +5,20 @@ module Rainbows
5
5
 
6
6
  # global vars because class/instance variables are confusing me :<
7
7
  # this struct is only accessed inside workers and thus private to each
8
- G = Struct.new(:cur, :max, :logger, :alive, :app).new
9
8
  # G.cur may not be used the network concurrency model
10
- G.alive = true
9
+ class State < Struct.new(:alive,:m,:cur,:server,:tmp)
10
+ def tick
11
+ tmp.chmod(self.m = m == 0 ? 1 : 0)
12
+ alive && server.master_pid == Process.ppid or quit!
13
+ end
14
+
15
+ def quit!
16
+ self.alive = false
17
+ server.class.const_get(:LISTENERS).map! { |s| s.close rescue nil }
18
+ false
19
+ end
20
+ end
21
+ G = State.new(true, 0, 0)
11
22
 
12
23
  require 'rainbows/const'
13
24
  require 'rainbows/http_server'
@@ -56,6 +67,7 @@ module Rainbows
56
67
  :ThreadSpawn => 30,
57
68
  :ThreadPool => 10,
58
69
  :Rev => 50,
70
+ :RevThreadSpawn => 50,
59
71
  :EventMachine => 50,
60
72
  }.each do |model, _|
61
73
  u = model.to_s.gsub(/([a-z0-9])([A-Z0-9])/) { "#{$1}_#{$2.downcase!}" }
data/lib/rainbows/base.rb CHANGED
@@ -10,14 +10,6 @@ module Rainbows
10
10
  include Rainbows::Const
11
11
  G = Rainbows::G
12
12
 
13
- # write a response without caring if it went out or not for error
14
- # messages.
15
- # TODO: merge into Unicorn::HttpServer
16
- def emergency_response(client, response_str)
17
- client.write_nonblock(response_str) rescue nil
18
- client.close rescue nil
19
- end
20
-
21
13
  def listen_loop_error(e)
22
14
  G.alive or return
23
15
  logger.error "Unhandled listen loop exception #{e.inspect}."
@@ -26,20 +18,13 @@ module Rainbows
26
18
 
27
19
  def init_worker_process(worker)
28
20
  super(worker)
29
- G.cur = 0
30
- G.max = worker_connections
31
- G.logger = logger
32
- G.app = app
21
+ G.tmp = worker.tmp
33
22
 
34
23
  # we're don't use the self-pipe mechanism in the Rainbows! worker
35
24
  # since we don't defer reopening logs
36
25
  HttpServer::SELF_PIPE.each { |x| x.close }.clear
37
- trap(:USR1) { reopen_worker_logs(worker.nr) rescue nil }
38
- trap(:QUIT) do
39
- G.alive = false
40
- # closing anything we IO.select on will raise EBADF
41
- HttpServer::LISTENERS.map! { |s| s.close rescue nil }
42
- end
26
+ trap(:USR1) { reopen_worker_logs(worker.nr) }
27
+ trap(:QUIT) { G.quit! }
43
28
  [:TERM, :INT].each { |sig| trap(sig) { exit!(0) } } # instant shutdown
44
29
  logger.info "Rainbows! #@use worker_connections=#@worker_connections"
45
30
  end
@@ -79,23 +64,16 @@ module Rainbows
79
64
  # assuming we haven't closed the socket, but don't get hung up
80
65
  # if the socket is already closed or broken. We'll always ensure
81
66
  # the socket is closed at the end of this function
82
- rescue EOFError,Errno::ECONNRESET,Errno::EPIPE,Errno::EINVAL,Errno::EBADF
83
- emergency_response(client, ERROR_500_RESPONSE)
84
- rescue HttpParserError # try to tell the client they're bad
85
- buf.empty? or emergency_response(client, ERROR_400_RESPONSE)
86
- rescue Object => e
87
- emergency_response(client, ERROR_500_RESPONSE)
88
- logger.error "Read error: #{e.inspect}"
89
- logger.error e.backtrace.join("\n")
67
+ rescue => e
68
+ handle_error(client, e)
90
69
  end
91
70
 
92
- def join_threads(threads, worker)
93
- Rainbows::G.alive = false
71
+ def join_threads(threads)
72
+ G.quit!
94
73
  expire = Time.now + (timeout * 2.0)
95
- m = 0
96
74
  until (threads.delete_if { |thr| ! thr.alive? }).empty?
97
75
  threads.each { |thr|
98
- worker.tmp.chmod(m = 0 == m ? 1 : 0)
76
+ G.tick
99
77
  thr.join(1)
100
78
  break if Time.now >= expire
101
79
  }
@@ -3,7 +3,7 @@
3
3
  module Rainbows
4
4
 
5
5
  module Const
6
- RAINBOWS_VERSION = '0.5.0'
6
+ RAINBOWS_VERSION = '0.6.0'
7
7
 
8
8
  include Unicorn::Const
9
9
 
@@ -8,6 +8,10 @@ module Rainbows
8
8
  include Rainbows::Const
9
9
  G = Rainbows::G
10
10
 
11
+ def self.setup(klass)
12
+ klass.const_set(:APP, G.server.app)
13
+ end
14
+
11
15
  def post_init
12
16
  @remote_addr = ::TCPSocket === @_io ? @_io.peeraddr.last : LOCALHOST
13
17
  @env = {}
@@ -28,8 +32,8 @@ module Rainbows
28
32
  when HttpParserError # try to tell the client they're bad
29
33
  ERROR_400_RESPONSE
30
34
  else
31
- G.logger.error "Read error: #{e.inspect}"
32
- G.logger.error e.backtrace.join("\n")
35
+ G.server.logger.error "Read error: #{e.inspect}"
36
+ G.server.logger.error e.backtrace.join("\n")
33
37
  ERROR_500_RESPONSE
34
38
  end
35
39
  write(msg)
@@ -0,0 +1,80 @@
1
+ # -*- encoding: binary -*-
2
+ require 'thread' # for Queue
3
+ require 'rainbows/ev_core'
4
+
5
+ module Rainbows
6
+
7
+ # base module for mixed Thread + evented models like RevThreadSpawn
8
+ module EvThreadCore
9
+ include EvCore
10
+
11
+ def post_init
12
+ super
13
+ @lock = Mutex.new
14
+ @thread = nil
15
+ end
16
+
17
+ # we pass ourselves off as a Socket to Unicorn::TeeInput and this
18
+ # is the only method Unicorn::TeeInput requires from the socket
19
+ def readpartial(length, buf = "")
20
+ # we must modify the original buffer if there was one
21
+ length == 0 and return buf.replace("")
22
+
23
+ # wait on the main loop to feed us
24
+ while @tbuf.size == 0
25
+ @tbuf.write(@state.pop)
26
+ resume
27
+ end
28
+ buf.replace(@tbuf.read(length))
29
+ end
30
+
31
+ def app_spawn(input)
32
+ begin
33
+ @thread.nil? or @thread.join # only one thread per connection
34
+ env = @env.dup
35
+ alive, headers = @hp.keepalive?, @hp.headers?
36
+ @thread = Thread.new(self) do |client|
37
+ begin
38
+ env[REMOTE_ADDR] = @remote_addr
39
+ env[RACK_INPUT] = input || TeeInput.new(client, env, @hp, @buf)
40
+ response = APP.call(env.update(RACK_DEFAULTS))
41
+ if 100 == response.first.to_i
42
+ write(EXPECT_100_RESPONSE)
43
+ env.delete(HTTP_EXPECT)
44
+ response = APP.call(env)
45
+ end
46
+
47
+ alive &&= G.alive
48
+ out = [ alive ? CONN_ALIVE : CONN_CLOSE ] if headers
49
+ response_write(response, out)
50
+ rescue => e
51
+ handle_error(e) rescue nil
52
+ end
53
+ end
54
+ if alive # in case we pipeline
55
+ @hp.reset
56
+ redo if @hp.headers(@env.clear, @buf)
57
+ end
58
+ end while false
59
+ end
60
+
61
+ def on_read(data)
62
+ case @state
63
+ when :headers
64
+ @hp.headers(@env, @buf << data) or return
65
+ if 0 == @hp.content_length
66
+ app_spawn(HttpRequest::NULL_IO) # common case
67
+ else # nil or len > 0
68
+ @state, @tbuf = Queue.new, ::IO::Buffer.new
69
+ app_spawn(nil)
70
+ end
71
+ when Queue
72
+ pause
73
+ @state << data
74
+ end
75
+ rescue => e
76
+ handle_error(e)
77
+ end
78
+
79
+ end
80
+ end
@@ -53,18 +53,17 @@ module Rainbows
53
53
  def app_call
54
54
  begin
55
55
  (@env[RACK_INPUT] = @input).rewind
56
- alive = @hp.keepalive?
57
56
  @env[REMOTE_ADDR] = @remote_addr
58
57
  @env[ASYNC_CALLBACK] = method(:response_write)
59
58
 
60
- response = catch(:async) { G.app.call(@env.update(RACK_DEFAULTS)) }
59
+ response = catch(:async) { APP.call(@env.update(RACK_DEFAULTS)) }
61
60
 
62
61
  # too tricky to support pipelining with :async since the
63
62
  # second (pipelined) request could be a stuck behind a
64
63
  # long-running async response
65
64
  (response.nil? || -1 == response.first) and return @state = :close
66
65
 
67
- alive &&= G.alive
66
+ alive = @hp.keepalive? && G.alive
68
67
  out = [ alive ? CONN_ALIVE : CONN_CLOSE ] if @hp.headers?
69
68
  response_write(response, out, alive)
70
69
 
@@ -166,22 +165,17 @@ module Rainbows
166
165
 
167
166
  module Server
168
167
 
169
- def initialize(conns)
170
- @limit = Rainbows::G.max + HttpServer::LISTENERS.size
171
- @em_conns = conns
172
- end
173
-
174
168
  def close
175
169
  detach
176
170
  @io.close
177
171
  end
178
172
 
179
173
  def notify_readable
180
- return if @em_conns.size >= @limit
174
+ return if CUR.size >= MAX
181
175
  begin
182
176
  io = @io.accept_nonblock
183
177
  sig = EM.attach_fd(io.fileno, false)
184
- @em_conns[sig] = Client.new(sig, io)
178
+ CUR[sig] = Client.new(sig, io)
185
179
  rescue Errno::EAGAIN, Errno::ECONNABORTED
186
180
  end
187
181
  end
@@ -192,24 +186,26 @@ module Rainbows
192
186
  # given a INT, QUIT, or TERM signal)
193
187
  def worker_loop(worker)
194
188
  init_worker_process(worker)
195
- m = 0
196
189
 
197
190
  # enable them both, should be non-fatal if not supported
198
191
  EM.epoll
199
192
  EM.kqueue
200
193
  logger.info "EventMachine: epoll=#{EM.epoll?} kqueue=#{EM.kqueue?}"
194
+ Server.const_set(:MAX, G.server.worker_connections +
195
+ HttpServer::LISTENERS.size)
196
+ EvCore.setup(Client)
201
197
  EM.run {
202
198
  conns = EM.instance_variable_get(:@conns) or
203
199
  raise RuntimeError, "EM @conns instance variable not accessible!"
200
+ Server.const_set(:CUR, conns)
204
201
  EM.add_periodic_timer(1) do
205
- worker.tmp.chmod(m = 0 == m ? 1 : 0)
206
- unless G.alive
202
+ unless G.tick
207
203
  conns.each_value { |client| Client === client and client.quit }
208
204
  EM.stop if conns.empty? && EM.reactor_running?
209
205
  end
210
206
  end
211
207
  LISTENERS.map! do |s|
212
- EM.watch(s, Server, conns) { |c| c.notify_readable = true }
208
+ EM.watch(s, Server) { |c| c.notify_readable = true }
213
209
  end
214
210
  }
215
211
  end
@@ -30,7 +30,7 @@ module Rainbows
30
30
 
31
31
  body.each { |chunk| socket.write(chunk) }
32
32
  ensure
33
- body.respond_to?(:close) and body.close rescue nil
33
+ body.respond_to?(:close) and body.close
34
34
  end
35
35
  end
36
36
  end
@@ -5,21 +5,27 @@ module Rainbows
5
5
  class HttpServer < ::Unicorn::HttpServer
6
6
  include Rainbows
7
7
 
8
- @@instance = nil
9
-
10
8
  class << self
11
9
  def setup(block)
12
- @@instance.instance_eval(&block)
10
+ G.server.instance_eval(&block)
13
11
  end
14
12
  end
15
13
 
16
14
  def initialize(app, options)
17
- @@instance = self
15
+ G.server = self
18
16
  rv = super(app, options)
19
17
  defined?(@use) or use(:Base)
20
18
  @worker_connections ||= MODEL_WORKER_CONNECTIONS[@use]
21
19
  end
22
20
 
21
+ def reopen_worker_logs(worker_nr)
22
+ logger.info "worker=#{worker_nr} reopening logs..."
23
+ Unicorn::Util.reopen_logs
24
+ logger.info "worker=#{worker_nr} done reopening logs"
25
+ rescue
26
+ G.quit! # let the master reopen and refork us
27
+ end
28
+
23
29
  #:stopdoc:
24
30
  #
25
31
  # Add one second to the timeout since our fchmod heartbeat is less