rainbows 0.95.1 → 0.96.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.
Files changed (57) hide show
  1. data/Documentation/comparison.haml +1 -1
  2. data/GIT-VERSION-GEN +1 -1
  3. data/GNUmakefile +2 -1
  4. data/Rakefile +16 -3
  5. data/Static_Files +11 -11
  6. data/TODO +1 -6
  7. data/lib/rainbows.rb +3 -2
  8. data/lib/rainbows/base.rb +12 -9
  9. data/lib/rainbows/const.rb +2 -4
  10. data/lib/rainbows/dev_fd_response.rb +16 -13
  11. data/lib/rainbows/error.rb +2 -0
  12. data/lib/rainbows/ev_core.rb +13 -0
  13. data/lib/rainbows/event_machine.rb +85 -128
  14. data/lib/rainbows/event_machine/response_chunk_pipe.rb +25 -0
  15. data/lib/rainbows/event_machine/response_pipe.rb +30 -0
  16. data/lib/rainbows/event_machine/try_defer.rb +27 -0
  17. data/lib/rainbows/fiber/body.rb +6 -4
  18. data/lib/rainbows/fiber/io.rb +4 -4
  19. data/lib/rainbows/fiber/rev.rb +16 -8
  20. data/lib/rainbows/http_response.rb +5 -6
  21. data/lib/rainbows/response.rb +37 -22
  22. data/lib/rainbows/response/body.rb +19 -16
  23. data/lib/rainbows/response/range.rb +34 -0
  24. data/lib/rainbows/rev.rb +2 -1
  25. data/lib/rainbows/rev/client.rb +105 -77
  26. data/lib/rainbows/rev/deferred_chunk_response.rb +16 -0
  27. data/lib/rainbows/rev/deferred_response.rb +16 -24
  28. data/lib/rainbows/rev/sendfile.rb +4 -13
  29. data/lib/rainbows/rev/thread.rb +3 -12
  30. data/lib/rainbows/rev_thread_pool.rb +2 -2
  31. data/lib/rainbows/revactor.rb +16 -9
  32. data/lib/rainbows/revactor/body.rb +42 -0
  33. data/lib/rainbows/revactor/proxy.rb +55 -0
  34. data/lib/rainbows/sendfile.rb +12 -14
  35. data/lib/rainbows/stream_file.rb +3 -3
  36. data/lib/rainbows/writer_thread_pool.rb +12 -12
  37. data/lib/rainbows/writer_thread_spawn.rb +6 -7
  38. data/t/GNUmakefile +2 -2
  39. data/t/close-pipe-response.ru +25 -0
  40. data/t/cramp/rainsocket.ru +1 -1
  41. data/t/fast-pipe-response.ru +10 -0
  42. data/t/file-wrap-to_path.ru +24 -0
  43. data/t/t0015-working_directory.sh +5 -1
  44. data/t/t0020-large-sendfile-response.sh +3 -3
  45. data/t/t0021-sendfile-wrap-to_path.sh +108 -0
  46. data/t/t0022-copy_stream-byte-range.sh +139 -0
  47. data/t/t0023-sendfile-byte-range.sh +63 -0
  48. data/t/t0024-pipelined-sendfile-response.sh +89 -0
  49. data/t/t0030-fast-pipe-response.sh +63 -0
  50. data/t/t0031-close-pipe-response.sh +96 -0
  51. data/t/t0034-pipelined-pipe-response.sh +87 -0
  52. data/t/t0105-rack-input-limit-bigger.sh +5 -2
  53. data/t/t0500-cramp-streaming.sh +0 -1
  54. data/t/t0501-cramp-rainsocket.sh +1 -0
  55. data/t/t9000-rack-app-pool.sh +1 -1
  56. data/t/test_isolate.rb +1 -0
  57. metadata +29 -5
@@ -0,0 +1,16 @@
1
+ # -*- encoding: binary -*-
2
+ # :enddoc:
3
+ #
4
+ # this is class is specific to Rev for proxying IO-derived objects
5
+ class Rainbows::Rev::DeferredChunkResponse < Rainbows::Rev::DeferredResponse
6
+ def on_read(data)
7
+ @client.write("#{data.size.to_s(16)}\r\n")
8
+ @client.write(data)
9
+ @client.write("\r\n")
10
+ end
11
+
12
+ def on_close
13
+ @client.write("0\r\n\r\n")
14
+ super
15
+ end
16
+ end
@@ -1,28 +1,20 @@
1
1
  # -*- encoding: binary -*-
