rainbows 0.4.0 → 0.5.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.
@@ -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
- }