rainbows 2.1.0 → 3.0.0

Sign up to get free protection for your applications and to get access to all the features.
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
@@ -8,15 +8,12 @@ module Rainbows::Coolio::Core
8
8
  # given a INT, QUIT, or TERM signal)
9
9
  def worker_loop(worker)
10
10
  Rainbows::Response.setup(Rainbows::Coolio::Client)
11
- require 'rainbows/coolio/sendfile'
12
- Rainbows::Coolio::Client.__send__(:include, Rainbows::Coolio::Sendfile)
13
11
  init_worker_process(worker)
14
12
  mod = Rainbows.const_get(@use)
15
13
  rloop = Rainbows::Coolio::Server.const_set(:LOOP, Coolio::Loop.default)
16
- Rainbows::Coolio::Client.const_set(:LOOP, rloop)
17
14
  Rainbows::Coolio::Server.const_set(:MAX, @worker_connections)
18
15
  Rainbows::Coolio::Server.const_set(:CL, mod.const_get(:Client))
19
- Rainbows::EvCore.const_set(:APP, G.server.app)
16
+ Rainbows::EvCore.const_set(:APP, Rainbows.server.app)
20
17
  Rainbows::EvCore.setup
21
18
  Rainbows::Coolio::Heartbeat.new(1, true).attach(rloop)
22
19
  LISTENERS.map! { |s| Rainbows::Coolio::Server.new(s).attach(rloop) }
@@ -8,13 +8,12 @@
8
8
  class Rainbows::Coolio::Heartbeat < Coolio::TimerWatcher
9
9
  KATO = Rainbows::Coolio::KATO
10
10
  CONN = Rainbows::Coolio::CONN
11
- G = Rainbows::G
12
11
 
13
12
  def on_timer
14
- if (ot = G.kato) >= 0
13
+ if (ot = Rainbows.keepalive_timeout) >= 0
15
14
  ot = Time.now - ot
16
15
  KATO.delete_if { |client, time| time < ot and client.timeout? }
17
16
  end
18
- exit if (! G.tick && CONN.size <= 0)
17
+ exit if (! Rainbows.tick && CONN.size <= 0)
19
18
  end
20
19
  end
@@ -7,15 +7,16 @@ class Rainbows::Coolio::Master < Coolio::IOWatcher
7
7
  @reader, @writer = Kgio::Pipe.new
8
8
  super(@reader)
9
9
  @queue = queue
10
+ @wbuf, @rbuf = "\0", "\0"
10
11
  end
11
12
 
12
13
  def <<(output)
13
14
  @queue << output
14
- @writer.kgio_trywrite("\0")
15
+ @writer.kgio_trywrite(@wbuf)
15
16
  end
16
17
 
17
18
  def on_readable
18
- if String === @reader.kgio_tryread(1)
19
+ if String === @reader.kgio_tryread(1, @rbuf)
19
20
  client, response = @queue.pop
20
21
  client.response_write(response)
21
22
  end
@@ -2,8 +2,7 @@
2
2
  # :enddoc:
3
3
  #
4
4
  # this is class is specific to Coolio for proxying IO-derived objects
5
- class Rainbows::Coolio::DeferredChunkResponse <
6
- Rainbows::Coolio::DeferredResponse
5
+ class Rainbows::Coolio::ResponseChunkPipe < Rainbows::Coolio::ResponsePipe
7
6
  def on_read(data)
8
7
  @client.write("#{data.size.to_s(16)}\r\n")
9
8
  @client.write(data)
@@ -3,7 +3,7 @@
3
3
  #
4
4
  # this is class is specific to Coolio for writing large static files
5
5
  # or proxying IO-derived objects
6
- class Rainbows::Coolio::DeferredResponse < Coolio::IO
6
+ class Rainbows::Coolio::ResponsePipe < Coolio::IO
7
7
  def initialize(io, client, body)
8
8
  super(io)
9
9
  @client, @body = client, body
@@ -14,11 +14,9 @@ class Rainbows::Coolio::ThreadClient < Rainbows::Coolio::Client
14
14
 
15
15
  # this is only called in the master thread
16
16
  def response_write(response)
