rainbows 0.4.0 → 0.5.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -1,7 +1,7 @@
1
1
  #!/bin/sh
2
2
 
3
3
  GVF=GIT-VERSION-FILE
4
- DEF_VER=v0.4.0.GIT
4
+ DEF_VER=v0.5.0.GIT
5
5
 
6
6
  LF='
7
7
  '
@@ -64,7 +64,7 @@ NEWS: GIT-VERSION-FILE
64
64
  $(rake) -s news_rdoc > $@+
65
65
  mv $@+ $@
66
66
 
67
- SINCE = 0.1.1
67
+ SINCE = 0.4.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))" > $@+
@@ -4,7 +4,7 @@ require 'unicorn/launcher'
4
4
  require 'rainbows'
5
5
  require 'optparse'
6
6
 
7
- env = "development"
7
+ ENV["RACK_ENV"] ||= "development"
8
8
  daemonize = false
9
9
  listeners = []
10
10
  options = { :listeners => listeners }
@@ -59,7 +59,7 @@ opts = OptionParser.new("", 24, ' ') do |opts|
59
59
 
60
60
  opts.on("-E", "--env ENVIRONMENT",
61
61
  "use ENVIRONMENT for defaults (default: development)") do |e|
62
- env = e
62
+ ENV["RACK_ENV"] = e
63
63
  end
64
64
 
65
65
  opts.on("-D", "--daemonize", "run daemonized in the background") do |d|
@@ -134,7 +134,7 @@ app = lambda do ||
134
134
  Object.const_get(File.basename(config, '.rb').capitalize)
135
135
  end
136
136
  pp({ :inner_app => inner_app }) if $DEBUG
137
- case env
137
+ case ENV["RACK_ENV"]
138
138
  when "development"
139
139
  Rack::Builder.new do
140
140
  use Rack::CommonLogger, $stderr
@@ -3,7 +3,7 @@
3
3
  module Rainbows
4
4
 
5
5
  module Const
6
- RAINBOWS_VERSION = '0.4.0'
6
+ RAINBOWS_VERSION = '0.5.0'
7
7
 
8
8
  include Unicorn::Const
9
9
 
@@ -14,7 +14,6 @@ module Rainbows
14
14
  @hp = HttpParser.new
15
15
  @state = :headers # [ :body [ :trailers ] ] :app_call :close
16
16
  @buf = ""
17
- @deferred_bodies = [] # for (fast) regular files only
18
17
  end
19
18
 
20
19
  # graceful exit, like SIGQUIT
@@ -34,16 +33,8 @@ module Rainbows
34
33
  ERROR_500_RESPONSE
35
34
  end
36
35
  write(msg)
37
- quit
38
- end
39
-
40
- def tmpio
41
- io = Util.tmpio
42
- def io.size
43
- # already sync=true at creation, so no need to flush before stat
44
- stat.size
45
- end
46
- io
36
+ ensure
37
+ quit
47
38
  end
48
39
 
49
40
  # TeeInput doesn't map too well to this right now...
@@ -63,7 +54,7 @@ module Rainbows
63
54
  write(EXPECT_100_RESPONSE)
64
55
  @env.delete(HTTP_EXPECT)
65
56
  end
66
- @input = len && len <= MAX_BODY ? StringIO.new("") : tmpio
57
+ @input = len && len <= MAX_BODY ? StringIO.new("") : Util.tmpio
67
58
  @hp.filter_body(@buf2 = @buf.dup, @buf)
68
59
  @input << @buf2
69
60
  on_read("")
@@ -78,7 +69,10 @@ module Rainbows
78
69
  on_read("")
79
70
  end
80
71
  when :trailers
81
- @hp.trailers(@env, @buf << data) and app_call
72
+ if @hp.trailers(@env, @buf << data)
73
+ app_call
74
+ @input.close if File === @input
75
+ end
82
76
  end
83
77
  rescue Object => e
84
78
  handle_error(e)
@@ -114,7 +114,7 @@ module Rainbows
114
114
  if do_chunk
115
115
  EM.watch(io, ResponseChunkPipe, self).notify_readable = true
116
116
  else
117
- EM.enable_proxy(EM.attach(io, ResponsePipe, self), self)
117
+ EM.enable_proxy(EM.attach(io, ResponsePipe, self), self, 16384)
118
118
  end
119
119
  else
120
120
  HttpResponse.write(self, response, out)
@@ -20,6 +20,21 @@ module Rainbows
20
20
  @worker_connections ||= MODEL_WORKER_CONNECTIONS[@use]
21
21
  end
22
22
 