2
2
  # :enddoc:
3
- module Rainbows
4
- module Rev
3
+ #
4
+ # this is class is specific to Rev for writing large static files
5
+ # or proxying IO-derived objects
6
+ class Rainbows::Rev::DeferredResponse < ::Rev::IO
7
+ def initialize(io, client, body)
8
+ super(io)
9
+ @client, @body = client, body
10
+ end
5
11
 
6
- # this is class is specific to Rev for writing large static files
7
- # or proxying IO-derived objects
8
- class DeferredResponse < ::Rev::IO
9
- include Rainbows::Const
10
- def initialize(io, client, do_chunk, body)
11
- super(io)
12
- @client, @do_chunk, @body = client, do_chunk, body
13
- end
12
+ def on_read(data)
13
+ @client.write(data)
14
+ end
14
15
 
15
- def on_read(data)
16
- @do_chunk and @client.write(sprintf("%x\r\n", data.size))
17
- @client.write(data)
18
- @do_chunk and @client.write("\r\n")
19
- end
20
-
21
- def on_close
22
- @do_chunk and @client.write("0\r\n\r\n")
23
- @client.quit
24
- @body.respond_to?(:close) and @body.close
25
- end
26
- end # class DeferredResponse
27
- end # module Rev
28
- end # module Rainbows
16
+ def on_close
17
+ @client.next! if @client.attached? # attached? is false if write fails
18
+ @body.respond_to?(:close) and @body.close
19
+ end
20
+ end
@@ -2,25 +2,16 @@
2
2
  # :enddoc:
3
3
  module Rainbows::Rev::Sendfile
4
4
  if IO.method_defined?(:sendfile_nonblock)
5
- F = Rainbows::StreamFile
6
-
7
- def to_sendfile(io)
8
- F[0, io]
9
- end
10
-
11
- def rev_sendfile(body)
12
- body.offset += @_io.sendfile_nonblock(body, body.offset, 0x10000)
5
+ def rev_sendfile(sf) # +sf+ is a Rainbows::StreamFile object
6
+ sf.offset += (n = @_io.sendfile_nonblock(sf, sf.offset, sf.count))
7
+ 0 == (sf.count -= n) and raise EOFError
13
8
  enable_write_watcher
14
9
  rescue Errno::EAGAIN
15
10
  enable_write_watcher
16
11
  end
17
12
  else
18
- def to_sendfile(io)
19
- io
20
- end
21
-
22
13
  def rev_sendfile(body)
23
- write(body.sysread(0x4000))
14
+ write(body.to_io.sysread(0x4000))
24
15
  end
25
16
  end
26
17
  end
@@ -13,29 +13,20 @@ module Rainbows
13
13
 
14
14
  def app_call
15
15
  KATO.delete(self)
16
- disable
16
+ disable if enabled?
17
17
  @env[RACK_INPUT] = @input
18
18
  app_dispatch # must be implemented by subclass
19
19
  end
20
20
 
21
21
  # this is only called in the master thread
22
22
  def response_write(response)
23
- enable
24
23
  alive = @hp.keepalive? && G.alive
25
- out = [ alive ? CONN_ALIVE : CONN_CLOSE ] if @hp.headers?
26
- rev_write_response(response, out)
27
- return quit unless alive && G.alive
24
+ rev_write_response(response, alive)
25
+ return quit unless alive && :close != @state
28
26
 
29
27
  @env.clear
30
28
  @hp.reset
31
29
  @state = :headers
32
- # keepalive requests are always body-less, so @input is unchanged
33
- if @hp.headers(@env, @buf)
34
- @input = HttpRequest::NULL_IO
35
- app_call
36
- else
37
- KATO[self] = Time.now
38
- end
39
30
  end
40
31
 
41
32
  # fails-safe application dispatch, we absolutely cannot
@@ -15,8 +15,8 @@ module Rainbows
15
15
  # slow clients and applications with medium-to-slow response times
16
16
  # (I/O bound), but less suitable for sleepy applications.
17
17
  #
18
- # Ruby 1.8 users are strongly advised to use Rev >= 0.3.2 to get
19
- # usable performance.
18
+ # This concurrency model is designed for Ruby 1.9, and Ruby 1.8
19
+ # users are NOT advised to use this due to high CPU usage.
20
20
 
