rainbows 2.1.0 → 3.0.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 (71) hide show
  1. data/GIT-VERSION-GEN +1 -1
  2. data/GNUmakefile +2 -3
  3. data/Rakefile +5 -2
  4. data/lib/rainbows.rb +72 -54
  5. data/lib/rainbows/base.rb +7 -9
  6. data/lib/rainbows/client.rb +25 -4
  7. data/lib/rainbows/const.rb +1 -1
  8. data/lib/rainbows/coolio.rb +6 -3
  9. data/lib/rainbows/coolio/client.rb +78 -57
  10. data/lib/rainbows/coolio/core.rb +1 -4
  11. data/lib/rainbows/coolio/heartbeat.rb +2 -3
  12. data/lib/rainbows/coolio/master.rb +3 -2
  13. data/lib/rainbows/coolio/{deferred_chunk_response.rb → response_chunk_pipe.rb} +1 -2
  14. data/lib/rainbows/coolio/{deferred_response.rb → response_pipe.rb} +1 -1
  15. data/lib/rainbows/coolio/thread_client.rb +4 -6
  16. data/lib/rainbows/coolio_fiber_spawn.rb +1 -1
  17. data/lib/rainbows/coolio_thread_pool.rb +1 -1
  18. data/lib/rainbows/coolio_thread_pool/watcher.rb +2 -4
  19. data/lib/rainbows/coolio_thread_spawn.rb +1 -1
  20. data/lib/rainbows/dev_fd_response.rb +2 -2
  21. data/lib/rainbows/error.rb +5 -7
  22. data/lib/rainbows/ev_core.rb +20 -7
  23. data/lib/rainbows/event_machine.rb +6 -5
  24. data/lib/rainbows/event_machine/client.rb +46 -53
  25. data/lib/rainbows/event_machine/response_pipe.rb +2 -3
  26. data/lib/rainbows/fiber/base.rb +5 -5
  27. data/lib/rainbows/fiber/body.rb +4 -13
  28. data/lib/rainbows/fiber/coolio/heartbeat.rb +1 -3
  29. data/lib/rainbows/fiber/coolio/server.rb +4 -7
  30. data/lib/rainbows/fiber_pool.rb +1 -1
  31. data/lib/rainbows/fiber_spawn.rb +2 -2
  32. data/lib/rainbows/http_parser.rb +12 -0
  33. data/lib/rainbows/http_server.rb +5 -7
  34. data/lib/rainbows/max_body.rb +2 -2
  35. data/lib/rainbows/never_block/core.rb +1 -1
  36. data/lib/rainbows/process_client.rb +15 -29
  37. data/lib/rainbows/queue_pool.rb +1 -3
  38. data/lib/rainbows/rack_input.rb +3 -3
  39. data/lib/rainbows/response.rb +164 -38
  40. data/lib/rainbows/revactor.rb +5 -65
  41. data/lib/rainbows/revactor/client.rb +60 -0
  42. data/lib/rainbows/revactor/{body.rb → client/methods.rb} +14 -14
  43. data/lib/rainbows/revactor/{tee_socket.rb → client/tee_socket.rb} +1 -1
  44. data/lib/rainbows/sendfile.rb +1 -2
  45. data/lib/rainbows/server_token.rb +1 -1
  46. data/lib/rainbows/thread_pool.rb +9 -9
  47. data/lib/rainbows/thread_spawn.rb +7 -6
  48. data/lib/rainbows/thread_timeout.rb +1 -1
  49. data/lib/rainbows/writer_thread_pool.rb +9 -25
  50. data/lib/rainbows/writer_thread_pool/client.rb +44 -1
  51. data/lib/rainbows/writer_thread_spawn.rb +2 -11
  52. data/lib/rainbows/writer_thread_spawn/client.rb +53 -13
  53. data/rainbows.gemspec +3 -12
  54. data/t/async_chunk_app.ru +62 -0
  55. data/t/byte-range-common.sh +142 -0
  56. data/t/t0022-copy_stream-byte-range.sh +2 -111
  57. data/t/t0023-sendfile-byte-range.sh +2 -32
  58. data/t/t0025-write-on-close.sh +23 -0
  59. data/t/t0040-keepalive_requests-setting.sh +0 -5
  60. data/t/t0402-async-keepalive.sh +146 -0
  61. data/t/t0500-cramp-streaming.sh +2 -0
  62. data/t/t0501-cramp-rainsocket.sh +2 -0
  63. data/t/t9000-rack-app-pool.sh +1 -1
  64. data/t/test_isolate.rb +5 -10
  65. data/t/test_isolate_cramp.rb +26 -0
  66. data/t/write-on-close.ru +11 -0
  67. metadata +33 -30
  68. data/lib/rainbows/coolio/sendfile.rb +0 -17
  69. data/lib/rainbows/response/body.rb +0 -127
  70. data/lib/rainbows/response/range.rb +0 -34
  71. data/lib/rainbows/timed_read.rb +0 -28
