rainbows 3.0.0 → 3.1.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 (77) hide show
  1. data/.wrongdoc.yml +2 -2
  2. data/Documentation/comparison.haml +6 -6
  3. data/GIT-VERSION-GEN +1 -1
  4. data/GNUmakefile +16 -128
  5. data/README +2 -3
  6. data/Rakefile +3 -3
  7. data/examples/reverse_proxy.ru +9 -0
  8. data/lib/rainbows.rb +14 -6
  9. data/lib/rainbows/base.rb +0 -1
  10. data/lib/rainbows/const.rb +1 -10
  11. data/lib/rainbows/coolio/client.rb +8 -4
  12. data/lib/rainbows/coolio/core.rb +0 -3
  13. data/lib/rainbows/coolio/thread_client.rb +2 -2
  14. data/lib/rainbows/coolio_fiber_spawn.rb +6 -6
  15. data/lib/rainbows/dev_fd_response.rb +16 -9
  16. data/lib/rainbows/epoll.rb +43 -0
  17. data/lib/rainbows/epoll/client.rb +232 -0
  18. data/lib/rainbows/epoll/response_chunk_pipe.rb +18 -0
  19. data/lib/rainbows/epoll/response_pipe.rb +32 -0
  20. data/lib/rainbows/epoll/server.rb +31 -0
  21. data/lib/rainbows/error.rb +1 -9
  22. data/lib/rainbows/ev_core.rb +12 -12
  23. data/lib/rainbows/ev_core/cap_input.rb +1 -1
  24. data/lib/rainbows/event_machine.rb +0 -6
  25. data/lib/rainbows/event_machine/client.rb +3 -3
  26. data/lib/rainbows/event_machine/response_chunk_pipe.rb +5 -7
  27. data/lib/rainbows/event_machine/response_pipe.rb +7 -8
  28. data/lib/rainbows/fiber/base.rb +2 -2
  29. data/lib/rainbows/fiber/io.rb +21 -63
  30. data/lib/rainbows/fiber/io/methods.rb +1 -1
  31. data/lib/rainbows/http_server.rb +4 -4
  32. data/lib/rainbows/join_threads.rb +18 -0
  33. data/lib/rainbows/max_body.rb +2 -1
  34. data/lib/rainbows/max_body/wrapper.rb +1 -1
  35. data/lib/rainbows/never_block/event_machine.rb +2 -2
  36. data/lib/rainbows/process_client.rb +9 -1
  37. data/lib/rainbows/queue_pool.rb +2 -2
  38. data/lib/rainbows/response.rb +1 -1
  39. data/lib/rainbows/rev_fiber_spawn.rb +4 -4
  40. data/lib/rainbows/revactor/client.rb +4 -5
  41. data/lib/rainbows/revactor/proxy.rb +1 -1
  42. data/lib/rainbows/reverse_proxy.rb +189 -0
  43. data/lib/rainbows/reverse_proxy/coolio.rb +61 -0
  44. data/lib/rainbows/reverse_proxy/ev_client.rb +39 -0
  45. data/lib/rainbows/reverse_proxy/event_machine.rb +46 -0
  46. data/lib/rainbows/reverse_proxy/multi_thread.rb +7 -0
  47. data/lib/rainbows/reverse_proxy/synchronous.rb +21 -0
  48. data/lib/rainbows/sendfile.rb +1 -1
  49. data/lib/rainbows/sync_close.rb +2 -2
  50. data/lib/rainbows/thread_pool.rb +1 -1
  51. data/lib/rainbows/writer_thread_pool.rb +1 -1
  52. data/lib/rainbows/xepoll.rb +24 -0
  53. data/lib/rainbows/xepoll/client.rb +45 -0
  54. data/pkg.mk +171 -0
  55. data/rainbows.gemspec +2 -4
  56. data/t/GNUmakefile +4 -0
  57. data/t/bin/content-md5-put +1 -0
  58. data/t/kgio-pipe-response.ru +9 -1
  59. data/t/rack-fiber_pool/app.ru +6 -1
  60. data/t/simple-http_Epoll.ru +9 -0
  61. data/t/simple-http_XEpoll.ru +9 -0
  62. data/t/t0014-config-conflict.sh +5 -3
  63. data/t/t0023-sendfile-byte-range.sh +1 -7
  64. data/t/t0034-pipelined-pipe-response.sh +2 -1
  65. data/t/t0035-kgio-pipe-response.sh +0 -7
  66. data/t/t0041-optional-pool-size.sh +51 -0
  67. data/t/t0050-response-body-close-has-env.sh +2 -1
  68. data/t/t0104-rack-input-limit-tiny.sh +6 -4
  69. data/t/t0105-rack-input-limit-bigger.sh +6 -4
  70. data/t/t0106-rack-input-keepalive.sh +2 -0
  71. data/t/t0107-rack-input-limit-zero.sh +60 -0
  72. data/t/t0113-rewindable-input-false.sh +1 -0
  73. data/t/t0114-rewindable-input-true.sh +1 -0
  74. data/t/t0202-async-response-one-oh.sh +56 -0
  75. data/t/test_isolate.rb +5 -2
  76. metadata +42 -37
  77. data/lib/rainbows/rack_input.rb +0 -17