23
+ #:stopdoc:
24
+ #
25
+ # Add one second to the timeout since our fchmod heartbeat is less
26
+ # precise (and must be more conservative) than Unicorn does. We
27
+ # handle many clients per process and can't chmod on every
28
+ # connection we accept without wasting cycles. That added to the
29
+ # fact that we let clients keep idle connections open for long
30
+ # periods of time means we have to chmod at a fixed interval.
31
+ alias_method :set_timeout, :timeout=
32
+ undef_method :timeout=
33
+ def timeout=(nr)
34
+ set_timeout(nr + 1)
35
+ end
36
+ #:startdoc:
37
+
23
38
  def use(*args)
24
39
  model = args.shift or return @use
25
40
  mod = begin
@@ -31,9 +46,9 @@ module Rainbows
31
46
  Module === mod or
32
47
  raise ArgumentError, "concurrency model #{model.inspect} not supported"
33
48
  extend(mod)
34
- Const::RACK_DEFAULTS['rainbows.model'] = @use = model
49
+ Const::RACK_DEFAULTS['rainbows.model'] = @use = model.to_sym
35
50
  Const::RACK_DEFAULTS['rack.multithread'] = !!(/Thread/ =~ model.to_s)
36
- case model
51
+ case @use
37
52
  when :Rev, :EventMachine
38
53
  Const::RACK_DEFAULTS['rainbows.autochunk'] = true
39
54
  end
@@ -1,6 +1,5 @@
1
1
  # -*- encoding: binary -*-
2
- require 'rev'
3
- Rev::VERSION >= '0.3.0' or abort 'rev >= 0.3.0 is required'
2
+ require 'rainbows/rev/heartbeat'
4
3
  require 'rainbows/ev_core'
5
4
 
6
5
  module Rainbows
@@ -35,6 +34,7 @@ module Rainbows
35
34
  G.cur += 1
36
35
  super(io)
37
36
  post_init
37
+ @deferred_bodies = [] # for (fast) regular files only
38
38
  end
39
39
 
40
40
  # queued, optional response bodies, it should only be unpollable "fast"
@@ -168,22 +168,6 @@ module Rainbows
168
168
  end
169
169
  end
170
170
 
171
- # This timer handles the fchmod heartbeat to prevent our master
172
- # from killing us.
173
- class Heartbeat < ::Rev::TimerWatcher
174
- G = Rainbows::G
175
-
176
- def initialize(tmp)
177
- @m, @tmp = 0, tmp
178
- super(1, true)
179
- end
180
-
181
- def on_timer
182
- @tmp.chmod(@m = 0 == @m ? 1 : 0)
183
- exit if (! G.alive && G.cur <= 0)
184
- end
185
- end
186
-
187
171
  # runs inside each forked worker, this sits around and waits
188
172
  # for connections and doesn't die until the parent dies (or is
189
173
  # given a INT, QUIT, or TERM signal)
@@ -0,0 +1,27 @@
1
+ # -*- encoding: binary -*-
2
+ require 'rev'
3
+ Rev::VERSION >= '0.3.0' or abort 'rev >= 0.3.0 is required'
4
+
5
+ module Rainbows
6
+ module Rev
7
+
8
+ # This class handles the Unicorn fchmod heartbeat mechanism
9
+ # in Rev-based concurrency models to prevent the master
10
+ # process from killing us unless we're blocked. This class
11
+ # will also detect and execute the graceful exit if triggered
12
+ # by SIGQUIT
13
+ class Heartbeat < ::Rev::TimerWatcher
14
+ # +tmp+ must be a +File+ that responds to +chmod+
15
+ def initialize(tmp)
16
+ @m, @tmp = 0, tmp
17
+ super(1, true)
18
+ end
19
+
20
+ def on_timer
21
+ @tmp.chmod(@m = 0 == @m ? 1 : 0)
22
+ exit if (! G.alive && G.cur <= 0)
23
+ end
24
+
25
+ end
26
+ end
27
+ end
@@ -34,7 +34,7 @@ module Rainbows
34
34
  pool.each do |thr|
35
35
  worker.tmp.chmod(m = 0 == m ? 1 : 0)
36
36
  # if any worker dies, something is serious wrong, bail
37
- thr.join(timeout) and break
37
+ thr.join(1) and break
38
38
  end
39
39
  end
40
40
  join_threads(pool, worker)