@@ -19,77 +19,17 @@ Revactor::VERSION >= '0.1.5' or abort 'revactor 0.1.5 is required'
19
19
  # \Revactor library as well, to take advantage of the networking
20
20
  # concurrency features this model provides.
21
21
  module Rainbows::Revactor
22
-
23
- # :stopdoc:
24
- RD_ARGS = {}
25
-
22
+ autoload :Client, 'rainbows/revactor/client'
26
23
  autoload :Proxy, 'rainbows/revactor/proxy'
27
- autoload :TeeSocket, 'rainbows/revactor/tee_socket'
28
24
 
29
25
  include Rainbows::Base
30
- LOCALHOST = Kgio::LOCALHOST
31
- TCP = Revactor::TCP::Socket
32
-
33
- # once a client is accepted, it is processed in its entirety here
34
- # in 3 easy steps: read request, call app, write app response
35
- def process_client(client) # :nodoc:
36
- io = client.instance_variable_get(:@_io)
37
- io.fcntl(Fcntl::F_SETFD, Fcntl::FD_CLOEXEC)
38
- rd_args = [ nil ]
39
- remote_addr = if TCP === client
40
- rd_args << RD_ARGS
41
- client.remote_addr
42
- else
43
- LOCALHOST
44
- end
45
- hp = Unicorn::HttpParser.new
46
- buf = hp.buf
47
- alive = false
48
-
49
- begin
50
- ts = nil
51
- until env = hp.parse
52
- buf << client.read(*rd_args)
53
- end
54
-
55
- env[CLIENT_IO] = client
56
- env[RACK_INPUT] = 0 == hp.content_length ?
57
- NULL_IO : IC.new(ts = TeeSocket.new(client), hp)
58
- env[REMOTE_ADDR] = remote_addr
59
- status, headers, body = app.call(env.update(RACK_DEFAULTS))
60
-
61
- if 100 == status.to_i
62
- client.write(EXPECT_100_RESPONSE)
63
- env.delete(HTTP_EXPECT)
64
- status, headers, body = app.call(env)
65
- end
66
-
67
- if hp.headers?
68
- headers = HH.new(headers)
69
- range = make_range!(env, status, headers) and status = range.shift
70
- alive = hp.next? && G.alive && G.kato > 0
71
- headers[CONNECTION] = alive ? KEEP_ALIVE : CLOSE
72
- client.write(response_header(status, headers))
73
- alive && ts and buf << ts.leftover
74
- end
75
- write_body(client, body, range)
76
- end while alive
77
- rescue Revactor::TCP::ReadError
78
- rescue => e
79
- Rainbows::Error.write(io, e)
80
- ensure
81
- client.close
82
- end
83
26
 
84
27
  # runs inside each forked worker, this sits around and waits
85
28
  # for connections and doesn't die until the parent dies (or is
86
29
  # given a INT, QUIT, or TERM signal)
87
30
  def worker_loop(worker) #:nodoc:
31
+ Client.setup
88
32
  init_worker_process(worker)
89
- require 'rainbows/revactor/body'
90
- self.class.__send__(:include, Rainbows::Revactor::Body)
91
- self.class.const_set(:IC, Unicorn::HttpRequest.input_class)
92
- RD_ARGS[:timeout] = G.kato if G.kato > 0
93
33
  nr = 0
94
34
  limit = worker_connections
95
35
  actor_exit = Case[:exit, Actor, Object]
@@ -115,12 +55,12 @@ module Rainbows::Revactor
115
55
  f.when(actor_exit) { nr -= 1 }
116
56
  f.when(accept) do |_, _, s|
117
57
  nr += 1
118
- Actor.spawn_link(s) { |c| process_client(c) }
58
+ Actor.spawn_link(s) { |c| Client.new(c).process_loop }
119
59
  end
120
60
  end
121
61
  rescue => e
122
62
  Rainbows::Error.listen_loop(e)
123
- end while G.alive
63
+ end while Rainbows.alive
124
64
  Actor.receive do |f|
125
65
  f.when(close) {}
126
66
  f.when(actor_exit) { nr -= 1 }
@@ -128,7 +68,7 @@ module Rainbows::Revactor
128
68
  end
129
69
  end
130
70
 