@@ -7,6 +7,8 @@ module Rainbows::EvCore
7
7
  NULL_IO = Unicorn::HttpRequest::NULL_IO
8
8
  HttpParser = Rainbows::HttpParser
9
9
  autoload :CapInput, 'rainbows/ev_core/cap_input'
10
+ RBUF = ""
11
+ Z = "".freeze
10
12
 
11
13
  # Apps may return this Rack response: AsyncResponse = [ -1, {}, [] ]
12
14
  ASYNC_CALLBACK = "async.callback".freeze
@@ -54,7 +56,7 @@ module Rainbows::EvCore
54
56
  rv = false
55
57
  else
56
58
  rv = !!(headers[Transfer_Encoding] =~ %r{\Achunked\z}i)
57
- rv = false if headers.delete('X-Rainbows-Autochunk') == 'no'
59
+ rv = false unless @env["rainbows.autochunk"]
58
60
  end
59
61
  write_headers(status, headers, alive)
60
62
  rv
@@ -70,7 +72,7 @@ module Rainbows::EvCore
70
72
  @input = mkinput
71
73
  @hp.filter_body(@buf2 = "", @buf)
72
74
  @input << @buf2
73
- on_read("")
75
+ on_read(Z)
74
76
  end
75
77
 
76
78
  # TeeInput doesn't map too well to this right now...
@@ -81,8 +83,7 @@ module Rainbows::EvCore
81
83
  @hp.parse or return want_more
82
84
  @state = :body
83
85
  if 0 == @hp.content_length
84
- @input = NULL_IO
85
- app_call # common case
86
+ app_call NULL_IO # common case
86
87
  else # nil or len > 0
87
88
  prepare_request_body
88
89
  end
@@ -90,7 +91,7 @@ module Rainbows::EvCore
90
91
  if @hp.body_eof?
91
92
  if @hp.content_length
92
93
  @input.rewind
93
- app_call
94
+ app_call @input
94
95
  else
95
96
  @state = :trailers
96
97
  on_read(data)
@@ -98,14 +99,14 @@ module Rainbows::EvCore
98
99
  elsif data.size > 0
99
100
  @hp.filter_body(@buf2, @buf << data)
100
101
  @input << @buf2
101
- on_read("")
102
+ on_read(Z)
102
103
  else
103
104
  want_more
104
105
  end
105
106
  when :trailers
106
107
  if @hp.trailers(@env, @buf << data)
107
108
  @input.rewind
108
- app_call
109
+ app_call @input
109
110
  else
110
111
  want_more
111
112
  end
@@ -114,14 +115,17 @@ module Rainbows::EvCore
114
115
  handle_error(e)
115
116
  end
116
117
 
118
+ ERROR_413_RESPONSE = "HTTP/1.1 413 Request Entity Too Large\r\n\r\n"
119
+
117
120
  def err_413(msg)
118
- write(Rainbows::Const::ERROR_413_RESPONSE)
121
+ write(ERROR_413_RESPONSE)
119
122
  quit
120
123
  # zip back up the stack
121
124
  raise IOError, msg, []
122
125
  end
123
126
 
124
127
  TmpIO = Unicorn::TmpIO
128
+ CBB = Unicorn::TeeInput.client_body_buffer_size
125
129
 
126
130
  def io_for(bytes)
127
131
  bytes <= CBB ? StringIO.new("") : TmpIO.new
@@ -139,8 +143,4 @@ module Rainbows::EvCore
139
143
  max ? CapInput.new(io_for(max), self, max) : TmpIO.new
140
144
  end
141
145
  end
142
-
143
- def self.setup
144
- const_set :CBB, Unicorn::TeeInput.client_body_buffer_size
145
- end
146
146
  end
@@ -15,7 +15,7 @@ class Rainbows::EvCore::CapInput
15
15
  end
16
16
 
17
17
  def gets; @io.gets; end
