rainbows 3.0.0 → 3.1.0

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