rainbows 0.96.0 → 0.97.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/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