18
- def each(&block); @io.each(&block); end
18
+ def each; @io.each { |x| yield x }; end
19
19
  def size; @io.size; end
20
20
  def rewind; @io.rewind; end
21
21
  def read(*args); @io.read(*args); end
@@ -48,11 +48,6 @@ module Rainbows::EventMachine
48
48
 
49
49
  include Rainbows::Base
50
50
 
51
- def init_worker_process(worker) # :nodoc:
52
- Rainbows::Response.setup(Rainbows::EventMachine::Client)
53
- super
54
- end
55
-
56
51
  # runs inside each forked worker, this sits around and waits
57
52
  # for connections and doesn't die until the parent dies (or is
58
53
  # given a INT, QUIT, or TERM signal)
@@ -71,7 +66,6 @@ module Rainbows::EventMachine
71
66
  Rainbows::EventMachine::Server.const_set(:MAX, max)
72
67
  Rainbows::EventMachine::Server.const_set(:CL, client_class)
73
68
  client_class.const_set(:APP, Rainbows.server.app)
74
- Rainbows::EvCore.setup
75
69
  EM.run {
76
70
  conns = EM.instance_variable_get(:@conns) or
77
71
  raise RuntimeError, "EM @conns instance variable not accessible!"
@@ -21,7 +21,7 @@ class Rainbows::EventMachine::Client < EM::Connection
21
21
  end
22
22
  EM.next_tick { receive_data(nil) } unless @buf.empty?
23
23
  else
24
- on_read(data || "") if (@buf.size > 0) || data
24
+ on_read(data || Z) if (@buf.size > 0) || data
25
25
  end
26
26
  end
27
27
 
@@ -30,9 +30,9 @@ class Rainbows::EventMachine::Client < EM::Connection
30
30
  close_connection_after_writing
31
31
  end
32
32
 
33
- def app_call
33
+ def app_call input
34
34
  set_comm_inactivity_timeout 0
35
- @env[RACK_INPUT] = @input
35
+ @env[RACK_INPUT] = input
36
36
  @env[REMOTE_ADDR] = @_io.kgio_addr
37
37
  @env[ASYNC_CALLBACK] = method(:write_async_response)
38
38
  @env[ASYNC_CLOSE] = EM::DefaultDeferrable.new
@@ -9,17 +9,15 @@ module Rainbows::EventMachine::ResponseChunkPipe
9
9
  end
10
10
 
11
11
  def notify_readable
12
- begin
13
- data = @io.read_nonblock(16384, BUF)
12
+ case data = Kgio.tryread(@io, 16384, RBUF)
13
+ when String
14
14
  @client.write("#{data.size.to_s(16)}\r\n")
15
15
  @client.write(data)
16
16
  @client.write("\r\n")
17
- rescue Errno::EINTR
18
- rescue Errno::EAGAIN
19
- return
20
- rescue EOFError
21
- detach
17
+ when :wait_readable
22
18
  return
19
+ when nil
20
+ return detach
23
21
  end while true
24
22
  end
25
23
  end
@@ -3,21 +3,20 @@
3
3
  module Rainbows::EventMachine::ResponsePipe
4
4
  # garbage avoidance, EM always uses this in a single thread,
5
5
  # so a single buffer for all clients will work safely
6
- BUF = ''
6
+ RBUF = Rainbows::EvCore::RBUF
7
7
 
8
8
  def initialize(client)
9
9
  @client = client
10
10
  end
11
11
 
12
12
  def notify_readable
13
- begin
14
- @client.write(@io.read_nonblock(16384, BUF))
15
- rescue Errno::EINTR
16
- rescue Errno::EAGAIN
17
- return
18
- rescue EOFError
19
- detach
13
+ case data = Kgio.tryread(@io, 16384, RBUF)
14
+ when String
15
+ @client.write(data)
16
+ when :wait_readable
20
17
  return
18
+ when nil
19
+ return detach
21
20
  end while true
22
21
  end
23
22
 
@@ -17,7 +17,7 @@ module Rainbows::Fiber::Base
17
17
  # schedules ones that were blocked on I/O. At most it'll sleep
18
18
  # for one second (returned by the schedule_sleepers method) which
19
19
  # will cause it.
20
- def schedule(&block)
20
+ def schedule
21
21
  begin
22
22
  Rainbows.tick
23
23
  t = schedule_sleepers
@@ -33,7 +33,7 @@ module Rainbows::Fiber::Base
33
33
  ret[1].concat(RD.compact & ret[0]).each { |c| c.f.resume }
34
34
 
35
35
  # accept is an expensive syscall, filter out listeners we don't want
36
- (ret[0] & LISTENERS).each(&block)
36
+ (ret[0] & LISTENERS).each { |x| yield x }
37
37
  end
38
38
 
39
39
  # wakes up any sleepers or keepalive-timeout violators that need to be
@@ -28,7 +28,7 @@ class Rainbows::Fiber::IO
28
28
  end
29
29
 
30
30
  # for wrapping output response bodies
31
- def each(&block)
31
+ def each
32
32
  buf = readpartial(16384)
33
33
  yield buf
34
34
  yield buf while readpartial(16384, buf)
@@ -45,78 +45,36 @@ class Rainbows::Fiber::IO
45
45
  end
46
46
 
47
47
  def write(buf)
48
- if @to_io.respond_to?(:kgio_trywrite)
49
- begin
50
- case rv = @to_io.kgio_trywrite(buf)
51
- when nil
52
- return
53
- when String
54
- buf = rv
55
- when :wait_writable
56
- kgio_wait_writable
57
- end
58
- end while true
59
- else
60
- begin
61
- (rv = @to_io.write_nonblock(buf)) == buf.bytesize and return
62
- buf = byte_slice(buf, rv)
63
- rescue Errno::EAGAIN
64
- kgio_wait_writable
65
- end while true
66
- end
67
- end
68
-
69
- def byte_slice(buf, start) # :nodoc:
70
- buf.encoding == Encoding::BINARY or
71
- buf = buf.dup.force_encoding(Encoding::BINARY)
72
- buf.slice(start, buf.size)
48
+ case rv = Kgio.trywrite(buf)
49
+ when String
50
+ buf = rv
51
+ when :wait_writable
52
+ kgio_wait_writable
53
+ end until nil == rv
73
54
  end
74
55
 
75
56
  # used for reading headers (respecting keepalive_timeout)
76
57
  def timed_read(buf)
77
58
  expire = nil
78
- if @to_io.respond_to?(:kgio_tryread)
79
- begin
80
- case rv = @to_io.kgio_tryread(16384, buf)
81
- when :wait_readable
82
- return if expire && expire < Time.now
83
- expire ||= read_expire
84
- kgio_wait_readable
85
- else
86
- return rv
87
- end
88
- end while true
59
+ case rv = Kgio.tryread(@to_io, 16384, buf)
60
+ when :wait_readable
61
+ return if expire && expire < Time.now
62
+ expire ||= read_expire
63
+ kgio_wait_readable
89
64
  else
90
- begin
91
- return @to_io.read_nonblock(16384, buf)
92
- rescue Errno::EAGAIN
93
- return if expire && expire < Time.now
94
- expire ||= read_expire
95
- kgio_wait_readable
96
- end while true
97
- end
65
+ return rv
66
+ end while true
98
67
  end
99
68
 
100
69
  def readpartial(length, buf = "")
101
- if @to_io.respond_to?(:kgio_tryread)
102
- begin
103
- rv = @to_io.kgio_tryread(length, buf)
104
- case rv
105
- when nil
106
- raise EOFError, "end of file reached", []
107
- when :wait_readable
108
- kgio_wait_readable
109
- else
110
- return rv
111
- end
112
- end while true
70
+ case rv = Kgio.tryread(@to_io, length, buf)
71
+ when nil
72
+ raise EOFError, "end of file reached", []
73
+ when :wait_readable
74
+ kgio_wait_readable
113
75
  else
114
- begin
115
- return @to_io.read_nonblock(length, buf)
116
- rescue Errno::EAGAIN
117
- kgio_wait_readable
118
- end while true
119
- end
76
+ return rv
77
+ end while true
120
78
  end
121
79
 
122
80
  def kgio_read(*args)
@@ -16,7 +16,7 @@ module Rainbows::Fiber::IO::Methods
16
16
  end
17
17
 
18
18
  # for wrapping output response bodies
19
- def each(&block)
19
+ def each
20
20
  if buf = kgio_read(16384)
21
21
  yield buf
22
22
  yield buf while kgio_read(16384, buf)
@@ -43,7 +43,7 @@ class Rainbows::HttpServer < Unicorn::HttpServer
43
43
 
44
44
  def ready_pipe=(v)
45
45
  # hacky hook got force Rainbows! to load modules only in workers
46
- if @master_pid && @master_pid == Process.ppid
46
+ if defined?(@master_pid) && @master_pid == Process.ppid
47
47
  extend(Rainbows.const_get(@use))
48
48
  end
49
49
  super
@@ -63,8 +63,8 @@ class Rainbows::HttpServer < Unicorn::HttpServer
63
63
  raise ArgumentError, "concurrency model #{model.inspect} not supported"
64
64
  args.each do |opt|
65
65
  case opt
66
- when Hash; O.update(opt)
67
- when Symbol; O[opt] = true
66
+ when Hash; Rainbows::O.update(opt)
67
+ when Symbol; Rainbows::O[opt] = true
68
68
  else; raise ArgumentError, "can't handle option: #{opt.inspect}"
69
69
  end
70
70
  end
@@ -72,7 +72,7 @@ class Rainbows::HttpServer < Unicorn::HttpServer
72
72
  new_defaults = {
73
73
  'rainbows.model' => (@use = model.to_sym),
74
74
  'rack.multithread' => !!(model.to_s =~ /Thread/),
75
- 'rainbows.autochunk' => [:Coolio,:Rev,
75
+ 'rainbows.autochunk' => [:Coolio,:Rev,:Epoll,:XEpoll,
76
76
  :EventMachine,:NeverBlock].include?(@use),
77
77
  }
78
78
  Rainbows::Const::RACK_DEFAULTS.update(new_defaults)
@@ -0,0 +1,18 @@
1
+ # -*- encoding: binary -*-
2
+ # :enddoc:
3
+ # This module only gets loaded on shutdown
4
+ module Rainbows::JoinThreads
5
+
6
+ # blocking acceptor threads must be forced to run
7
+ def self.acceptors(threads)
8
+ threads.delete_if do |thr|
9
+ Rainbows.tick
10
+ begin
11
+ thr.run
12
+ thr.join(0.01)
13
+ rescue
14
+ true
15
+ end
16
+ end until threads.empty?
17
+ end
18
+ end
@@ -55,7 +55,8 @@ class Rainbows::MaxBody
55
55
  case Rainbows.server.use
56
56
  when :Rev, :Coolio, :EventMachine, :NeverBlock,
57
57
  :RevThreadSpawn, :RevThreadPool,
58
- :CoolioThreadSpawn, :CoolioThreadPool
58
+ :CoolioThreadSpawn, :CoolioThreadPool,
59
+ :Epoll, :XEpoll
59
60
  return
60
61
  end
61
62
 
@@ -7,7 +7,7 @@ class Rainbows::MaxBody::Wrapper
7
7
  @input, @limit, @rbuf = rack_input, limit, ''
8
8
  end
9
9
 
10
- def each(&block)
10
+ def each
11
11
  while line = gets
12
12
  yield line
13
13
  end
@@ -1,10 +1,10 @@
1
1
  # -*- encoding: binary -*-
2
2
  # :enddoc:
3
3
  class Rainbows::NeverBlock::Client < Rainbows::EventMachine::Client
4
- def app_call
4
+ def app_call input
5
5
  POOL.spawn do
6
6
  begin
7
- super
7
+ super input
8
8
  rescue => e
9
9
  handle_error(e)
10
10
  end
@@ -1,9 +1,13 @@
1
1
  # -*- encoding: binary -*-
2
+ # :enddoc:
2
3
  module Rainbows::ProcessClient
3
4
  include Rainbows::Response
4
- include Rainbows::RackInput
5
5
  include Rainbows::Const
6
6
 
7
+ NULL_IO = Unicorn::HttpRequest::NULL_IO
8
+ RACK_INPUT = Unicorn::HttpRequest::RACK_INPUT
9
+ IC = Unicorn::HttpRequest.input_class
10
+
7
11
  def process_loop
8
12
  @hp = hp = Rainbows::HttpParser.new
9
13
  kgio_read!(16384, buf = hp.buf) or return
@@ -38,4 +42,8 @@ module Rainbows::ProcessClient
38
42
  def handle_error(e)
39
43
  Rainbows::Error.write(self, e)
40
44
  end
45
+
46
+ def set_input(env, hp)
47
+ env[RACK_INPUT] = 0 == hp.content_length ? NULL_IO : IC.new(self, hp)
48
+ end
41
49
  end
@@ -6,12 +6,12 @@ require 'thread'
6
6
  # This is NOT used for the ThreadPool class, since that class does not
7
7
  # need a userspace Queue.
8
8
  class Rainbows::QueuePool < Struct.new(:queue, :threads)
9
- def initialize(size = 20, &block)
9
+ def initialize(size = 20)
10
10
  q = Queue.new
11
11
  self.threads = (1..size).map do
12
12
  Thread.new do
13
13
  while job = q.shift
14
- block.call(job)
14
+ yield job
15
15
  end
16
16
  end
17
17
  end