17
- alive = @hp.next? && G.alive
18
- coolio_write_response(response, alive)
19
- return quit unless alive && :close != @state
20
-
21
- @state = :headers
17
+ ev_write_response(*response, @hp.next?)
18
+ rescue => e
19
+ handle_error(e)
22
20
  end
23
21
 
24
22
  # fails-safe application dispatch, we absolutely cannot
@@ -27,7 +25,7 @@ class Rainbows::Coolio::ThreadClient < Rainbows::Coolio::Client
27
25
  def app_response
28
26
  begin
29
27
  @env[REMOTE_ADDR] = @_io.kgio_addr
30
- APP.call(@env.update(RACK_DEFAULTS))
28
+ APP.call(@env.merge!(RACK_DEFAULTS))
31
29
  rescue => e
32
30
  Rainbows::Error.app(e) # we guarantee this does not raise
33
31
  [ 500, {}, [] ]
@@ -18,7 +18,7 @@ module Rainbows::CoolioFiberSpawn
18
18
  init_worker_process(worker)
19
19
  Server.const_set(:MAX, @worker_connections)
20
20
  Rainbows::Fiber::Base.setup(Server, nil)
21
- Server.const_set(:APP, G.server.app)
21
+ Server.const_set(:APP, Rainbows.server.app)
22
22
  Heartbeat.new(1, true).attach(Coolio::Loop.default)
23
23
  LISTENERS.map! { |s| Server.new(s).attach(Coolio::Loop.default) }
24
24
  Coolio::Loop.default.run
@@ -16,6 +16,7 @@
16
16
  # users are NOT advised to use this due to high CPU usage.
17
17
  module Rainbows::CoolioThreadPool
18
18
  # :stopdoc:
19
+ autoload :Client, 'rainbows/coolio_thread_pool/client'
19
20
  DEFAULTS = {
20
21
  :pool_size => 20, # same default size as ThreadPool (w/o Coolio)
21
22
  }
@@ -53,5 +54,4 @@ module Rainbows::CoolioThreadPool
53
54
  end
54
55
  end
55
56
  # :enddoc:
56
- require 'rainbows/coolio_thread_pool/client'
57
57
  require 'rainbows/coolio_thread_pool/watcher'
@@ -1,14 +1,12 @@
1
1
  # -*- encoding: binary -*-
2
2
  # :enddoc:
3
3
  class Rainbows::CoolioThreadPool::Watcher < Coolio::TimerWatcher
4
- G = Rainbows::G
5
-
6
4
  def initialize(threads)
7
5
  @threads = threads
8
- super(G.server.timeout, true)
6
+ super(Rainbows.server.timeout, true)
9
7
  end
10
8
 
11
9
  def on_timer
12
- @threads.each { |t| t.join(0) and G.quit! }
10
+ @threads.each { |t| t.join(0) and Rainbows.quit! }
13
11
  end
14
12
  end
@@ -15,6 +15,7 @@
15
15
  # users are NOT advised to use this due to high CPU usage.
16
16
  module Rainbows::CoolioThreadSpawn
17
17
  include Rainbows::Coolio::Core
18
+ autoload :Client, 'rainbows/coolio_thread_spawn/client'
18
19
 
19
20
  def init_worker_process(worker) # :nodoc:
20
21
  super
@@ -24,4 +25,3 @@ module Rainbows::CoolioThreadSpawn
24
25
  end
25
26
  end
26
27
  # :enddoc:
27
- require 'rainbows/coolio_thread_spawn/client'
@@ -39,7 +39,7 @@ class Rainbows::DevFdResponse < Struct.new(:app)
39
39
  io ||= File.open(body.to_path) if body.respond_to?(:to_path)
40
40
  return response if io.nil?
41
41
 
42
- headers = HeaderHash.new(headers)
42
+ headers = Rack::Utils::HeaderHash.new(headers) unless Hash === headers
43
43
  st = io.stat
44
44
  fileno = io.fileno
45
45
  FD_MAP[fileno] = io
@@ -47,7 +47,7 @@ class Rainbows::DevFdResponse < Struct.new(:app)
47
47
  headers['Content-Length'] ||= st.size.to_s
48
48
  headers.delete('Transfer-Encoding')