21
21
  module RevThreadPool
22
22
 
@@ -22,6 +22,8 @@ module Rainbows::Revactor
22
22
  # :stopdoc:
23
23
  RD_ARGS = {}
24
24
 
25
+ autoload :Proxy, 'rainbows/revactor/proxy'
26
+
25
27
  include Rainbows::Base
26
28
  LOCALHOST = Unicorn::HttpRequest::LOCALHOST
27
29
  TCP = ::Revactor::TCP::Socket
@@ -41,7 +43,6 @@ module Rainbows::Revactor
41
43
  buf = client.read(*rd_args)
42
44
  hp = HttpParser.new
43
45
  env = {}
44
- alive = true
45
46
 
46
47
  begin
47
48
  buf << client.read(*rd_args) until hp.headers(env, buf)
@@ -50,18 +51,23 @@ module Rainbows::Revactor
50
51
  env[RACK_INPUT] = 0 == hp.content_length ?
51
52
  NULL_IO : TeeInput.new(PartialSocket.new(client), env, hp, buf)
52
53
  env[REMOTE_ADDR] = remote_addr
53
- response = app.call(env.update(RACK_DEFAULTS))
54
+ status, headers, body = app.call(env.update(RACK_DEFAULTS))
54
55
 
55
- if 100 == response[0].to_i
56
+ if 100 == status.to_i
56
57
  client.write(EXPECT_100_RESPONSE)
57
58
  env.delete(HTTP_EXPECT)
58
- response = app.call(env)
59
+ status, headers, body = app.call(env)
59
60
  end
60
61
 
61
- alive = hp.keepalive? && G.alive
62
- out = [ alive ? CONN_ALIVE : CONN_CLOSE ] if hp.headers?
63
- write_response(client, response, out)
64
- end while alive and hp.reset.nil? and env.clear
62
+ if hp.headers?
63
+ headers = HH.new(headers)
64
+ range = make_range!(env, status, headers) and status = range.shift
65
+ env = false unless hp.keepalive? && G.alive
66
+ headers[CONNECTION] = env ? KEEP_ALIVE : CLOSE
67
+ client.write(response_header(status, headers))
68
+ end
69
+ write_body(client, body, range)
70
+ end while env && env.clear && hp.reset.nil?
65
71
  rescue ::Revactor::TCP::ReadError
66
72
  rescue => e
67
73
  Rainbows::Error.write(io, e)
@@ -74,7 +80,8 @@ module Rainbows::Revactor
74
80
  # given a INT, QUIT, or TERM signal)
75
81
  def worker_loop(worker) #:nodoc:
76
82
  init_worker_process(worker)
77
- self.class.__send__(:alias_method, :write_body, :write_body_each)
83
+ require 'rainbows/revactor/body'
84
+ self.class.__send__(:include, Rainbows::Revactor::Body)
78
85
  RD_ARGS[:timeout] = G.kato if G.kato > 0
79
86
  nr = 0
80
87
  limit = worker_connections