@@ -44,6 +44,11 @@ module Rainbows
44
44
  Thread.new {
45
45
  begin
46
46
  begin
47
+ # TODO: check if select() or accept() is a problem on large
48
+ # SMP systems under Ruby 1.9. Hundreds of native threads
49
+ # all working off the same socket could be a thundering herd
50
+ # problem. On the other hand, a thundering herd may not
51
+ # even incur as much overhead as an extra Mutex#synchronize
47
52
  ret = IO.select(LISTENERS, nil, nil, 1) and
48
53
  ret.first.each do |sock|
49
54
  begin
@@ -6,7 +6,7 @@
6
6
 
7
7
  DLEXT := so
8
8
  gems := rack-1.0.1
9
- # gems += unicorn-0.93.3 # installed via setup.rb
9
+ # gems += unicorn-0.93.5 # installed via setup.rb
10
10
  gems += rev-0.3.1 iobuffer-0.1.1
11
11
  gems += eventmachine-0.12.10
12
12
  gems += async_sinatra-0.1.5 sinatra-0.9.4
@@ -41,7 +41,7 @@ Gem::Specification.new do |s|
41
41
  s.test_files = test_files
42
42
 
43
43
  # we need Unicorn for the HTTP parser and process management
44
- s.add_dependency(%q<unicorn>, ["~> 0.93.4"])
44
+ s.add_dependency(%q<unicorn>, ["~> 0.94.0"])
45
45
 
46
46
  # Unicorn already depends on Rack
47
47
  # s.add_dependency(%q<rack>)
@@ -82,7 +82,7 @@ $(deps): dep_bin = $(lastword $(subst +, ,$@))
82
82
  $(deps):
83
83
  @which $(dep_bin) > $@.$(pid) 2>/dev/null || :
84
84
  @test -s $@.$(pid) || \
85
- { echo >&2 "E `$(dep_bin)' not found in PATH=$(PATH)"; exit 1; }
85
+ { echo >&2 "E '$(dep_bin)' not found in PATH=$(PATH)"; exit 1; }
86
86
  @mv $@.$(pid) $@
87
87
  dep: $(deps)
88
88
 
@@ -0,0 +1,4 @@
1
+ use Rack::ContentLength
2
+ run proc { |env|
3
+ [ 200, { "Content-Type" => "text/plain" }, [ ENV["RACK_ENV"] ] ]
4
+ }
@@ -0,0 +1,14 @@
1
+ use Rack::ContentLength
2
+ fifo = ENV['FIFO_PATH'] or abort "FIFO_PATH not defined"
3
+ headers = { 'Content-Type' => 'text/plain' }
4
+ run lambda { |env|
5
+ case env['PATH_INFO']
6
+ when "/block-forever"
7
+ # one of these should block forever
8
+ Process.kill(:STOP, $$)
9
+ ::File.open(fifo, "rb") { |fp| fp.syswrite("NEVER\n") }
10
+ [ 500, headers, [ "Should never get here\n" ] ]
11
+ else
12
+ [ 200, headers, [ "#$$\n" ] ]
13
+ end
14
+ }
@@ -0,0 +1,62 @@
1
+ #!/bin/sh
2
+ . ./test-lib.sh
3
+
4
+ t_plan 9 "heartbeat/timeout test for $model"
5
+
6
+ t_begin "setup and startup" && {
7
+ rainbows_setup $model
8
+ echo timeout 3 >> $unicorn_config
9
+ echo preload_app true >> $unicorn_config
10
+ FIFO_PATH=$fifo rainbows -D heartbeat-timeout.ru -c $unicorn_config
11
+ rainbows_wait_start
12
+ }
13
+
14
+ t_begin "read worker PID" && {
15
+ worker_pid=$(curl -sSf http://$listen/)
16
+ t_info "worker_pid=$worker_pid"
17
+ }
18
+
19
+ t_begin "sleep for a bit, ensure worker PID does not change" && {
20
+ sleep 4
21
+ test $(curl -sSf http://$listen/) -eq $worker_pid
22
+ }
23
+
24
+ t_begin "block the worker process to force it to die" && {
25
+ t0=$(date +%s)
26
+ err="$(curl -sSf http://$listen/block-forever 2>&1 || :)"
27
+ t1=$(date +%s)
28
+ elapsed=$(($t1 - $t0))
29
+ t_info "elapsed=$elapsed err=$err"
30
+ test x"$err" != x"Should never get here"
31
+ test x"$err" != x"$worker_pid"
32
+ }
33
+
34
+ t_begin "ensure timeout took 3-6 seconds" && {
35
+ test $elapsed -ge 3
36
+ test $elapsed -le 6 # give it some slack in case box is bogged down
37
+ }
38
+
39
+ t_begin "wait for new worker to start up" && {
40
+ test x = x"$(cat $fifo)"
41
+ }
42
+
43
+ t_begin "we get a fresh new worker process" && {
44
+ new_worker_pid=$(curl -sSf http://$listen/)
45
+ test $new_worker_pid -ne $worker_pid
46
+ }
47
+
48
+ t_begin "SIGSTOP and SIGCONT on rainbows master does not kill worker" && {
49
+ kill -STOP $rainbows_pid
50
+ sleep 4
51
+ kill -CONT $rainbows_pid
52
+ sleep 2
53
+ test $new_worker_pid -eq $(curl -sSf http://$listen/)
54
+ }
55
+
56
+ t_begin "stop server" && {
57
+ kill $rainbows_pid
58
+ }
59
+
60
+ dbgcat r_err
61
+
62
+ t_done
@@ -0,0 +1,40 @@
1
+ #!/bin/sh
2
+ . ./test-lib.sh
3
+
4
+ t_plan 4 'ensure ENV["RACK_ENV"] is set correctly for '$model
5
+
6
+ finish_checks () {
7
+ kill $rainbows_pid
8
+ test ! -s $curl_err
9
+ check_stderr
10
+ }
11
+
12
+ t_begin "setup" && {
13
+ rtmpfiles curl_out curl_err
14
+ }
15
+
16
+ t_begin "default RACK_ENV is 'development'" && {
17
+ rainbows_setup
18
+ rainbows -D -c $unicorn_config env_rack_env.ru
19
+ rainbows_wait_start
20
+ test x"$(curl -sSf http://$listen 2>$curl_err)" = x"development"
21
+ finish_checks
22
+ }
23
+
24
+ t_begin "RACK_ENV from process ENV is inherited" && {
25
+ rainbows_setup
26
+ ( RACK_ENV=production rainbows -D -c $unicorn_config env_rack_env.ru )
27
+ rainbows_wait_start
28
+ test x$(curl -sSf http://$listen 2>$curl_err) = x"production"
29
+ finish_checks
30
+ }
31
+
32
+ t_begin "RACK_ENV from -E is set" && {
33
+ rainbows_setup
34
+ rainbows -D -c $unicorn_config -E none env_rack_env.ru
35
+ rainbows_wait_start
36
+ test x$(curl -sSf http://$listen 2>$curl_err) = x"none"
37
+ finish_checks
38
+ }
39
+
40
+ t_done
@@ -35,7 +35,7 @@ t_begin "all responses identical" && {
35
35
  }
36
36
 
37
37
  t_begin "sha1 matches on-disk sha1" && {
38
- blob_sha1=$( expr "$(sha1sum < random_blob)" : '\([a-f0-9]\+\)')
38
+ blob_sha1=$( expr "$(sha1sum < random_blob)" : '\([a-f0-9]\{40\}\)')
39
39
  t_info blob_sha1=$blob_sha1
40
40
  test x"$blob_sha1" = x"$(sort < $curl_out | uniq)"
41
41
  }
@@ -2,7 +2,7 @@
2
2
  . ./test-lib.sh
3
3
  test -r random_blob || die "random_blob required, run with 'make $0'"
4
4
 
5
- t_plan 11 "input trailer test $model"
5
+ t_plan 13 "input trailer test $model"
6
6
 
7
7
  t_begin "setup and startup" && {
8
8
  rtmpfiles curl_out
@@ -11,6 +11,27 @@ t_begin "setup and startup" && {
11
11
  rainbows_wait_start
12
12
  }
13
13
 
14
+ t_begin "staggered trailer upload" && {
15
+ zero_md5="1B2M2Y8AsgTpgAmY7PhCfg=="
16
+ (
17
+ cat $fifo > $tmp &
18
+ printf 'PUT /s HTTP/1.1\r\n'
19
+ printf 'Host: example.com\r\n'
20
+ printf 'Transfer-Encoding: chunked\r\n'
21
+ printf 'Trailer: Content-MD5\r\n\r\n'
22
+ printf '0\r\nContent-MD5: '
23
+ sleep 5
24
+ printf '%s\r\n\r\n' $zero_md5
25
+ wait
26
+ echo ok > $ok
27
+ ) | socat - TCP:$listen > $fifo
28
+ test xok = x"$(cat $ok)"
29
+ }
30
+
31
+ t_begin "HTTP response is OK" && {
32
+ fgrep 'HTTP/1.1 200 OK' $tmp
33
+ }
34
+
14
35
  t_begin "upload small blob" && {
15
36
  (
16
37
  cat $fifo > $tmp &
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: rainbows
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.4.0
4
+ version: 0.5.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Rainbows! developers
@@ -9,7 +9,7 @@ autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
11
 
12
- date: 2009-10-27 00:00:00 -07:00
12
+ date: 2009-11-05 00:00:00 -08:00
13
13
  default_executable:
14
14
  dependencies:
15
15
  - !ruby/object:Gem::Dependency
@@ -20,7 +20,7 @@ dependencies:
20
20
  requirements:
21
21
  - - ~>
22
22
  - !ruby/object:Gem::Version
23
- version: 0.93.4
23
+ version: 0.94.0
24
24
  version:
25
25
  description: |-
26
26
  Rainbows! is a HTTP server for sleepy Rack applications. It is based on
@@ -47,6 +47,7 @@ extra_rdoc_files:
47
47
  - lib/rainbows/http_response.rb
48
48
  - lib/rainbows/http_server.rb
49
49
  - lib/rainbows/rev.rb
50
+ - lib/rainbows/rev/heartbeat.rb
50
51
  - lib/rainbows/revactor.rb
51
52
  - lib/rainbows/revactor/tee_input.rb
52
53
  - lib/rainbows/thread_pool.rb
@@ -90,6 +91,7 @@ files:
90
91
  - lib/rainbows/http_response.rb
91
92
  - lib/rainbows/http_server.rb
92
93
  - lib/rainbows/rev.rb
94
+ - lib/rainbows/rev/heartbeat.rb
93
95
  - lib/rainbows/revactor.rb
94
96
  - lib/rainbows/revactor/tee_input.rb
95
97
  - lib/rainbows/thread_pool.rb
@@ -109,6 +111,8 @@ files:
109
111
  - t/bin/utee
110
112
  - t/content-md5.ru
111
113
  - t/env.ru
114
+ - t/env_rack_env.ru
115
+ - t/heartbeat-timeout.ru
112
116
  - t/large-file-response.ru
113
117
  - t/my-tap-lib.sh
114
118
  - t/sha1.ru
@@ -125,16 +129,14 @@ files:
125
129
  - t/t0002-graceful.sh
126
130
  - t/t0002-parser-error.sh
127
131
  - t/t0003-reopen-logs.sh
132
+ - t/t0004-heartbeat-timeout.sh
128
133
  - t/t0005-large-file-response.sh
134
+ - t/t0006-process-rack-env.sh
129
135
  - t/t0100-rack-input-hammer.sh
130
136
  - t/t0101-rack-input-trailer.sh
131
137
  - t/t0200-async-response.sh
132
138
  - t/t0201-async-response-no-autochunk.sh
133
139
  - t/t0300-async_sinatra.sh
134
- - t/t1000.ru
135
- - t/t2000.ru
136
- - t/t3000.ru
137
- - t/t4000.ru
138
140
  - t/t9000-rack-app-pool.sh
139
141
  - t/t9000.ru
140
142
  - t/test-lib.sh
data/t/t1000.ru DELETED
@@ -1,10 +0,0 @@
1
- use Rack::ContentLength
2
- use Rack::ContentType
3
- run lambda { |env|
4
- sleep 1
5
- if env['rack.multithread'] && env['rainbows.model'] == :ThreadPool
6
- [ 200, {}, [ Thread.current.inspect << "\n" ] ]
7
- else
8
- raise "rack.multithread is not true"
9
- end
10
- }
data/t/t2000.ru DELETED
@@ -1,10 +0,0 @@
1
- use Rack::ContentLength
2
- use Rack::ContentType
3
- run lambda { |env|
4
- sleep 1
5
- if env['rack.multithread'] && env['rainbows.model'] == :ThreadSpawn
6
- [ 200, {}, [ Thread.current.inspect << "\n" ] ]
7
- else
8
- raise "rack.multithread is not true"
9
- end
10
- }
data/t/t3000.ru DELETED
@@ -1,10 +0,0 @@
1
- use Rack::ContentLength
2
- use Rack::ContentType
3
- run lambda { |env|
4
- Actor.sleep 1
5
- if env['rack.multithread'] == false && env['rainbows.model'] == :Revactor
6
- [ 200, {}, [ Thread.current.inspect << "\n" ] ]
7
- else
8
- raise "rack.multithread is true"
9
- end
10
- }
data/t/t4000.ru DELETED
@@ -1,9 +0,0 @@
1
- use Rack::ContentLength
2
- use Rack::ContentType
3
- run lambda { |env|
4
- if env['rack.multithread'] == false && env['rainbows.model'] == :Rev
5
- [ 200, {}, [ env.inspect << "\n" ] ]
6
- else
7
- raise "rack.multithread is true"
8
- end
9
- }