rainbows 0.96.0 → 0.97.0

Sign up to get free protection for your applications and to get access to all the features.
data/FAQ CHANGED
@@ -2,7 +2,7 @@
2
2
 
3
3
  === Why is \Rainbows! a separate project from Unicorn?
4
4
 
5
- \Rainbows is for the odd, corner-case requests that Unicorn is poorly
5
+ \Rainbows! is for the odd, corner-case requests that Unicorn is poorly
6
6
  suited for. More scalable concurrency models introduce additional
7
7
  complexity that Unicorn users and developers are uncomfortable with for
8
8
  the common cases.
@@ -52,9 +52,14 @@ solution even if nginx will always outperform it in raw throughput.
52
52
 
53
53
  === How do I support SSL?
54
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.
55
+ If you need streaming "rack.input" to do on-the-fly upload processing
56
+ within your Rack application, then using an SSL proxy such as
57
+ {Pound}[http://www.apsis.ch/pound/] or {Stunnel}[http://stunnel.org/] is
58
+ required. Pound has built-in X-Forwarded-For support while Stunnel
59
+ requires a extra {patch}[http://haproxy.1wt.eu/download/patches/].
60
+
61
+ If you don't need streaming "rack.input", then nginx is a great HTTPS
62
+ reverse proxy.
58
63
 
59
64
  Refer to the {Unicorn FAQ}[http://unicorn.bogomips.org/FAQ.html] on how
60
65
  to ensure redirects go to "https://" URLs.
@@ -77,15 +82,15 @@ to set RAILS_ENV.
77
82
  For Rails 2.3.x, the following config.ru will work for you:
78
83
 
79
84
  ENV["RAILS_ENV"] ||= ENV["RACK_ENV"]
80
- require "config/environment"
85
+ require "#{::File.expand_path('config/environment')}"
81
86
  use Rails::Rack::Static
82
87
  run ActionController::Dispatcher.new
83
88
 
84
89
  For older versions of Rails, the following config.ru will work:
85
90
 
86
91
  ENV["RAILS_ENV"] ||= ENV["RACK_ENV"]
87
- require 'config/boot'
88
- require 'config/environment'
92
+ require "#{::File.expand_path('config/boot')}"
93
+ require "#{::File.expand_path('config/environment')}"
89
94
  require 'unicorn/app/old_rails'
90
95
  require 'unicorn/app/old_rails/static' # not needed with Unicorn 0.95+
91
96
  use Unicorn::App::OldRails::Static
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.96.0.GIT
4
+ DEF_VER=v0.97.0.GIT
5
5
 
6
6
  LF='
7
7
  '
data/GNUmakefile CHANGED
@@ -62,7 +62,7 @@ NEWS: GIT-VERSION-FILE
62
62
  $(RAKE) -s news_rdoc > $@+
63
63
  mv $@+ $@
64
64
 
65
- SINCE = 0.95.0
65
+ SINCE = 0.96.0
66
66
  ChangeLog: LOG_VERSION = \
67
67
  $(shell git rev-parse -q "$(GIT_VERSION)" >/dev/null 2>&1 && \
68
68
  echo $(GIT_VERSION) || git describe)
data/README CHANGED
@@ -67,7 +67,7 @@ network concurrency.
67
67
 
68
68
  == Applications
69
69
 
70
- \Rainbows is mainly designed for the odd things Unicorn sucks at:
70
+ \Rainbows! is mainly designed for the odd things Unicorn sucks at:
71
71
 
72
72
  * Web Sockets (via {Sunshowers}[http://rainbows.rubyforge.org/sunshowers/])
73
73
  * 3rd-party APIs (to services outside your control/LAN)
@@ -81,7 +81,7 @@ network concurrency.
81
81
  * Reverse AJAX
82
82
  * real-time upload processing (via {upr}[http://upr.bogomips.org/])
83
83
 
84
- \Rainbows can also be used to service slow clients directly even with
84
+ \Rainbows! can also be used to service slow clients directly even with
85
85
  fast applications.
86
86
 
87
87
  == License
@@ -97,7 +97,7 @@ details.
97
97
 
98
98
  == Install
99
99
 
100
- You may download the tarball from the Rainbows project page on Rubyforge
100
+ You may download the tarball from the \Rainbows! project page on Rubyforge
101
101
  and run setup.rb after unpacking it:
102
102
 
103
103
  http://rubyforge.org/frs/?group_id=8977
data/lib/rainbows.rb CHANGED
@@ -30,6 +30,12 @@ module Rainbows
30
30
  G = State.new(true, 0, 0, 5)
31
31
  O = {}
32
32
  class Response416 < RangeError; end
33
+
34
+ # map of numeric file descriptors to IO objects to avoid using IO.new
35
+ # and potentially causing race conditions when using /dev/fd/
36
+ FD_MAP = {}
37
+ FD_MAP.compare_by_identity if FD_MAP.respond_to?(:compare_by_identity)
38
+
33
39
  # :startdoc:
34
40
 
35
41
  require 'rainbows/const'
@@ -69,22 +75,6 @@ module Rainbows
69
75
  HttpServer.new(app, options).start.join
70
76
  end
71
77
 
72
- # returns nil if accept fails
73
- def sync_accept(sock) # :nodoc:
74
- rv = sock.accept
75
- rv.fcntl(Fcntl::F_SETFD, Fcntl::FD_CLOEXEC)
76
- rv
77
- rescue Errno::EAGAIN, Errno::ECONNABORTED, Errno::EINTR
78
- end
79
-
80
- # returns nil if accept fails
81
- def accept(sock) # :nodoc:
82
- rv = sock.accept_nonblock
83
- rv.fcntl(Fcntl::F_SETFD, Fcntl::FD_CLOEXEC)
84
- rv
85
- rescue Errno::EAGAIN, Errno::ECONNABORTED
86
- end
87
-
88
78
  # returns a string representing the address of the given client +io+
89
79
  # For local UNIX domain sockets, this will return a string referred
90
80
  # to by the (non-frozen) Unicorn::HttpRequest::LOCALHOST constant.
@@ -131,4 +121,6 @@ module Rainbows
131
121
  autoload :ByteSlice, 'rainbows/byte_slice'
132
122
  autoload :StreamFile, 'rainbows/stream_file'
133
123
  autoload :HttpResponse, 'rainbows/http_response' # deprecated
124
+ autoload :ThreadTimeout, 'rainbows/thread_timeout'
134
125
  end
126
+ require 'rainbows/acceptor'
@@ -0,0 +1,26 @@
1
+ # -*- encoding: binary -*-
2
+
3
+ # :enddoc:
4
+ require 'fcntl'
5
+
6
+ # this should make life easier for Zbatery if compatibility with
7
+ # fcntl-crippled platforms is required (or if FD_CLOEXEC is inherited)
8
+ # and we want to microptimize away fcntl(2) syscalls.
9
+ module Rainbows::Acceptor
10
+
11
+ # returns nil if accept fails
12
+ def sync_accept(sock)
13
+ rv = sock.accept
14
+ rv.fcntl(Fcntl::F_SETFD, Fcntl::FD_CLOEXEC)
15
+ rv
16
+ rescue Errno::EAGAIN, Errno::ECONNABORTED, Errno::EINTR
17
+ end
18
+
19
+ # returns nil if accept fails
20
+ def accept(sock)
21
+ rv = sock.accept_nonblock
22
+ rv.fcntl(Fcntl::F_SETFD, Fcntl::FD_CLOEXEC)
23
+ rv
24
+ rescue Errno::EAGAIN, Errno::ECONNABORTED
25
+ end
26
+ end
data/lib/rainbows/base.rb CHANGED
@@ -1,6 +1,6 @@
1
1
  # -*- encoding: binary -*-
2
2
 
3
- # base class for Rainbows concurrency models, this is currently used by
3
+ # base class for \Rainbows! concurrency models, this is currently used by
4
4
  # ThreadSpawn and ThreadPool models. Base is also its own
5
5
  # (non-)concurrency model which is basically Unicorn-with-keepalive, and
6
6
  # not intended for production use, as keepalive with a pure prefork
@@ -3,7 +3,7 @@
3
3
  module Rainbows
4
4
 
5
5
  module Const
6
- RAINBOWS_VERSION = '0.96.0'
6
+ RAINBOWS_VERSION = '0.97.0'
7
7
 
8
8
  include Unicorn::Const
9
9
 
@@ -14,7 +14,8 @@
14
14
  class Rainbows::DevFdResponse < Struct.new(:app)
15
15
 
16
16
  # :stopdoc:
17
- #
17
+ FD_MAP = Rainbows::FD_MAP
18
+
18
19
  # make this a no-op under Rubinius, it's pointless anyways
19
20
  # since Rubinius doesn't have IO.copy_stream
20
21
  def self.new(app)
@@ -37,6 +38,7 @@ class Rainbows::DevFdResponse < Struct.new(:app)
37
38
  headers = HeaderHash.new(headers)
38
39
  st = io.stat
39
40
  fileno = io.fileno
41
+ FD_MAP[fileno] = io
40
42
  if st.file?
41
43
  headers['Content-Length'] ||= st.size.to_s
42
44
  headers.delete('Transfer-Encoding')
@@ -70,7 +72,7 @@ class Rainbows::DevFdResponse < Struct.new(:app)
70
72
  end
71
73
 
72
74
  # remain Rack::Lint-compatible for people with wonky systems :P
73
- unless File.exist?("/dev/fd/0")
75
+ unless File.directory?("/dev/fd")
74
76
  alias to_path_orig to_path
75
77
  undef_method :to_path
76
78
  end
@@ -95,7 +95,8 @@ module Rainbows
95
95
  # long-running async response
96
96
  (response.nil? || -1 == response[0]) and return @state = :close
97
97
 
98
- em_write_response(response, alive = @hp.keepalive? && G.alive)
98
+ alive = @hp.keepalive? && G.alive && G.kato > 0
99
+ em_write_response(response, alive)
99
100
  if alive
100
101
  @env.clear
101
102
  @hp.reset
@@ -169,6 +170,7 @@ module Rainbows
169
170
  end
170
171
 
171
172
  module Server # :nodoc: all
173
+ include Rainbows::Acceptor
172
174
 
173
175
  def close
174
176
  detach
@@ -177,7 +179,7 @@ module Rainbows
177
179
 
178
180
  def notify_readable
179
181
  return if CUR.size >= MAX
180
- io = Rainbows.accept(@io) or return
182
+ io = accept(@io) or return
181
183
  sig = EM.attach_fd(io.fileno, false)
182
184
  CUR[sig] = CL.new(sig, io)
183
185
  end
@@ -54,6 +54,7 @@ module Rainbows::Fiber
54
54
  include Rainbows
55
55
  include Rainbows::Const
56
56
  include Rainbows::Response
57
+ include Rainbows::Acceptor
57
58
  FIO = Rainbows::Fiber::IO
58
59
 
59
60
  def to_io
@@ -72,7 +73,7 @@ module Rainbows::Fiber
72
73
 
73
74
  def on_readable
74
75
  return if G.cur >= MAX
75
- c = Rainbows.accept(@io) and ::Fiber.new { process(c) }.resume
76
+ c = accept(@io) and ::Fiber.new { process(c) }.resume
76
77
  end
77
78
 
78
79
  def process(io)
@@ -15,6 +15,7 @@ module Rainbows
15
15
 
16
16
  module FiberPool
17
17
  include Fiber::Base
18
+ include Rainbows::Acceptor
18
19
 
19
20
  def worker_loop(worker) # :nodoc:
20
21
  init_worker_process(worker)
@@ -29,7 +30,7 @@ module Rainbows
29
30
  begin
30
31
  schedule do |l|
31
32
  fib = pool.shift or break # let another worker process take it
32
- if io = Rainbows.accept(l)
33
+ if io = accept(l)
33
34
  fib.resume(Fiber::IO.new(io, fib))
34
35
  else
35
36
  pool << fib
@@ -12,6 +12,7 @@ module Rainbows
12
12
 
13
13
  module FiberSpawn
14
14
  include Fiber::Base
15
+ include Rainbows::Acceptor
15
16
 
16
17
  def worker_loop(worker) # :nodoc:
17
18
  init_worker_process(worker)
@@ -22,7 +23,7 @@ module Rainbows
22
23
  begin
23
24
  schedule do |l|
24
25
  break if G.cur >= limit
25
- io = Rainbows.accept(l) or next
26
+ io = accept(l) or next
26
27
  ::Fiber.new { process_client(fio.new(io, ::Fiber.current)) }.resume
27
28
  end
28
29
  rescue => e
@@ -34,6 +34,7 @@ module Rainbows::Response
34
34
 
35
35
  # called after forking
36
36
  def self.setup(klass)
37
+ Rainbows::G.kato == 0 and KEEP_ALIVE.replace(CLOSE)
37
38
  range_class = body_class = klass
38
39
  case Rainbows::Const::RACK_DEFAULTS['rainbows.model']
39
40
  when :WriterThreadSpawn
@@ -30,6 +30,12 @@
30
30
  module Rainbows::Response::Body # :nodoc:
31
31
  ALIASES = {}
32
32
 
33
+ FD_MAP = Rainbows::FD_MAP
34
+
35
+ def io_for_fd(fd)
36
+ FD_MAP.delete(fd) || IO.new(fd)
37
+ end
38
+
33
39
  # to_io is not part of the Rack spec, but make an exception here
34
40
  # since we can conserve path lookups and file descriptors.
35
41
  # \Rainbows! will never get here without checking for the existence
@@ -41,7 +47,7 @@ module Rainbows::Response::Body # :nodoc:
41
47
  # try to take advantage of Rainbows::DevFdResponse, calling File.open
42
48
  # is a last resort
43
49
  path = body.to_path
44
- path =~ %r{\A/dev/fd/(\d+)\z} ? IO.new($1.to_i) : File.open(path)
50
+ path =~ %r{\A/dev/fd/(\d+)\z} ? io_for_fd($1.to_i) : File.open(path)
45
51
  end
46
52
  end
47
53
 
@@ -58,7 +58,7 @@ module Rainbows
58
58
 
59
59
  def next!
60
60
  @deferred = nil
61
- on_write_complete
61
+ enable_write_watcher
62
62
  end
63
63
 
64
64
  def timeout?
@@ -170,6 +170,7 @@ module Rainbows
170
170
  def on_close
171
171
  close_deferred
172
172
  CONN.delete(self)
173
+ KATO.delete(self)
173
174
  end
174
175
 
175
176
  end # module Client
@@ -7,12 +7,12 @@ require 'rainbows/rev/heartbeat'
7
7
  module Rainbows
8
8
  module Rev
9
9
  class Server < ::Rev::IO
10
- G = Rainbows::G
10
+ include Rainbows::Acceptor
11
11
  # CL and MAX will be defined in the corresponding worker loop
12
12
 
13
13
  def on_readable
14
14
  return if CONN.size >= MAX
15
- io = Rainbows.accept(@_io) and CL.new(io).attach(LOOP)
15
+ io = accept(@_io) and CL.new(io).attach(LOOP)
16
16
  end
17
17
  end # class Server
18
18
 
@@ -11,7 +11,7 @@ module Rainbows
11
11
  class Heartbeat < ::Rev::TimerWatcher
12
12
 
13
13
  def on_timer
14
- if (ot = G.kato) > 0
14
+ if (ot = G.kato) >= 0
15
15
  ot = Time.now - ot
16
16
  KATO.delete_if { |client, time| time < ot and client.timeout? }
17
17
  end
@@ -2,25 +2,20 @@
2
2
  # :enddoc:
3
3
  require 'rainbows/rev'
4
4
 
5
- module Rainbows
5
+ class Rainbows::Rev::Master < Rev::AsyncWatcher
6
6
 
7
- module Rev
8
- class Master < ::Rev::AsyncWatcher
9
-
10
- def initialize(queue)
11
- super()
12
- @queue = queue
13
- end
7
+ def initialize(queue)
8
+ super()
9
+ @queue = queue
10
+ end
14
11
 
15
- def <<(output)
16
- @queue << output
17
- signal
18
- end
12
+ def <<(output)
13
+ @queue << output
14
+ signal
15
+ end
19
16
 
20
- def on_signal
21
- client, response = @queue.pop
22
- client.response_write(response)
23
- end
24
- end
17
+ def on_signal
18
+ client, response = @queue.pop
19
+ client.response_write(response)
25
20
  end
26
21
  end
@@ -62,7 +62,7 @@ module Rainbows::Revactor
62
62
  if hp.headers?
63
63
  headers = HH.new(headers)
64
64
  range = make_range!(env, status, headers) and status = range.shift
65
- env = false unless hp.keepalive? && G.alive
65
+ env = false unless hp.keepalive? && G.alive && G.kato > 0
66
66
  headers[CONNECTION] = env ? KEEP_ALIVE : CLOSE
67
67
  client.write(response_header(status, headers))
68
68
  end
@@ -24,6 +24,7 @@ module Rainbows
24
24
  module ThreadPool
25
25
 
26
26
  include Base
27
+ include Rainbows::Acceptor
27
28
 
28
29
  def worker_loop(worker) # :nodoc:
29
30
  init_worker_process(worker)
@@ -44,7 +45,7 @@ module Rainbows
44
45
  def sync_worker # :nodoc:
45
46
  s = LISTENERS[0]
46
47
  begin
47
- c = Rainbows.sync_accept(s) and process_client(c)
48
+ c = sync_accept(s) and process_client(c)
48
49
  rescue => e
49
50
  Error.listen_loop(e)
50
51
  end while G.alive
@@ -58,7 +59,7 @@ module Rainbows
58
59
  # problem. On the other hand, a thundering herd may not
59
60
  # even incur as much overhead as an extra Mutex#synchronize
60
61
  ret = IO.select(LISTENERS, nil, nil, 1) and ret[0].each do |s|
61
- s = Rainbows.accept(s) and process_client(s)
62
+ s = accept(s) and process_client(s)
62
63
  end
63
64
  rescue Errno::EINTR
64
65
  rescue => e
@@ -18,6 +18,7 @@ module Rainbows
18
18
 
19
19
  module ThreadSpawn
20
20
  include Base
21
+ include Rainbows::Acceptor
21
22
 
22
23
  def accept_loop(klass) #:nodoc:
23
24
  lock = Mutex.new
@@ -36,7 +37,7 @@ module Rainbows
36
37
  # CPU during I/O wait, CPU cycles that can be better used
37
38
  # by other worker _processes_.
38
39
  sleep(0.01)
39
- elsif c = Rainbows.sync_accept(l)
40
+ elsif c = sync_accept(l)
40
41
  klass.new(c) do |c|
41
42
  begin
42
43
  lock.synchronize { G.cur += 1 }
@@ -0,0 +1,94 @@
1
+ # -*- encoding: binary -*-
2
+ require 'thread'
3
+
4
+ # Soft timeout middleware for thread-based concurrency models in \Rainbows!
5
+ # This timeout only includes application dispatch, and will not take into
6
+ # account the (rare) response bodies that are dynamically generated while
7
+ # they are being written out to the client.
8
+ #
9
+ # In your rackup config file (config.ru), the following line will
10
+ # cause execution to timeout in 1.5 seconds.
11
+ #
12
+ # use Rainbows::ThreadTimeout, :timeout => 1.5
13
+ # run MyApplication.new
14
+ #
15
+ # You may also specify a threshold, so the timeout does not take
16
+ # effect until there are enough active clients. It does not make
17
+ # sense to set a +:threshold+ higher or equal to the
18
+ # +worker_connections+ \Rainbows! configuration parameter.
19
+ # You may specify a negative threshold to be an absolute
20
+ # value relative to the +worker_connections+ parameter, thus
21
+ # if you specify a threshold of -1, and have 100 worker_connections,
22
+ # ThreadTimeout will only activate when there are 99 active requests.
23
+ #
24
+ # use Rainbows::ThreadTimeout, :timeout => 1.5, :threshold => -1
25
+ # run MyApplication.new
26
+ #
27
+ # This middleware only affects elements below it in the stack, so
28
+ # it can be configured to ignore certain endpoints or middlewares.
29
+ #
30
+ # Timed-out requests will cause this middleware to return with a
31
+ # "408 Request Timeout" response.
32
+
33
+ class Rainbows::ThreadTimeout < Struct.new(:app, :timeout,
34
+ :threshold, :watchdog,
35
+ :active, :lock)
36
+
37
+ # :stopdoc:
38
+ class ExecutionExpired < ::Exception
39
+ end
40
+
41
+ def initialize(app, opts)
42
+ timeout = opts[:timeout]
43
+ Numeric === timeout or
44
+ raise TypeError, "timeout=#{timeout.inspect} is not numeric"
45
+
46
+ if threshold = opts[:threshold]
47
+ Integer === threshold or
48
+ raise TypeError, "threshold=#{threshold.inspect} is not an integer"
49
+ threshold == 0 and
50
+ raise ArgumentError, "threshold=0 does not make sense"
51
+ threshold < 0 and
52
+ threshold += Rainbows::G.server.worker_connections
53
+ end
54
+ super(app, timeout, threshold, nil, {}, Mutex.new)
55
+ end
56
+
57
+ def call(env)
58
+ lock.synchronize do
59
+ start_watchdog unless watchdog
60
+ active[Thread.current] = Time.now + timeout
61
+ end
62
+ begin
63
+ app.call(env)
64
+ ensure
65
+ lock.synchronize { active.delete(Thread.current) }
66
+ end
67
+ rescue ExecutionExpired
68
+ [ 408, { 'Content-Type' => 'text/plain', 'Content-Length' => '0' }, [] ]
69
+ end
70
+
71
+ def start_watchdog
72
+ self.watchdog = Thread.new do
73
+ begin
74
+ if next_wake = lock.synchronize { active.values }.min
75
+ next_wake -= Time.now
76
+ sleep(next_wake) if next_wake > 0
77
+ else
78
+ sleep(timeout)
79
+ end
80
+
81
+ # "active.size" is atomic in MRI 1.8 and 1.9
82
+ next if threshold && active.size < threshold
83
+
84
+ now = Time.now
85
+ lock.synchronize do
86
+ active.delete_if do |thread, time|
87
+ time >= now and thread.raise(ExecutionExpired).nil?
88
+ end
89
+ end
90
+ end while true
91
+ end
92
+ end
93
+ # :startdoc:
94
+ end
data/rainbows.gemspec CHANGED
@@ -46,7 +46,7 @@ Gem::Specification.new do |s|
46
46
  # we need Unicorn for the HTTP parser and process management
47
47
  # Unicorn 0.991.0 handles config.ru when started outside of
48
48
  # the prespecified working_directory
49
- s.add_dependency(%q<unicorn>, [">= 1.1.1", "< 2.0.0"])
49
+ s.add_dependency(%q<unicorn>, [">= 1.1.3", "< 2.0.0"])
50
50
  s.add_development_dependency(%q<isolate>, "~> 2.1.0")
51
51
 
52
52
  # optional runtime dependencies depending on configuration
@@ -0,0 +1,30 @@
1
+ # must be run without Rack::Lint since that clobbers to_path
2
+ class MyMiddleware < Struct.new(:app)
3
+ class Body < Struct.new(:body, :to_path)
4
+ def each(&block); body.each(&block); end
5
+ def close
6
+ c = body.respond_to?(:close)
7
+ ::File.open(ENV['fifo'], 'wb') do |fp|
8
+ fp.syswrite("CLOSING #{body.inspect} #{to_path} (#{c})\n")
9
+ end
10
+ body.close if c
11
+ end
12
+ end
13
+
14
+ def call(env)
15
+ status, headers, body = app.call(env)
16
+ body.respond_to?(:to_path) and body = Body.new(body, body.to_path)
17
+ [ status, headers, body ]
18
+ end
19
+ end
20
+ use MyMiddleware
21
+ use Rainbows::DevFdResponse
22
+ run(lambda { |env|
23
+ io = IO.popen('cat random_blob', 'rb')
24
+ [ 200,
25
+ {
26
+ 'Content-Length' => ::File.stat('random_blob').size.to_s,
27
+ 'Content-Type' => 'application/octet-stream',
28
+ },
29
+ io ]
30
+ })
@@ -4,7 +4,7 @@ t_plan 4 "proper handling of onenine encoding for $model"
4
4
 
5
5
  t_begin "setup and startup" && {
6
6
  rainbows_setup $model
7
- rainbows -D ./t0016.rb -c $unicorn_config
7
+ rainbows -E none -D ./t0016.rb -c $unicorn_config
8
8
  rainbows_wait_start
9
9
  expect_sha1=8ff79d8115f9fe38d18be858c66aa08a1cc27a66
10
10
  }
@@ -0,0 +1,43 @@
1
+ #!/bin/sh
2
+ . ./test-lib.sh
3
+ t_plan 6 "keepalive_timeout 0 tests for $model"
4
+
5
+ t_begin "setup and start" && {
6
+ rainbows_setup $model 2 0
7
+ grep 'keepalive_timeout 0' $unicorn_config
8
+ rainbows -D env.ru -c $unicorn_config
9
+ rainbows_wait_start
10
+ }
11
+
12
+ t_begin 'check server responds with Connection: close' && {
13
+ curl -sSfi http://$listen/ | grep 'Connection: close'
14
+ }
15
+
16
+ t_begin "send keepalive response that does not expect close" && {
17
+ req='GET / HTTP/1.1\r\nHost: example.com\r\n\r\n'
18
+ t0=$(date +%s)
19
+ (
20
+ cat $fifo > $tmp &
21
+ printf "$req"
22
+ wait
23
+ date +%s > $ok
24
+ ) | socat - TCP:$listen > $fifo
25
+ now="$(cat $ok)"
26
+ elapsed=$(( $now - $t0 ))
27
+ t_info "elapsed=$elapsed (expecting <=3)"
28
+ test $elapsed -le 3
29
+ }
30
+
31
+ t_begin "'Connection: close' header set" && {
32
+ grep 'Connection: close' $tmp
33
+ }
34
+
35
+ t_begin "killing succeeds" && {
36
+ kill $rainbows_pid
37
+ }
38
+
39
+ t_begin "check stderr" && {
40
+ check_stderr
41
+ }
42
+
43
+ t_done
@@ -0,0 +1,101 @@
1
+ #!/bin/sh
2
+ . ./test-lib.sh
3
+ if ! test -d /dev/fd
4
+ then
5
+ t_info "skipping $T since /dev/fd is required"
6
+ exit 0
7
+ fi
8
+
9
+ t_plan 16 "close pipe to_path response for $model"
10
+
11
+ t_begin "setup and startup" && {
12
+ rtmpfiles err out http_fifo sub_ok
13
+ rainbows_setup $model
14
+ export fifo
15
+ rainbows -E none -D close-pipe-to_path-response.ru -c $unicorn_config
16
+ rainbows_wait_start
17
+ }
18
+
19
+ t_begin "read random blob sha1" && {
20
+ random_blob_sha1=$(rsha1 < random_blob)
21
+ }
22
+
23
+ t_begin "start FIFO reader" && {
24
+ cat $fifo > $out &
25
+ }
26
+
27
+ t_begin "single request matches" && {
28
+ sha1=$(curl -sSfv 2> $err http://$listen/ | rsha1)
29
+ test -n "$sha1"
30
+ test x"$sha1" = x"$random_blob_sha1"
31
+ }
32
+
33
+ t_begin "body.close called" && {
34
+ wait # for cat $fifo
35
+ grep CLOSING $out || die "body.close not logged"
36
+ }
37
+
38
+ t_begin "start FIFO reader for abortive HTTP/1.1 request" && {
39
+ cat $fifo > $out &
40
+ }
41
+
42
+ t_begin "send abortive HTTP/1.1 request" && {
43
+ rm -f $ok
44
+ (
45
+ printf 'GET /random_blob HTTP/1.1\r\nHost: example.com\r\n\r\n'
46
+ dd bs=4096 count=1 < $http_fifo >/dev/null
47
+ echo ok > $ok
48
+ ) | socat - TCP:$listen > $http_fifo || :
49
+ test xok = x$(cat $ok)
50
+ }
51
+
52
+ t_begin "body.close called for aborted HTTP/1.1 request" && {
53
+ wait # for cat $fifo
54
+ grep CLOSING $out || die "body.close not logged"
55
+ }
56
+
57
+ t_begin "start FIFO reader for abortive HTTP/1.0 request" && {
58
+ cat $fifo > $out &
59
+ }
60
+
61
+ t_begin "send abortive HTTP/1.0 request" && {
62
+ rm -f $ok
63
+ (
64
+ printf 'GET /random_blob HTTP/1.0\r\n\r\n'
65
+ dd bs=4096 count=1 < $http_fifo >/dev/null
66
+ echo ok > $ok
67
+ ) | socat - TCP:$listen > $http_fifo || :
68
+ test xok = x$(cat $ok)
69
+ }
70
+
71
+ t_begin "body.close called for aborted HTTP/1.0 request" && {
72
+ wait # for cat $fifo
73
+ grep CLOSING $out || die "body.close not logged"
74
+ }
75
+
76
+ t_begin "start FIFO reader for abortive HTTP/0.9 request" && {
77
+ cat $fifo > $out &
78
+ }
79
+
80
+ t_begin "send abortive HTTP/0.9 request" && {
81
+ rm -f $ok
82
+ (
83
+ printf 'GET /random_blob\r\n'
84
+ dd bs=4096 count=1 < $http_fifo >/dev/null
85
+ echo ok > $ok
86
+ ) | socat - TCP:$listen > $http_fifo || :
87
+ test xok = x$(cat $ok)
88
+ }
89
+
90
+ t_begin "body.close called for aborted HTTP/0.9 request" && {
91
+ wait # for cat $fifo
92
+ grep CLOSING $out || die "body.close not logged"
93
+ }
94
+
95
+ t_begin "shutdown server" && {
96
+ kill -QUIT $rainbows_pid
97
+ }
98
+
99
+ t_begin "check stderr" && check_stderr
100
+
101
+ t_done
@@ -0,0 +1,36 @@
1
+ #!/bin/sh
2
+ . ./test-lib.sh
3
+ case $model in
4
+ ThreadSpawn|ThreadPool|RevThreadSpawn|RevThreadPool) ;;
5
+ *) t_info "$0 is only compatible with Thread*"; exit 0 ;;
6
+ esac
7
+
8
+ t_plan 5 "ThreadTimeout Rack middleware test for $model"
9
+
10
+ t_begin "configure and start" && {
11
+ rtmpfiles curl_err
12
+ rainbows_setup
13
+ rainbows -D t9100.ru -c $unicorn_config
14
+ rainbows_wait_start
15
+ }
16
+
17
+ t_begin "normal request should not timeout" && {
18
+ test x"HI" = x"$(curl -sSf http://$listen/ 2>> $curl_err)"
19
+ }
20
+
21
+ t_begin "sleepy request times out with 408" && {
22
+ rm -f $ok
23
+ curl -sSf http://$listen/2 2>> $curl_err || > $ok
24
+ test -e $ok
25
+ grep 408 $curl_err
26
+ }
27
+
28
+ t_begin "kill server" && {
29
+ kill $rainbows_pid
30
+ }
31
+
32
+ t_begin "no errors in Rainbows! stderr" && {
33
+ check_stderr
34
+ }
35
+
36
+ t_done
data/t/t9100.ru ADDED
@@ -0,0 +1,9 @@
1
+ use Rack::ContentLength
2
+ use Rack::ContentType, 'text/plain'
3
+ use Rainbows::ThreadTimeout, :timeout => 1
4
+ run lambda { |env|
5
+ if env["PATH_INFO"] =~ %r{/([\d\.]+)\z}
6
+ Rainbows.sleep($1.to_f)
7
+ end
8
+ [ 200, [], [ "HI\n" ] ]
9
+ }
@@ -0,0 +1,62 @@
1
+ #!/bin/sh
2
+ . ./test-lib.sh
3
+ case $model in
4
+ ThreadSpawn|ThreadPool|RevThreadSpawn|RevThreadPool) ;;
5
+ *) t_info "$0 is only compatible with Thread*"; exit 0 ;;
6
+ esac
7
+
8
+ t_plan 6 "ThreadTimeout Rack middleware test for $model"
9
+
10
+ t_begin "configure and start" && {
11
+ rtmpfiles curl_err curl_out
12
+ rainbows_setup $model 10
13
+ rainbows -D t9101.ru -c $unicorn_config
14
+ rainbows_wait_start
15
+ }
16
+
17
+ t_begin "normal request should not timeout" && {
18
+ test x"HI" = x"$(curl -sSf http://$listen/ 2>> $curl_err)"
19
+ }
20
+
21
+ t_begin "8 sleepy requests do not time out" && {
22
+ > $curl_err
23
+ for i in 1 2 3 4 5 6 7 8
24
+ do
25
+ curl --no-buffer -sSf http://$listen/3 \
26
+ 2>> $curl_err >> $curl_out &
27
+ done
28
+ wait
29
+ test 8 -eq "$(wc -l < $curl_out)"
30
+ test xHI = x"$(sort < $curl_out | uniq)"
31
+ }
32
+
33
+ t_begin "9 sleepy requests do time out" && {
34
+ > $curl_err
35
+ > $curl_out
36
+ for i in 1 2 3 4 5 6 7 8 9
37
+ do
38
+ rtmpfiles curl_err_$i
39
+ curl -sSf --no-buffer \
40
+ http://$listen/3 2>> ${curl_err}_${i} >> $curl_out &
41
+ done
42
+ wait
43
+ if test -s $curl_out
44
+ then
45
+ dbgcat curl_out
46
+ die "$curl_out should be empty"
47
+ fi
48
+ for i in 1 2 3 4 5 6 7 8 9
49
+ do
50
+ grep 408 ${curl_err}_${i}
51
+ done
52
+ }
53
+
54
+ t_begin "kill server" && {
55
+ kill $rainbows_pid
56
+ }
57
+
58
+ t_begin "no errors in Rainbows! stderr" && {
59
+ check_stderr
60
+ }
61
+
62
+ t_done
data/t/t9101.ru ADDED
@@ -0,0 +1,9 @@
1
+ use Rack::ContentLength
2
+ use Rack::ContentType, 'text/plain'
3
+ use Rainbows::ThreadTimeout, :timeout => 1, :threshold => -1
4
+ run lambda { |env|
5
+ if env["PATH_INFO"] =~ %r{/([\d\.]+)\z}
6
+ Rainbows.sleep($1.to_f)
7
+ end
8
+ [ 200, [], [ "HI\n" ] ]
9
+ }
data/t/test_isolate.rb CHANGED
@@ -15,7 +15,7 @@ $stdout.reopen($stderr)
15
15
 
16
16
  Isolate.now!(opts) do
17
17
  gem 'rack', '1.1.0' # Cramp currently requires ~> 1.1.0
18
- gem 'unicorn', '1.1.1'
18
+ gem 'unicorn', '1.1.3'
19
19
  gem 'kcar', '0.1.1'
20
20
 
21
21
  if engine == "ruby"
metadata CHANGED
@@ -1,13 +1,13 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: rainbows
3
3
  version: !ruby/object:Gem::Version
4
- hash: 415
4
+ hash: 411
5
5
  prerelease: false
6
6
  segments:
7
7
  - 0
8
- - 96
8
+ - 97
9
9
  - 0
10
- version: 0.96.0
10
+ version: 0.97.0
11
11
  platform: ruby
12
12
  authors:
13
13
  - Rainbows! hackers
@@ -15,7 +15,7 @@ autorequire:
15
15
  bindir: bin
16
16
  cert_chain: []
17
17
 
18
- date: 2010-08-03 00:00:00 +00:00
18
+ date: 2010-08-28 00:00:00 +00:00
19
19
  default_executable:
20
20
  dependencies:
21
21
  - !ruby/object:Gem::Dependency
@@ -41,12 +41,12 @@ dependencies:
41
41
  requirements:
42
42
  - - ">="
43
43
  - !ruby/object:Gem::Version
44
- hash: 17
44
+ hash: 21
45
45
  segments:
46
46
  - 1
47
47
  - 1
48
- - 1
49
- version: 1.1.1
48
+ - 3
49
+ version: 1.1.3
50
50
  - - <
51
51
  - !ruby/object:Gem::Version
52
52
  hash: 15
@@ -87,6 +87,7 @@ extra_rdoc_files:
87
87
  - DEPLOY
88
88
  - FAQ
89
89
  - lib/rainbows.rb
90
+ - lib/rainbows/acceptor.rb
90
91
  - lib/rainbows/actor_spawn.rb
91
92
  - lib/rainbows/app_pool.rb
92
93
  - lib/rainbows/base.rb
@@ -138,6 +139,7 @@ extra_rdoc_files:
138
139
  - lib/rainbows/tee_input.rb
139
140
  - lib/rainbows/thread_pool.rb
140
141
  - lib/rainbows/thread_spawn.rb
142
+ - lib/rainbows/thread_timeout.rb
141
143
  - lib/rainbows/writer_thread_pool.rb
142
144
  - lib/rainbows/writer_thread_spawn.rb
143
145
  - LICENSE
@@ -178,6 +180,7 @@ files:
178
180
  - Test_Suite
179
181
  - bin/rainbows
180
182
  - lib/rainbows.rb
183
+ - lib/rainbows/acceptor.rb
181
184
  - lib/rainbows/actor_spawn.rb
182
185
  - lib/rainbows/app_pool.rb
183
186
  - lib/rainbows/base.rb
@@ -229,6 +232,7 @@ files:
229
232
  - lib/rainbows/tee_input.rb
230
233
  - lib/rainbows/thread_pool.rb
231
234
  - lib/rainbows/thread_spawn.rb
235
+ - lib/rainbows/thread_timeout.rb
232
236
  - lib/rainbows/writer_thread_pool.rb
233
237
  - lib/rainbows/writer_thread_spawn.rb
234
238
  - local.mk.sample
@@ -250,6 +254,7 @@ files:
250
254
  - t/bin/unused_listen
251
255
  - t/bin/utee
252
256
  - t/close-pipe-response.ru
257
+ - t/close-pipe-to_path-response.ru
253
258
  - t/content-md5.ru
254
259
  - t/cramp/README
255
260
  - t/cramp/rainsocket.ru
@@ -302,6 +307,7 @@ files:
302
307
  - t/t0015-working_directory.sh
303
308
  - t/t0016-onenine-encoding-is-tricky.sh
304
309
  - t/t0016.rb
310
+ - t/t0017-keepalive-timeout-zero.sh
305
311
  - t/t0020-large-sendfile-response.sh
306
312
  - t/t0021-sendfile-wrap-to_path.sh
307
313
  - t/t0022-copy_stream-byte-range.sh
@@ -309,6 +315,7 @@ files:
309
315
  - t/t0024-pipelined-sendfile-response.sh
310
316
  - t/t0030-fast-pipe-response.sh
311
317
  - t/t0031-close-pipe-response.sh
318
+ - t/t0032-close-pipe-to_path-response.sh
312
319
  - t/t0034-pipelined-pipe-response.sh
313
320
  - t/t0100-rack-input-hammer-chunked.sh
314
321
  - t/t0100-rack-input-hammer-content-length.sh
@@ -332,6 +339,10 @@ files:
332
339
  - t/t9001.ru
333
340
  - t/t9002-server-token.sh
334
341
  - t/t9002.ru
342
+ - t/t9100-thread-timeout.sh
343
+ - t/t9100.ru
344
+ - t/t9101-thread-timeout-threshold.sh
345
+ - t/t9101.ru
335
346
  - t/test-lib.sh
336
347
  - t/test_isolate.rb
337
348
  - t/worker-follows-master-to-death.ru