49
49
  elsif st.pipe? || st.socket? # epoll-able things
50
- unless headers['Content-Length']
50
+ unless headers.include?('Content-Length')
51
51
  if env['rainbows.autochunk']
52
52
  headers['Transfer-Encoding'] = 'chunked'
53
53
  else
@@ -2,8 +2,6 @@
2
2
  # :enddoc:
3
3
  module Rainbows::Error
4
4
 
5
- G = Rainbows::G
6
-
7
5
  # if we get any error, try to write something back to the client
8
6
  # assuming we haven't closed the socket, but don't get hung up
9
7
  # if the socket is already closed or broken. We'll always ensure
@@ -20,15 +18,15 @@ module Rainbows::Error
20
18
  end
21
19
 
22
20
  def self.app(e)
23
- G.server.logger.error "app error: #{e.inspect}"
24
- G.server.logger.error e.backtrace.join("\n")
21
+ Rainbows.server.logger.error "app error: #{e.inspect}"
22
+ Rainbows.server.logger.error e.backtrace.join("\n")
25
23
  rescue
26
24
  end
27
25
 
28
26
  def self.listen_loop(e)
29
- G.alive or return
30
- G.server.logger.error "listen loop error: #{e.inspect}."
31
- G.server.logger.error e.backtrace.join("\n")
27
+ Rainbows.alive or return
28
+ Rainbows.server.logger.error "listen loop error: #{e.inspect}."
29
+ Rainbows.server.logger.error e.backtrace.join("\n")
32
30
  rescue
33
31
  end
34
32
 
@@ -4,16 +4,28 @@
4
4
  module Rainbows::EvCore
5
5
  include Rainbows::Const
6
6
  include Rainbows::Response
7
- G = Rainbows::G
8
7
  NULL_IO = Unicorn::HttpRequest::NULL_IO
9
- HttpParser = Unicorn::HttpParser
8
+ HttpParser = Rainbows::HttpParser
10
9
  autoload :CapInput, 'rainbows/ev_core/cap_input'
11
10
 
12
11
  # Apps may return this Rack response: AsyncResponse = [ -1, {}, [] ]
13
12
  ASYNC_CALLBACK = "async.callback".freeze
14
-
15
13
  ASYNC_CLOSE = "async.close".freeze
16
14
 
15
+ def write_async_response(response)
16
+ status, headers, body = response
17
+ if alive = @hp.next?
18
+ # we can't do HTTP keepalive without Content-Length or
19
+ # "Transfer-Encoding: chunked", and the async.callback stuff
20
+ # isn't Rack::Lint-compatible, so we have to enforce it here.
21
+ headers = Rack::Utils::HeaderHash.new(headers) unless Hash === headers
22
+ alive = headers.include?(Content_Length) ||
23
+ !!(%r{\Achunked\z}i =~ headers[Transfer_Encoding])
24
+ end
25
+ @deferred = nil
26
+ ev_write_response(status, headers, body, alive)
27
+ end
28
+
17
29
  def post_init
18
30
  @hp = HttpParser.new
19
31
  @env = @hp.env
@@ -36,14 +48,15 @@ module Rainbows::EvCore
36
48
  end
37
49
 
38
50
  # returns whether to enable response chunking for autochunk models
39
- def stream_response_headers(status, headers)
40
- if headers['Content-Length']
51
+ def stream_response_headers(status, headers, alive)
52
+ headers = Rack::Utils::HeaderHash.new(headers) unless Hash === headers
53
+ if headers.include?(Content_Length)
41
54
  rv = false
42
55
  else
43
- rv = !!(headers['Transfer-Encoding'] =~ %r{\Achunked\z}i)
56
+ rv = !!(headers[Transfer_Encoding] =~ %r{\Achunked\z}i)
44
57
  rv = false if headers.delete('X-Rainbows-Autochunk') == 'no'
45
58
  end
46
- write(response_header(status, headers))
59
+ write_headers(status, headers, alive)
47
60
  rv
48
61
  end
49
62
 
@@ -44,6 +44,7 @@ module Rainbows::EventMachine
44
44
  autoload :ResponsePipe, 'rainbows/event_machine/response_pipe'