131
- Actor.sleep 1 while G.tick || nr > 0
71
+ Actor.sleep 1 while Rainbows.tick || nr > 0
132
72
  rescue Errno::EMFILE
133
73
  # ignore, let another worker process take it
134
74
  end
@@ -0,0 +1,60 @@
1
+ # -*- encoding: binary -*-
2
+ # :enddoc:
3
+ require 'fcntl'
4
+ class Rainbows::Revactor::Client
5
+ autoload :TeeSocket, 'rainbows/revactor/client/tee_socket'
6
+ RD_ARGS = {}
7
+ Rainbows.keepalive_timeout > 0 and
8
+ RD_ARGS[:timeout] = Rainbows.keepalive_timeout
9
+ attr_reader :kgio_addr
10
+
11
+ def initialize(client)
12
+ @client, @rd_args, @ts = client, [ nil ], nil
13
+ io = client.instance_variable_get(:@_io)
14
+ io.fcntl(Fcntl::F_SETFD, Fcntl::FD_CLOEXEC)
15
+ @kgio_addr = if Revactor::TCP::Socket === client
16
+ @rd_args << RD_ARGS
17
+ client.remote_addr
18
+ else
19
+ Kgio::LOCALHOST
20
+ end
21
+ end
22
+
23
+ def kgio_read!(nr, buf)
24
+ buf.replace(@client.read)
25
+ end
26
+
27
+ def write(buf)
28
+ @client.write(buf)
29
+ end
30
+
31
+ def write_nonblock(buf) # only used for errors
32
+ @client.instance_variable_get(:@_io).write_nonblock(buf)
33
+ end
34
+
35
+ def timed_read(buf2)
36
+ buf2.replace(@client.read(*@rd_args))
37
+ end
38
+
39
+ def set_input(env, hp)
40
+ env[RACK_INPUT] = 0 == hp.content_length ?
41
+ NULL_IO : IC.new(@ts = TeeSocket.new(@client), hp)
42
+ env[CLIENT_IO] = @client
43
+ end
44
+
45
+ def close
46
+ @client.close
47
+ @client = nil
48
+ end
49
+
50
+ def closed?
51
+ @client.nil?
52
+ end
53
+
54
+ def self.setup
55
+ self.const_set(:IC, Unicorn::HttpRequest.input_class)
56
+ include Rainbows::ProcessClient
57
+ include Methods
58
+ end
59
+ end
60
+ require 'rainbows/revactor/client/methods'
@@ -1,15 +1,10 @@
1
1
  # -*- encoding: binary -*-
2
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
-
3
+ module Rainbows::Revactor::Client::Methods
9
4
  if IO.method_defined?(:sendfile_nonblock)
10
- def write_body_file(client, body, range)
11
- body = body_to_io(body)
12
- sock = client.instance_variable_get(:@_io)
5
+ def write_body_file(body, range)
6
+ body, client = body_to_io(body), @client
7
+ sock = @client.instance_variable_get(:@_io)
13
8
  pfx = Revactor::TCP::Socket === client ? :tcp : :unix
14
9
  write_complete = T[:"#{pfx}_write_complete", client]
15
10
  closed = T[:"#{pfx}_closed", client]
@@ -33,13 +28,18 @@ module Rainbows::Revactor::Body
33
28
  ensure
34
29
  close_if_private(body)
35
30
  end
36
- else
37
- ALIASES[:write_body] = :write_body_each
31
+ end
32
+
33
+ def handle_error(e)
34
+ Revactor::TCP::ReadError === e or super
35
+ end
36
+
37
+ def write_response(status, headers, body, alive)
38
+ super(status, headers, body, alive)
39
+ alive && @ts and @hp.buf << @ts.leftover
38
40
  end
39
41
 
40
42
  def self.included(klass)
41
- ALIASES.each do |new_method, orig_method|
42
- klass.__send__(:alias_method, new_method, orig_method)
43
- end
43
+ klass.__send__ :alias_method, :write_body_stream, :write_body_each
44
44
  end
45
45
  end
@@ -5,7 +5,7 @@
5
5
  # enough to avoid mucking with TeeInput internals. Fortunately
6
6
  # this code is not heavily used so we can usually avoid the overhead
7
7
  # of adding a userspace buffer.
8
- class Rainbows::Revactor::TeeSocket
8
+ class Rainbows::Revactor::Client::TeeSocket
9
9
  def initialize(socket)
10
10
  # IO::Buffer is used internally by Rev which Revactor is based on
11
11
  # so we'll always have it available
@@ -72,13 +72,12 @@ class Rainbows::Sendfile < Struct.new(:app)
72
72
  end
73
73
 