@@ -0,0 +1,42 @@
1
+ # -*- encoding: binary -*-
2
+ # :enddoc:
3
+ module Rainbows::Revactor::Body
4
+ # TODO non-blocking splice(2) under Linux
5
+ ALIASES = {
6
+ :write_body_stream => :write_body_each
7
+ }
8
+
9
+ if IO.method_defined?(:sendfile_nonblock)
10
+ def write_body_file(client, body, range)
11
+ sock = client.instance_variable_get(:@_io)
12
+ pfx = ::Revactor::TCP::Socket === client ? :tcp : :unix
13
+ write_complete = T[:"#{pfx}_write_complete", client]
14
+ closed = T[:"#{pfx}_closed", client]
15
+ offset, count = range ? range : [ 0, body.stat.size ]
16
+ begin
17
+ offset += (n = sock.sendfile_nonblock(body, offset, count))
18
+ rescue Errno::EAGAIN
19
+ # The @_write_buffer is empty at this point, trigger the
20
+ # on_readable method which in turn triggers on_write_complete
21
+ # even though nothing was written
22
+ client.controller = Actor.current
23
+ client.__send__(:enable_write_watcher)
24
+ Actor.receive do |filter|
25
+ filter.when(write_complete) {}
26
+ filter.when(closed) { raise Errno::EPIPE }
27
+ end
28
+ retry
29
+ rescue EOFError
30
+ break
31
+ end while (count -= n) > 0
32
+ end
33
+ else
34
+ ALIASES[:write_body] = :write_body_each
35
+ end
36
+
37
+ def self.included(klass)
38
+ ALIASES.each do |new_method, orig_method|
39
+ klass.__send__(:alias_method, new_method, orig_method)
40
+ end
41
+ end
42
+ end
@@ -0,0 +1,55 @@
1
+ # -*- encoding: binary -*-
2
+ # :enddoc:
3
+ # Generic IO wrapper for proxying pipe and socket objects
4
+ # this behaves more like Rainbows::Fiber::IO than anything,
5
+ # making it highly suitable for proxying data from pipes/sockets
6
+ class Rainbows::Revactor::Proxy < Rev::IO
7
+ def initialize(io)
8
+ @receiver = Actor.current
9
+ super(io)
10
+ attach(Rev::Loop.default)
11
+ end
12
+
13
+ def close
14
+ if @_io
15
+ super
16
+ @_io = nil
17
+ end
18
+ end
19
+
20
+ def each(&block)
21
+ # when yield-ing, Revactor::TCP#write may raise EOFError
22
+ # (instead of Errno::EPIPE), so we need to limit the rescue
23
+ # to just readpartial and let EOFErrors during yield bubble up
24
+ begin
25
+ buf = readpartial(INPUT_SIZE)
26
+ rescue EOFError
27
+ break
28
+ end while yield(buf) || true
29
+ end
30
+
31
+ # this may return more than the specified length, Rainbows! won't care...
32
+ def readpartial(length)
33
+ @receiver = Actor.current
34
+ enable if attached? && ! enabled?
35
+
36
+ Actor.receive do |filter|
37
+ filter.when(T[:rainbows_io_input, self]) do |_, _, data|
38
+ return data
39
+ end
40
+
41
+ filter.when(T[:rainbows_io_closed, self]) do
42
+ raise EOFError, "connection closed"
43
+ end
44
+ end
45
+ end
46
+
47
+ def on_close
48
+ @receiver << T[:rainbows_io_closed, self]
49
+ end
50
+
51
+ def on_read(data)
52
+ @receiver << T[:rainbows_io_input, self, data ]
53
+ disable
54
+ end
55
+ end
@@ -1,6 +1,4 @@
1
1
  # -*- encoding: binary -*-
2
- module Rainbows
3
-
4
2
  # This middleware handles X-\Sendfile headers generated by applications
5
3
  # or middlewares down the stack. It should be placed at the top
6
4
  # (outermost layer) of the middleware stack to avoid having its
@@ -48,42 +46,42 @@ module Rainbows
48
46
  # ]
49
47
  # }
50
48
 
51
- class Sendfile < Struct.new(:app)
52
-
53
- # :stopdoc:
54
- HH = Rack::Utils::HeaderHash
55
- # :startdoc:
49
+ class Rainbows::Sendfile < Struct.new(:app)
56
50
 
57
51
  # Body wrapper, this allows us to fall back gracefully to
58
52
  # +each+ in case a given concurrency model does not optimize
59
53
  # +to_path+ calls.
60
54
  class Body < Struct.new(:to_path) # :nodoc: all
55
+ CONTENT_LENGTH = 'Content-Length'.freeze
61
56
 
62
57
  def self.new(path, headers)
63
- unless headers['Content-Length']
58
+ unless headers[CONTENT_LENGTH]
64
59
  stat = File.stat(path)
65
- headers['Content-Length'] = stat.size.to_s if stat.file?
60
+ headers[CONTENT_LENGTH] = stat.size.to_s if stat.file?
66
61
  end
67
62
  super(path)
68
63
  end
69
64
 
70
65
  # fallback in case our +to_path+ doesn't get handled for whatever reason
71
66
  def each(&block)
72
- File.open(to_path, 'rb') do |fp|
73
- buf = ''
67
+ buf = ''
68
+ File.open(to_path) do |fp|
74
69
  yield buf while fp.read(0x4000, buf)
75
70
  end
76
71
  end
77
72
  end
78
73
 
74
+ # :stopdoc:
75
+ HH = Rack::Utils::HeaderHash
76
+ X_SENDFILE = 'X-Sendfile'
77
+ # :startdoc:
78
+
79
79
  def call(env) # :nodoc:
80
80
  status, headers, body = app.call(env)
81
81
  headers = HH.new(headers)