45
45
  autoload :ResponseChunkPipe, 'rainbows/event_machine/response_chunk_pipe'
46
46
  autoload :TryDefer, 'rainbows/event_machine/try_defer'
47
+ autoload :Client, 'rainbows/event_machine/client'
47
48
 
48
49
  include Rainbows::Base
49
50
 
@@ -57,8 +58,9 @@ module Rainbows::EventMachine
57
58
  # given a INT, QUIT, or TERM signal)
58
59
  def worker_loop(worker) # :nodoc:
59
60
  init_worker_process(worker)
60
- G.server.app.respond_to?(:deferred?) and
61
- G.server.app = Rainbows::EventMachine::TryDefer[G.server.app]
61
+ server = Rainbows.server
62
+ server.app.respond_to?(:deferred?) and
63
+ server.app = TryDefer.new(server.app)
62
64
 
63
65
  # enable them both, should be non-fatal if not supported
64
66
  EM.epoll
@@ -68,14 +70,14 @@ module Rainbows::EventMachine
68
70
  max = worker_connections + LISTENERS.size
69
71
  Rainbows::EventMachine::Server.const_set(:MAX, max)
70
72
  Rainbows::EventMachine::Server.const_set(:CL, client_class)
71
- client_class.const_set(:APP, G.server.app)
73
+ client_class.const_set(:APP, Rainbows.server.app)
72
74
  Rainbows::EvCore.setup