74
74
  # :stopdoc:
75
- HH = Rack::Utils::HeaderHash
76
75
  X_SENDFILE = 'X-Sendfile'
77
76
  # :startdoc:
78
77
 
79
78
  def call(env) # :nodoc:
80
79
  status, headers, body = app.call(env)
81
- headers = HH.new(headers)
80
+ headers = Rack::Utils::HeaderHash.new(headers) unless Hash === headers
82
81
  if path = headers.delete(X_SENDFILE)
83
82
  body = Body.new(path, headers) unless body.respond_to?(:to_path)
84
83
  end
@@ -30,7 +30,7 @@ class ServerToken < Struct.new(:app, :token)
30
30
 
31
31
  def call(env)
32
32
  status, headers, body = app.call(env)
33
- headers = Rack::Utils::HeaderHash.new(headers)
33
+ headers = Rack::Utils::HeaderHash.new(headers) unless Hash === headers
34
34
  headers[SERVER] = token
35
35
  [ status, headers, body ]
36
36
  end
@@ -28,11 +28,11 @@ module Rainbows::ThreadPool
28
28
  Thread.new { LISTENERS.size == 1 ? sync_worker : async_worker }
29
29
  end
30
30
 
31
- while G.alive
31
+ while Rainbows.alive
32
32
  # if any worker dies, something is serious wrong, bail
33
33
  pool.each do |thr|
34
- G.tick or break
35
- thr.join(1) and G.quit!
34
+ Rainbows.tick or break
35
+ thr.join(1) and Rainbows.quit!
36
36
  end
37
37
  end
38
38
  join_threads(pool)
@@ -41,10 +41,10 @@ module Rainbows::ThreadPool
41
41
  def sync_worker # :nodoc:
42
42
  s = LISTENERS[0]
43
43
  begin
44
- c = s.kgio_accept and process_client(c)
44
+ c = s.kgio_accept and c.process_loop
45
45
  rescue => e
46
46
  Rainbows::Error.listen_loop(e)
47
- end while G.alive
47
+ end while Rainbows.alive
48
48
  end
49
49
 
50
50
  def async_worker # :nodoc:
@@ -55,18 +55,18 @@ module Rainbows::ThreadPool
55
55
  # problem. On the other hand, a thundering herd may not
56
56
  # even incur as much overhead as an extra Mutex#synchronize
57
57
  ret = select(LISTENERS) and ret[0].each do |s|
58
- s = s.kgio_tryaccept and process_client(s)
58
+ s = s.kgio_tryaccept and s.process_loop
59
59
  end
60
60
  rescue Errno::EINTR
61
61
  rescue => e
62
62
  Rainbows::Error.listen_loop(e)
63
- end while G.alive
63
+ end while Rainbows.alive
64
64
  end
65
65
 
66
66
  def join_threads(threads) # :nodoc:
67
- G.quit!
67
+ Rainbows.quit!
68
68
  threads.delete_if do |thr|
69
- G.tick
69
+ Rainbows.tick
70
70
  begin
71
71
  thr.run
72
72
  thr.join(0.01)
@@ -22,27 +22,28 @@ module Rainbows::ThreadSpawn
22
22
  def accept_loop(klass) #:nodoc:
23
23
  lock = Mutex.new
24
24
  limit = worker_connections
25
+ nr = 0
25
26
  LISTENERS.each do |l|
26
27
  klass.new(l) do |l|
27
28
  begin
28
- if lock.synchronize { G.cur >= limit }
29
+ if lock.synchronize { nr >= limit }
29
30
  worker_yield
30
31
  elsif c = l.kgio_accept
31
32
  klass.new(c) do |c|
32
33
  begin
33
- lock.synchronize { G.cur += 1 }
34
- process_client(c)
34
+ lock.synchronize { nr += 1 }
35
+ c.process_loop
35
36
  ensure
36
- lock.synchronize { G.cur -= 1 }
37
+ lock.synchronize { nr -= 1 }
37
38
  end
38
39
  end
39
40
  end
40
41
  rescue => e
41
42
  Rainbows::Error.listen_loop(e)
42
- end while G.alive
43
+ end while Rainbows.alive
43
44
  end
44
45
  end
45
- sleep 1 while G.tick || lock.synchronize { G.cur > 0 }
46
+ sleep 1 while Rainbows.tick || lock.synchronize { nr > 0 }
46
47
  end
47
48
 
48
49
  def worker_loop(worker) #:nodoc:
@@ -47,7 +47,7 @@ class Rainbows::ThreadTimeout
47
47
  @threshold == 0 and
48
48
  raise ArgumentError, "threshold=0 does not make sense"