82
- if path = headers.delete('X-Sendfile')
82
+ if path = headers.delete(X_SENDFILE)
83
83
  body = Body.new(path, headers) unless body.respond_to?(:to_path)
84
84
  end
85
85
  [ status, headers, body ]
86
86
  end
87
87
  end
88
-
89
- end
@@ -5,10 +5,10 @@
5
5
  # models. We always maintain our own file offsets in userspace because
6
6
  # because sendfile() implementations offer pread()-like idempotency for
7
7
  # concurrency (multiple clients can read the same underlying file handle).
8
- class Rainbows::StreamFile < Struct.new(:offset, :to_io)
9
-
8
+ class Rainbows::StreamFile < Struct.new(:offset, :count, :to_io, :body)
10
9
  def close
11
- to_io.close
10
+ body.close if body.respond_to?(:close)
11
+ to_io.close unless to_io.closed?
12
12
  self.to_io = nil
13
13
  end
14
14
  end
@@ -46,33 +46,33 @@ module Rainbows
46
46
  end
47
47
  end
48
48
 
49
- module Response # :nodoc:
50
- def write_body(qclient, body)
51
- qclient.q << [ qclient.to_io, :body, body ]
52
- end
53
- end
54
-
55
49
  @@nr = 0
56
50
  @@q = nil
57
51
 
52
+ def async_write_body(qclient, body, range)
53
+ qclient.q << [ qclient.to_io, :body, body, range ]
54
+ end
55
+
58
56
  def process_client(client) # :nodoc:
59
57
  @@nr += 1
60
- super(QueueSocket[client, @@q[@@nr %= @@q.size]])
58
+ super(QueueSocket.new(client, @@q[@@nr %= @@q.size]))
61
59
  end
62
60
 
63
- def worker_loop(worker) # :nodoc:
64
- Rainbows::Response.setup(self.class)
61
+ def init_worker_process(worker)
62
+ super
65
63
  self.class.__send__(:alias_method, :sync_write_body, :write_body)
66
- self.class.__send__(:include, Response)
64
+ WriterThreadPool.__send__(:alias_method, :write_body, :async_write_body)
65
+ end
67
66
 
67
+ def worker_loop(worker) # :nodoc:
68
68
  # we have multiple, single-thread queues since we don't want to
69
69
  # interleave writes from the same client
70
70
  qp = (1..worker_connections).map do |n|
71
71
  QueuePool.new(1) do |response|
72
72
  begin
73
- io, arg1, arg2 = response
73
+ io, arg1, arg2, arg3 = response
74
74
  case arg1
75
- when :body then sync_write_body(io, arg2)
75
+ when :body then sync_write_body(io, arg2, arg3)
76
76
  when :close then io.close unless io.closed?
77
77
  else
78
78
  io.write(arg1)
@@ -51,9 +51,9 @@ module Rainbows
51
51
  self.thr = Thread.new(to_io, q) do |io, q|
52
52
  while response = q.shift
53
53
  begin
54
- arg1, arg2 = response
54
+ arg1, arg2, arg3 = response
55
55
  case arg1
56
- when :body then write_body(io, arg2)
56
+ when :body then write_body(io, arg2, arg3)
57
57
  when :close
58
58
  io.close unless io.closed?
59
59
  break
@@ -73,8 +73,8 @@ module Rainbows
73
73
  (self.q ||= queue_writer) << buf
74
74
  end
75
75
 
76
- def queue_body(body)
77
- (self.q ||= queue_writer) << [ :body, body ]
76
+ def queue_body(body, range)
77
+ (self.q ||= queue_writer) << [ :body, body, range ]
78
78
  end
79
79
 
80
80
  def close
@@ -90,8 +90,8 @@ module Rainbows
90
90
  end
91
91
  end
92
92
 
93
- def write_body(my_sock, body) # :nodoc:
94
- my_sock.queue_body(body)
93
+ def write_body(my_sock, body, range) # :nodoc:
94
+ my_sock.queue_body(body, range)
95
95
  end
96
96
 
97
97
  def process_client(client) # :nodoc:
@@ -100,7 +100,6 @@ module Rainbows
100
100
 
101
101
  def worker_loop(worker) # :nodoc:
102
102
  MySocket.const_set(:MAX, worker_connections)
103
- Rainbows::Response.setup(MySocket)
104
103
  super(worker) # accept loop from Unicorn
105
104
  CUR.delete_if do |t,q|
106
105
  q << nil