73
75
  EM.run {
74
76
  conns = EM.instance_variable_get(:@conns) or
75
77
  raise RuntimeError, "EM @conns instance variable not accessible!"
76
78
  Rainbows::EventMachine::Server.const_set(:CUR, conns)
77
79
  EM.add_periodic_timer(1) do
78
- unless G.tick
80
+ unless Rainbows.tick
79
81
  conns.each_value { |c| client_class === c and c.quit }
80
82
  EM.stop if conns.empty? && EM.reactor_running?
81
83
  end
@@ -89,5 +91,4 @@ module Rainbows::EventMachine
89
91
  end
90
92
  end
91
93
  # :enddoc:
92
- require 'rainbows/event_machine/client'
93
94
  require 'rainbows/event_machine/server'
@@ -1,12 +1,11 @@
1
1
  # -*- encoding: binary -*-
2
2
  # :enddoc:
3
3
  class Rainbows::EventMachine::Client < EM::Connection
4
- attr_writer :body
5
4
  include Rainbows::EvCore
6
5
 
7
6
  def initialize(io)
8
7
  @_io = io
9
- @body = nil
8
+ @deferred = nil
10
9
  end
11
10
 
12
11
  alias write send_data
@@ -15,7 +14,7 @@ class Rainbows::EventMachine::Client < EM::Connection
15
14
  # To avoid clobbering the current streaming response
16
15
  # (often a static file), we do not attempt to process another
17
16
  # request on the same connection until the first is complete
18
- if @body
17
+ if @deferred
19
18
  if data
20
19
  @buf << data
21
20
  @_io.shutdown(Socket::SHUT_RD) if @buf.size > 0x1c000
@@ -35,83 +34,77 @@ class Rainbows::EventMachine::Client < EM::Connection
35
34
  set_comm_inactivity_timeout 0
36
35
  @env[RACK_INPUT] = @input
37
36
  @env[REMOTE_ADDR] = @_io.kgio_addr
38
- @env[ASYNC_CALLBACK] = method(:em_write_response)
37
+ @env[ASYNC_CALLBACK] = method(:write_async_response)
39
38
  @env[ASYNC_CLOSE] = EM::DefaultDeferrable.new
39
+ status, headers, body = catch(:async) {
40
+ APP.call(@env.merge!(RACK_DEFAULTS))
41
+ }
40
42
 
41
- response = catch(:async) { APP.call(@env.update(RACK_DEFAULTS)) }
42
-
43
- # too tricky to support pipelining with :async since the
44
- # second (pipelined) request could be a stuck behind a
45
- # long-running async response
46
- (response.nil? || -1 == response[0]) and return @state = :close
43
+ (nil == status || -1 == status) ? @deferred = true :
44
+ ev_write_response(status, headers, body, @hp.next?)
45
+ end
47
46
 
48
- if @hp.next? && G.alive && G.kato > 0
49
- @state = :headers
50
- em_write_response(response, true)
51
- if @buf.empty?
52
- set_comm_inactivity_timeout(G.kato)
53
- elsif @body.nil?
54
- EM.next_tick { receive_data(nil) }
55
- end
56
- else
57
- em_write_response(response, false)
47
+ def deferred_errback(orig_body)
48
+ @deferred.errback do
49
+ orig_body.close if orig_body.respond_to?(:close)
50
+ quit
58
51
  end
59
52
  end
60
53
 
61
- def em_write_response(response, alive = false)
62
- status, headers, body = response
63
- if @hp.headers?
64
- headers = HH.new(headers)
65
- headers[CONNECTION] = alive ? KEEP_ALIVE : CLOSE
66
- else
67
- headers = nil
54
+ def deferred_callback(orig_body, alive)
55
+ @deferred.callback do
56
+ orig_body.close if orig_body.respond_to?(:close)
57
+ @deferred = nil
58
+ alive ? receive_data(nil) : quit
68
59
  end
60
+ end
69
61
 
62
+ def ev_write_response(status, headers, body, alive)
63
+ @state = :headers if alive
70
64
  if body.respond_to?(:errback) && body.respond_to?(:callback)
71
- @body = body
72
- body.callback { quit }
73
- body.errback { quit }
74
- # async response, this could be a trickle as is in comet-style apps
75
- headers[CONNECTION] = CLOSE if headers
76
- alive = true
65
+ @deferred = body
66
+ deferred_errback(body)
67
+ deferred_callback(body, alive)
77
68
  elsif body.respond_to?(:to_path)
78
69
  st = File.stat(path = body.to_path)
79
70
 
80
71
  if st.file?
81
- write(response_header(status, headers)) if headers
82
- @body = stream_file_data(path)
83
- @body.errback do
84
- body.close if body.respond_to?(:close)
85
- quit
86
- end
87
- @body.callback do
88
- body.close if body.respond_to?(:close)
89
- @body = nil
90
- alive ? receive_data(nil) : quit
91
- end
72
+ write_headers(status, headers, alive)
73
+ @deferred = stream_file_data(path)
74
+ deferred_errback(body)
75
+ deferred_callback(body, alive)
92
76
  return
93
77
  elsif st.socket? || st.pipe?
94
- @body = io = body_to_io(body)
95
- chunk = stream_response_headers(status, headers) if headers
78
+ io = body_to_io(@deferred = body)
79
+ chunk = stream_response_headers(status, headers, alive)
96
80
  m = chunk ? Rainbows::EventMachine::ResponseChunkPipe :
97
81
  Rainbows::EventMachine::ResponsePipe
98
- return EM.watch(io, m, self, alive, body).notify_readable = true
82
+ return EM.watch(io, m, self).notify_readable = true
99
83
  end
100
84
  # char or block device... WTF? fall through to body.each
101
85
  end
102
-
103
- write(response_header(status, headers)) if headers
104
- write_body_each(self, body)
105
- quit unless alive
86
+ write_response(status, headers, body, alive)
87
+ if alive
88
+ if @deferred.nil?
89
+ if @buf.empty?
90
+ set_comm_inactivity_timeout(Rainbows.keepalive_timeout)
91
+ else
92
+ EM.next_tick { receive_data(nil) }
93
+ end
94
+ end
95
+ else
96
+ quit unless @deferred
97
+ end
106
98
  end
107
99
 
108
100
  def next!
109
- @hp.keepalive? ? receive_data(@body = nil) : quit
101
+ @deferred.close if @deferred.respond_to?(:close)
102
+ @hp.keepalive? ? receive_data(@deferred = nil) : quit
110
103
  end
111
104
 
112
105
  def unbind
113
106
  async_close = @env[ASYNC_CLOSE] and async_close.succeed
114
- @body.respond_to?(:fail) and @body.fail
107
+ @deferred.respond_to?(:fail) and @deferred.fail
115
108
  begin
116
109
  @_io.close
117
110
  rescue Errno::EBADF