49
49
  @threshold < 0 and
50
- @threshold += Rainbows::G.server.worker_connections
50
+ @threshold += Rainbows.server.worker_connections
51
51
  end
52
52
  @app = app
53
53
  @active = {}
@@ -19,30 +19,14 @@
19
19
  module Rainbows::WriterThreadPool
20
20
  # :stopdoc:
21
21
  include Rainbows::Base
22
+ autoload :Client, 'rainbows/writer_thread_pool/client'
22
23
 
23
24
  @@nr = 0
24
25
  @@q = nil
25
26
 
26
- def async_write_body(qclient, body, range)
27
- if body.respond_to?(:close)
28
- Rainbows::SyncClose.new(body) do |body|
29
- qclient.q << [ qclient.to_io, :body, body, range ]
30
- end
31
- else
32
- qclient.q << [ qclient.to_io, :body, body, range ]
33
- end
34
- end
35
-
36
27
  def process_client(client) # :nodoc:
37
28
  @@nr += 1
38
- super(Client.new(client, @@q[@@nr %= @@q.size]))
39
- end
40
-
41
- def init_worker_process(worker)
42
- super
43
- self.class.__send__(:alias_method, :sync_write_body, :write_body)
44
- Rainbows::WriterThreadPool.__send__(
45
- :alias_method, :write_body, :async_write_body)
29
+ Client.new(client, @@q[@@nr %= @@q.size]).process_loop
46
30
  end
47
31
 
48
32
  def worker_loop(worker) # :nodoc:
@@ -51,12 +35,14 @@ module Rainbows::WriterThreadPool
51
35
  qp = (1..worker_connections).map do |n|
52
36
  Rainbows::QueuePool.new(1) do |response|
53
37
  begin
54
- io, arg1, arg2, arg3 = response
55
- case arg1
56
- when :body then sync_write_body(io, arg2, arg3)
57
- when :close then io.close unless io.closed?
38
+ io, arg, *rest = response
39
+ case arg
40
+ when String
41
+ io.kgio_write(arg)
42
+ when :close
43
+ io.close unless io.closed?
58
44
  else
59
- io.write(arg1)
45
+ io.__send__(arg, *rest)
60
46
  end
61
47
  rescue => err
62
48
  Rainbows::Error.write(io, err)
@@ -70,5 +56,3 @@ module Rainbows::WriterThreadPool
70
56
  end
71
57
  # :startdoc:
72
58
  end
73
- # :enddoc:
74
- require 'rainbows/writer_thread_pool/client'
@@ -4,6 +4,49 @@
4
4
  # this is compatible with IO.select
5
5
  class Rainbows::WriterThreadPool::Client < Struct.new(:to_io, :q)
6
6
  include Rainbows::SocketProxy
7
+ include Rainbows::ProcessClient
8
+
9
+ module Methods
10
+ def write_body_each(body)
11
+ q << [ to_io, :write_body_each, body ]
12
+ end
13
+
14
+ def write_response_close(status, headers, body, alive)
15
+ to_io.instance_variable_set(:@hp, @hp) # XXX ugh
16
+ Rainbows::SyncClose.new(body) { |sync_body|
17
+ q << [ to_io, :write_response, status, headers, sync_body, alive ]
18
+ }
19
+ end
20
+
21
+ if IO.respond_to?(:copy_stream) || IO.method_defined?(:sendfile_nonblock)
22
+ def write_response(status, headers, body, alive)
23
+ if body.respond_to?(:close)
24
+ write_response_close(status, headers, body, alive)
25
+ elsif body.respond_to?(:to_path)
26
+ write_response_path(status, headers, body, alive)
27
+ else
28
+ super
29
+ end
30
+ end
31
+
32
+ def write_body_file(body, range)
33
+ q << [ to_io, :write_body_file, body, range ]
34
+ end
35
+
36
+ def write_body_stream(body)
37
+ q << [ to_io, :write_body_stream, body ]
38
+ end
39
+ else # each-only body response
40
+ def write_response(status, headers, body, alive)
41
+ if body.respond_to?(:close)
42
+ write_response_close(status, headers, body, alive)
43
+ else
44
+ super
45
+ end
46
+ end
47
+ end # each-only body response
48
+ end # module Methods
49
+ include Methods
7
50
 
8
51
  def write(buf)
9
52
  q << [ to_io, buf ]
@@ -14,6 +57,6 @@ class Rainbows::WriterThreadPool::Client < Struct.new(:to_io, :q)
14
57
  end
15
58
 
16
59
  def closed?
17
- false
60
+ to_io.closed?
18
61
  end
19
62
  end