rainbows 0.95.1 → 0.96.0

Sign up to get free protection for your applications and to get access to all the features.
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