rainbows 2.0.1 → 2.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 (118) hide show
  1. data/.document +1 -0
  2. data/.gitignore +1 -0
  3. data/.manifest +46 -18
  4. data/.wrongdoc.yml +8 -0
  5. data/ChangeLog +849 -374
  6. data/Documentation/comparison.haml +26 -21
  7. data/FAQ +6 -0
  8. data/GIT-VERSION-GEN +1 -1
  9. data/GNUmakefile +23 -65
  10. data/LATEST +27 -0
  11. data/NEWS +53 -26
  12. data/README +7 -7
  13. data/Rakefile +1 -98
  14. data/Summary +0 -7
  15. data/TODO +2 -2
  16. data/lib/rainbows/app_pool.rb +2 -1
  17. data/lib/rainbows/base.rb +1 -0
  18. data/lib/rainbows/configurator.rb +9 -0
  19. data/lib/rainbows/const.rb +1 -1
  20. data/lib/rainbows/coolio/client.rb +191 -0
  21. data/lib/rainbows/coolio/core.rb +25 -0
  22. data/lib/rainbows/{rev → coolio}/deferred_chunk_response.rb +3 -2
  23. data/lib/rainbows/{rev → coolio}/deferred_response.rb +3 -3
  24. data/lib/rainbows/coolio/heartbeat.rb +20 -0
  25. data/lib/rainbows/{rev → coolio}/master.rb +2 -3
  26. data/lib/rainbows/{rev → coolio}/sendfile.rb +1 -1
  27. data/lib/rainbows/coolio/server.rb +11 -0
  28. data/lib/rainbows/coolio/thread_client.rb +36 -0
  29. data/lib/rainbows/coolio.rb +45 -0
  30. data/lib/rainbows/coolio_fiber_spawn.rb +26 -0
  31. data/lib/rainbows/coolio_support.rb +9 -0
  32. data/lib/rainbows/coolio_thread_pool/client.rb +8 -0
  33. data/lib/rainbows/coolio_thread_pool/watcher.rb +14 -0
  34. data/lib/rainbows/coolio_thread_pool.rb +57 -0
  35. data/lib/rainbows/coolio_thread_spawn/client.rb +8 -0
  36. data/lib/rainbows/coolio_thread_spawn.rb +27 -0
  37. data/lib/rainbows/dev_fd_response.rb +6 -2
  38. data/lib/rainbows/ev_core/cap_input.rb +3 -2
  39. data/lib/rainbows/ev_core.rb +13 -3
  40. data/lib/rainbows/event_machine/client.rb +124 -0
  41. data/lib/rainbows/event_machine/response_pipe.rb +1 -2
  42. data/lib/rainbows/event_machine/server.rb +15 -0
  43. data/lib/rainbows/event_machine.rb +13 -137
  44. data/lib/rainbows/fiber/base.rb +6 -7
  45. data/lib/rainbows/fiber/body.rb +4 -2
  46. data/lib/rainbows/fiber/coolio/heartbeat.rb +15 -0
  47. data/lib/rainbows/fiber/{rev → coolio}/methods.rb +4 -5
  48. data/lib/rainbows/fiber/{rev → coolio}/server.rb +1 -1
  49. data/lib/rainbows/fiber/{rev → coolio}/sleeper.rb +2 -2
  50. data/lib/rainbows/fiber/coolio.rb +12 -0
  51. data/lib/rainbows/fiber/io/methods.rb +6 -0
  52. data/lib/rainbows/fiber/io.rb +8 -10
  53. data/lib/rainbows/fiber/queue.rb +24 -30
  54. data/lib/rainbows/fiber.rb +7 -4
  55. data/lib/rainbows/fiber_pool.rb +1 -1
  56. data/lib/rainbows/http_server.rb +9 -2
  57. data/lib/rainbows/max_body.rb +3 -1
  58. data/lib/rainbows/never_block/core.rb +15 -0
  59. data/lib/rainbows/never_block/event_machine.rb +8 -3
  60. data/lib/rainbows/never_block.rb +37 -70
  61. data/lib/rainbows/process_client.rb +3 -6
  62. data/lib/rainbows/rack_input.rb +17 -0
  63. data/lib/rainbows/response/body.rb +18 -19
  64. data/lib/rainbows/response.rb +1 -1
  65. data/lib/rainbows/rev.rb +21 -43
  66. data/lib/rainbows/rev_fiber_spawn.rb +4 -19
  67. data/lib/rainbows/rev_thread_pool.rb +21 -75
  68. data/lib/rainbows/rev_thread_spawn.rb +18 -36
  69. data/lib/rainbows/revactor/body.rb +4 -1
  70. data/lib/rainbows/revactor/tee_socket.rb +44 -0
  71. data/lib/rainbows/revactor.rb +13 -48
  72. data/lib/rainbows/socket_proxy.rb +24 -0
  73. data/lib/rainbows/sync_close.rb +37 -0
  74. data/lib/rainbows/thread_pool.rb +66 -70
  75. data/lib/rainbows/thread_spawn.rb +40 -50
  76. data/lib/rainbows/thread_timeout.rb +33 -27
  77. data/lib/rainbows/timed_read.rb +5 -1
  78. data/lib/rainbows/worker_yield.rb +16 -0
  79. data/lib/rainbows/writer_thread_pool/client.rb +19 -0
  80. data/lib/rainbows/writer_thread_pool.rb +60 -91
  81. data/lib/rainbows/writer_thread_spawn/client.rb +69 -0
  82. data/lib/rainbows/writer_thread_spawn.rb +37 -117
  83. data/lib/rainbows.rb +12 -4
  84. data/rainbows.gemspec +15 -19
  85. data/t/GNUmakefile +4 -4
  86. data/t/close-has-env.ru +65 -0
  87. data/t/simple-http_Coolio.ru +9 -0
  88. data/t/simple-http_CoolioFiberSpawn.ru +10 -0
  89. data/t/simple-http_CoolioThreadPool.ru +9 -0
  90. data/t/simple-http_CoolioThreadSpawn.ru +9 -0
  91. data/t/t0004-heartbeat-timeout.sh +2 -2
  92. data/t/t0007-worker-follows-master-to-death.sh +1 -1
  93. data/t/t0015-working_directory.sh +7 -1
  94. data/t/t0017-keepalive-timeout-zero.sh +1 -1
  95. data/t/t0019-keepalive-cpu-usage.sh +62 -0
  96. data/t/t0040-keepalive_requests-setting.sh +51 -0
  97. data/t/t0050-response-body-close-has-env.sh +109 -0
  98. data/t/t0102-rack-input-short.sh +6 -6
  99. data/t/t0106-rack-input-keepalive.sh +48 -2
  100. data/t/t0113-rewindable-input-false.sh +28 -0
  101. data/t/t0113.ru +12 -0
  102. data/t/t0114-rewindable-input-true.sh +28 -0
  103. data/t/t0114.ru +12 -0
  104. data/t/t9100-thread-timeout.sh +24 -2
  105. data/t/t9101-thread-timeout-threshold.sh +6 -13
  106. data/t/test-lib.sh +2 -1
  107. data/t/test_isolate.rb +9 -4
  108. data/t/times.ru +6 -0
  109. metadata +109 -42
  110. data/GIT-VERSION-FILE +0 -1
  111. data/lib/rainbows/fiber/rev/heartbeat.rb +0 -8
  112. data/lib/rainbows/fiber/rev/kato.rb +0 -22
  113. data/lib/rainbows/fiber/rev.rb +0 -13
  114. data/lib/rainbows/rev/client.rb +0 -194
  115. data/lib/rainbows/rev/core.rb +0 -41
  116. data/lib/rainbows/rev/heartbeat.rb +0 -23
  117. data/lib/rainbows/rev/thread.rb +0 -46
  118. data/man/man1/rainbows.1 +0 -193
@@ -0,0 +1,57 @@
1
+ # -*- encoding: binary -*-
2
+
3
+ # A combination of the Coolio and ThreadPool models. This allows Ruby
4
+ # Thread-based concurrency for application processing. It DOES NOT
5
+ # expose a streamable "rack.input" for upload processing within the
6
+ # app. DevFdResponse should be used with this class to proxy
7
+ # asynchronous responses. All network I/O between the client and
8
+ # server are handled by the main thread and outside of the core
9
+ # application dispatch.
10
+ #
11
+ # Unlike ThreadPool, Cool.io makes this model highly suitable for
12
+ # slow clients and applications with medium-to-slow response times
13
+ # (I/O bound), but less suitable for sleepy applications.
14
+ #
15
+ # This concurrency model is designed for Ruby 1.9, and Ruby 1.8
16
+ # users are NOT advised to use this due to high CPU usage.
17
+ module Rainbows::CoolioThreadPool
18
+ # :stopdoc:
19
+ DEFAULTS = {
20
+ :pool_size => 20, # same default size as ThreadPool (w/o Coolio)
21
+ }
22
+ #:startdoc:
23
+
24
+ def self.setup # :nodoc:
25
+ o = Rainbows::O
26
+ DEFAULTS.each { |k,v| o[k] ||= v }
27
+ Integer === o[:pool_size] && o[:pool_size] > 0 or
28
+ raise ArgumentError, "pool_size must a be an Integer > 0"
29
+ end
30
+ include Rainbows::Coolio::Core
31
+
32
+ def init_worker_threads(master, queue) # :nodoc:
33
+ Rainbows::O[:pool_size].times.map do
34
+ Thread.new do
35
+ begin
36
+ client = queue.pop
37
+ master << [ client, client.app_response ]
38
+ rescue => e
39
+ Rainbows::Error.listen_loop(e)
40
+ end while true
41
+ end
42
+ end
43
+ end
44
+
45
+ def init_worker_process(worker) # :nodoc:
46
+ super
47
+ cloop = Coolio::Loop.default
48
+ master = Rainbows::Coolio::Master.new(Queue.new).attach(cloop)
49
+ queue = Client.const_set(:QUEUE, Queue.new)
50
+ threads = init_worker_threads(master, queue)
51
+ Watcher.new(threads).attach(cloop)
52
+ logger.info "CoolioThreadPool pool_size=#{Rainbows::O[:pool_size]}"
53
+ end
54
+ end
55
+ # :enddoc:
56
+ require 'rainbows/coolio_thread_pool/client'
57
+ require 'rainbows/coolio_thread_pool/watcher'
@@ -0,0 +1,8 @@
1
+ # -*- encoding: binary -*-
2
+ # :enddoc:
3
+ class Rainbows::CoolioThreadSpawn::Client < Rainbows::Coolio::ThreadClient
4
+ # MASTER will be set in worker_loop
5
+ def app_dispatch
6
+ Thread.new(self) { |client| MASTER << [ client, app_response ] }
7
+ end
8
+ end
@@ -0,0 +1,27 @@
1
+ # -*- encoding: binary -*-
2
+ # A combination of the Coolio and ThreadSpawn models. This allows Ruby
3
+ # Thread-based concurrency for application processing. It DOES NOT
4
+ # expose a streamable "rack.input" for upload processing within the
5
+ # app. DevFdResponse should be used with this class to proxy
6
+ # asynchronous responses. All network I/O between the client and
7
+ # server are handled by the main thread and outside of the core
8
+ # application dispatch.
9
+ #
10
+ # Unlike ThreadSpawn, Cool.io makes this model highly suitable for
11
+ # slow clients and applications with medium-to-slow response times
12
+ # (I/O bound), but less suitable for sleepy applications.
13
+ #
14
+ # This concurrency model is designed for Ruby 1.9, and Ruby 1.8
15
+ # users are NOT advised to use this due to high CPU usage.
16
+ module Rainbows::CoolioThreadSpawn
17
+ include Rainbows::Coolio::Core
18
+
19
+ def init_worker_process(worker) # :nodoc:
20
+ super
21
+ master = Rainbows::Coolio::Master.new(Queue.new)
22
+ master.attach(Coolio::Loop.default)
23
+ Client.const_set(:MASTER, master)
24
+ end
25
+ end
26
+ # :enddoc:
27
+ require 'rainbows/coolio_thread_spawn/client'
@@ -29,7 +29,11 @@ class Rainbows::DevFdResponse < Struct.new(:app)
29
29
  status, headers, body = response = app.call(env)
30
30
 
31
31
  # totally uninteresting to us if there's no body
32
- return response if STATUS_WITH_NO_ENTITY_BODY.include?(status)
32
+ if STATUS_WITH_NO_ENTITY_BODY.include?(status.to_i) ||
33
+ File === body ||
34
+ (body.respond_to?(:to_path) && File.file?(body.to_path))
35
+ return response
36
+ end
33
37
 
34
38
  io = body.to_io if body.respond_to?(:to_io)
35
39
  io ||= File.open(body.to_path) if body.respond_to?(:to_path)
@@ -53,7 +57,7 @@ class Rainbows::DevFdResponse < Struct.new(:app)
53
57
 
54
58
  # we need to make sure our pipe output is Fiber-compatible
55
59
  case env["rainbows.model"]
56
- when :FiberSpawn, :FiberPool, :RevFiberSpawn
60
+ when :FiberSpawn, :FiberPool, :RevFiberSpawn, :CoolioFiberSpawn
57
61
  io.respond_to?(:kgio_wait_readable) or
58
62
  io = Rainbows::Fiber::IO.new(io)
59
63
  when :Revactor
@@ -1,8 +1,9 @@
1
1
  # -*- encoding: binary -*-
2
2
  # :enddoc:
3
3
  class Rainbows::EvCore::CapInput
4
- def initialize(io, client)
5
- @io, @client, @bytes_left = io, client, Rainbows.max_bytes
4
+
5
+ def initialize(io, client, max)
6
+ @io, @client, @bytes_left = io, client, max
6
7
  end
7
8
 
8
9
  def <<(buf)
@@ -86,6 +86,8 @@ module Rainbows::EvCore
86
86
  @hp.filter_body(@buf2, @buf << data)
87
87
  @input << @buf2
88
88
  on_read("")
89
+ else
90
+ want_more
89
91
  end
90
92
  when :trailers
91
93
  if @hp.trailers(@env, @buf << data)
@@ -106,8 +108,12 @@ module Rainbows::EvCore
106
108
  raise IOError, msg, []
107
109
  end
108
110
 
109
- MAX_BODY = Unicorn::Const::MAX_BODY
110
111
  TmpIO = Unicorn::TmpIO
112
+
113
+ def io_for(bytes)
114
+ bytes <= CBB ? StringIO.new("") : TmpIO.new
115
+ end
116
+
111
117
  def mkinput
112
118
  max = Rainbows.max_bytes
113
119
  len = @hp.content_length
@@ -115,9 +121,13 @@ module Rainbows::EvCore
115
121
  if max && (len > max)
116
122
  err_413("Content-Length too big: #{len} > #{max}")
117
123
  end
118
- len <= MAX_BODY ? StringIO.new("") : TmpIO.new
124
+ io_for(len)
119
125
  else
120
- max ? CapInput.new(TmpIO.new, self) : TmpIO.new
126
+ max ? CapInput.new(io_for(max), self, max) : TmpIO.new
121
127
  end
122
128
  end
129
+
130
+ def self.setup
131
+ const_set :CBB, Unicorn::TeeInput.client_body_buffer_size
132
+ end
123
133
  end
@@ -0,0 +1,124 @@
1
+ # -*- encoding: binary -*-
2
+ # :enddoc:
3
+ class Rainbows::EventMachine::Client < EM::Connection
4
+ attr_writer :body
5
+ include Rainbows::EvCore
6
+
7
+ def initialize(io)
8
+ @_io = io
9
+ @body = nil
10
+ end
11
+
12
+ alias write send_data
13
+
14
+ def receive_data(data)
15
+ # To avoid clobbering the current streaming response
16
+ # (often a static file), we do not attempt to process another
17
+ # request on the same connection until the first is complete
18
+ if @body
19
+ if data
20
+ @buf << data
21
+ @_io.shutdown(Socket::SHUT_RD) if @buf.size > 0x1c000
22
+ end
23
+ EM.next_tick { receive_data(nil) } unless @buf.empty?
24
+ else
25
+ on_read(data || "") if (@buf.size > 0) || data
26
+ end
27
+ end
28
+
29
+ def quit
30
+ super
31
+ close_connection_after_writing
32
+ end
33
+
34
+ def app_call
35
+ set_comm_inactivity_timeout 0
36
+ @env[RACK_INPUT] = @input
37
+ @env[REMOTE_ADDR] = @_io.kgio_addr
38
+ @env[ASYNC_CALLBACK] = method(:em_write_response)
39
+ @env[ASYNC_CLOSE] = EM::DefaultDeferrable.new
40
+
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
47
+
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)
58
+ end
59
+ end
60
+
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
68
+ end
69
+
70
+ 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
77
+ elsif body.respond_to?(:to_path)
78
+ st = File.stat(path = body.to_path)
79
+
80
+ 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
92
+ return
93
+ elsif st.socket? || st.pipe?
94
+ @body = io = body_to_io(body)
95
+ chunk = stream_response_headers(status, headers) if headers
96
+ m = chunk ? Rainbows::EventMachine::ResponseChunkPipe :
97
+ Rainbows::EventMachine::ResponsePipe
98
+ return EM.watch(io, m, self, alive, body).notify_readable = true
99
+ end
100
+ # char or block device... WTF? fall through to body.each
101
+ end
102
+
103
+ write(response_header(status, headers)) if headers
104
+ write_body_each(self, body)
105
+ quit unless alive
106
+ end
107
+
108
+ def next!
109
+ @hp.keepalive? ? receive_data(@body = nil) : quit
110
+ end
111
+
112
+ def unbind
113
+ async_close = @env[ASYNC_CLOSE] and async_close.succeed
114
+ @body.respond_to?(:fail) and @body.fail
115
+ begin
116
+ @_io.close
117
+ rescue Errno::EBADF
118
+ # EventMachine's EventableDescriptor::Close() may close
119
+ # the underlying file descriptor without invalidating the
120
+ # associated IO object on errors, so @_io.closed? isn't
121
+ # sufficient.
122
+ end
123
+ end
124
+ end
@@ -22,9 +22,8 @@ module Rainbows::EventMachine::ResponsePipe
22
22
  end
23
23
 
24
24
  def unbind
25
- @client.body = nil
26
- @alive ? @client.on_read('') : @client.quit
27
25
  @body.close if @body.respond_to?(:close)
26
+ @client.next!
28
27
  @io.close unless @io.closed?
29
28
  end
30
29
  end
@@ -0,0 +1,15 @@
1
+ # -*- encoding: binary -*-
2
+ module Rainbows::EventMachine::Server # :nodoc: all
3
+ def close
4
+ detach
5
+ @io.close
6
+ end
7
+
8
+ # CL, CUR and MAX will be set when worker_loop starts
9
+ def notify_readable
10
+ return if CUR.size >= MAX
11
+ io = @io.kgio_tryaccept or return
12
+ sig = EM.attach_fd(io.fileno, false)
13
+ CUR[sig] = CL.new(sig, io)
14
+ end
15
+ end
@@ -1,7 +1,6 @@
1
1
  # -*- encoding: binary -*-
2
2
  require 'eventmachine'
3
3
  EM::VERSION >= '0.12.10' or abort 'eventmachine 0.12.10 is required'
4
- require 'rainbows/ev_core'
5
4
 
6
5
  # Implements a basic single-threaded event model with
7
6
  # {EventMachine}[http://rubyeventmachine.com/]. It is capable of
@@ -42,141 +41,11 @@ require 'rainbows/ev_core'
42
41
  # "rack.input" will be fully buffered in memory or to a temporary file
43
42
  # before the application is entered.
44
43
  module Rainbows::EventMachine
45
-
46
- include Rainbows::Base
47
44
  autoload :ResponsePipe, 'rainbows/event_machine/response_pipe'
48
45
  autoload :ResponseChunkPipe, 'rainbows/event_machine/response_chunk_pipe'
49
46
  autoload :TryDefer, 'rainbows/event_machine/try_defer'
50
47
 
51
- class Client < EM::Connection # :nodoc: all
52
- attr_writer :body
53
- include Rainbows::EvCore
54
-
55
- def initialize(io)
56
- @_io = io
57
- @body = nil
58
- end
59
-
60
- alias write send_data
61
-
62
- def receive_data(data)
63
- # To avoid clobbering the current streaming response
64
- # (often a static file), we do not attempt to process another
65
- # request on the same connection until the first is complete
66
- if @body
67
- @buf << data
68
- @_io.shutdown(Socket::SHUT_RD) if @buf.size > 0x1c000
69
- EM.next_tick { receive_data('') }
70
- else
71
- on_read(data)
72
- end
73
- end
74
-
75
- def quit
76
- super
77
- close_connection_after_writing
78
- end
79
-
80
- def app_call
81
- set_comm_inactivity_timeout 0
82
- @env[RACK_INPUT] = @input
83
- @env[REMOTE_ADDR] = @_io.kgio_addr
84
- @env[ASYNC_CALLBACK] = method(:em_write_response)
85
- @env[ASYNC_CLOSE] = EM::DefaultDeferrable.new
86
-
87
- response = catch(:async) { APP.call(@env.update(RACK_DEFAULTS)) }
88
-
89
- # too tricky to support pipelining with :async since the
90
- # second (pipelined) request could be a stuck behind a
91
- # long-running async response
92
- (response.nil? || -1 == response[0]) and return @state = :close
93
-
94
- alive = @hp.keepalive? && G.alive && G.kato > 0
95
- em_write_response(response, alive)
96
- if alive
97
- @hp.reset
98
- @state = :headers
99
- if @buf.empty?
100
- set_comm_inactivity_timeout(G.kato)
101
- else
102
- EM.next_tick { receive_data('') }
103
- end
104
- end
105
- end
106
-
107
- def em_write_response(response, alive = false)
108
- status, headers, body = response
109
- if @hp.headers?
110
- headers = HH.new(headers)
111
- headers[CONNECTION] = alive ? KEEP_ALIVE : CLOSE
112
- else
113
- headers = nil
114
- end
115
-
116
- if body.respond_to?(:errback) && body.respond_to?(:callback)
117
- @body = body
118
- body.callback { quit }
119
- body.errback { quit }
120
- # async response, this could be a trickle as is in comet-style apps
121
- headers[CONNECTION] = CLOSE if headers
122
- alive = true
123
- elsif body.respond_to?(:to_path)
124
- st = File.stat(path = body.to_path)
125
-
126
- if st.file?
127
- write(response_header(status, headers)) if headers
128
- @body = stream_file_data(path)
129
- @body.errback do
130
- body.close if body.respond_to?(:close)
131
- quit
132
- end
133
- @body.callback do
134
- body.close if body.respond_to?(:close)
135
- @body = nil
136
- alive ? receive_data('') : quit
137
- end
138
- return
139
- elsif st.socket? || st.pipe?
140
- @body = io = body_to_io(body)
141
- chunk = stream_response_headers(status, headers) if headers
142
- m = chunk ? ResponseChunkPipe : ResponsePipe
143
- return EM.watch(io, m, self, alive, body).notify_readable = true
144
- end
145
- # char or block device... WTF? fall through to body.each
146
- end
147
-
148
- write(response_header(status, headers)) if headers
149
- write_body_each(self, body)
150
- quit unless alive
151
- end
152
-
153
- def unbind
154
- async_close = @env[ASYNC_CLOSE] and async_close.succeed
155
- @body.respond_to?(:fail) and @body.fail
156
- begin
157
- @_io.close
158
- rescue Errno::EBADF
159
- # EventMachine's EventableDescriptor::Close() may close
160
- # the underlying file descriptor without invalidating the
161
- # associated IO object on errors, so @_io.closed? isn't
162
- # sufficient.
163
- end
164
- end
165
- end
166
-
167
- module Server # :nodoc: all
168
- def close
169
- detach
170
- @io.close
171
- end
172
-
173
- def notify_readable
174
- return if CUR.size >= MAX
175
- io = @io.kgio_tryaccept or return
176
- sig = EM.attach_fd(io.fileno, false)
177
- CUR[sig] = CL.new(sig, io)
178
- end
179
- end
48
+ include Rainbows::Base
180
49
 
181
50
  def init_worker_process(worker) # :nodoc:
182
51
  Rainbows::Response.setup(Rainbows::EventMachine::Client)
@@ -189,20 +58,22 @@ module Rainbows::EventMachine
189
58
  def worker_loop(worker) # :nodoc:
190
59
  init_worker_process(worker)
191
60
  G.server.app.respond_to?(:deferred?) and
192
- G.server.app = TryDefer[G.server.app]
61
+ G.server.app = Rainbows::EventMachine::TryDefer[G.server.app]
193
62
 
194
63
  # enable them both, should be non-fatal if not supported
195
64
  EM.epoll
196
65
  EM.kqueue
197
66
  logger.info "#@use: epoll=#{EM.epoll?} kqueue=#{EM.kqueue?}"
198
67
  client_class = Rainbows.const_get(@use).const_get(:Client)
199
- Server.const_set(:MAX, worker_connections + LISTENERS.size)
200
- Server.const_set(:CL, client_class)
68
+ max = worker_connections + LISTENERS.size
69
+ Rainbows::EventMachine::Server.const_set(:MAX, max)
70
+ Rainbows::EventMachine::Server.const_set(:CL, client_class)
201
71
  client_class.const_set(:APP, G.server.app)
72
+ Rainbows::EvCore.setup
202
73
  EM.run {
203
74
  conns = EM.instance_variable_get(:@conns) or
204
75
  raise RuntimeError, "EM @conns instance variable not accessible!"
205
- Server.const_set(:CUR, conns)
76
+ Rainbows::EventMachine::Server.const_set(:CUR, conns)
206
77
  EM.add_periodic_timer(1) do
207
78
  unless G.tick
208
79
  conns.each_value { |c| client_class === c and c.quit }
@@ -210,8 +81,13 @@ module Rainbows::EventMachine
210
81
  end
211
82
  end
212
83
  LISTENERS.map! do |s|
213
- EM.watch(s, Server) { |c| c.notify_readable = true }
84
+ EM.watch(s, Rainbows::EventMachine::Server) do |c|
85
+ c.notify_readable = true
86
+ end
214
87
  end
215
88
  }
216
89
  end
217
90
  end
91
+ # :enddoc:
92
+ require 'rainbows/event_machine/client'
93
+ require 'rainbows/event_machine/server'
@@ -18,11 +18,10 @@ module Rainbows::Fiber::Base
18
18
  # for one second (returned by the schedule_sleepers method) which
19
19
  # will cause it.
20
20
  def schedule(&block)
21
- ret = begin
21
+ begin
22
22
  G.tick
23
- RD.compact.each { |c| c.f.resume } # attempt to time out idle clients
24
23
  t = schedule_sleepers
25
- Kernel.select(RD.compact.concat(LISTENERS), WR.compact, nil, t) or return
24
+ ret = select(RD.compact.concat(LISTENERS), WR.compact, nil, t)
26
25
  rescue Errno::EINTR
27
26
  retry
28
27
  rescue Errno::EBADF, TypeError
@@ -30,15 +29,15 @@ module Rainbows::Fiber::Base
30
29
  raise
31
30
  end or return
32
31
 
33
- # active writers first, then _all_ readers for keepalive timeout
34
- ret[1].concat(RD.compact).each { |c| c.f.resume }
32
+ # active writers first, then readers
33
+ ret[1].concat(RD.compact & ret[0]).each { |c| c.f.resume }
35
34
 
36
35
  # accept is an expensive syscall, filter out listeners we don't want
37
36
  (ret[0] & LISTENERS).each(&block)
38
37
  end
39
38
 
40
- # wakes up any sleepers that need to be woken and
41
- # returns an interval to IO.select on
39
+ # wakes up any sleepers or keepalive-timeout violators that need to be
40
+ # woken and returns an interval to IO.select on
42
41
  def schedule_sleepers
43
42
  max = nil
44
43
  now = Time.now
@@ -11,9 +11,9 @@ module Rainbows::Fiber::Body # :nodoc:
11
11
  }
12
12
 
13
13
  # the sendfile 1.0.0+ gem includes IO#sendfile_nonblock
14
- if ::IO.method_defined?(:sendfile_nonblock)
14
+ if IO.method_defined?(:sendfile_nonblock)
15
15
  def write_body_file(client, body, range)
16
- sock, n = client.to_io, nil
16
+ sock, n, body = client.to_io, nil, body_to_io(body)
17
17
  offset, count = range ? range : [ 0, body.stat.size ]
18
18
  begin
19
19
  offset += (n = sock.sendfile_nonblock(body, offset, count))
@@ -23,6 +23,8 @@ module Rainbows::Fiber::Body # :nodoc:
23
23
  rescue EOFError
24
24
  break
25
25
  end while (count -= n) > 0
26
+ ensure
27
+ close_if_private(body)
26
28
  end
27
29
  else
28
30
  ALIASES[:write_body] = :write_body_each
@@ -0,0 +1,15 @@
1
+ # -*- encoding: binary -*-
2
+ # :enddoc:
3
+ class Rainbows::Fiber::Coolio::Heartbeat < Coolio::TimerWatcher
4
+ G = Rainbows::G
5
+
6
+ # ZZ gets populated by read_expire in rainbows/fiber/io/methods
7
+ ZZ = Rainbows::Fiber::ZZ
8
+ def on_timer
9
+ exit if (! G.tick && G.cur <= 0)
10
+ now = Time.now
11
+ fibs = []
12
+ ZZ.delete_if { |fib, time| now >= time ? fibs << fib : ! fib.alive? }
13
+ fibs.each { |fib| fib.resume if fib.alive? }
14
+ end
15
+ end
@@ -1,11 +1,11 @@
1
1
  # -*- encoding: binary -*-
2
2
  # :enddoc:
3
- module Rainbows::Fiber::Rev::Methods
4
- class Watcher < Rev::IOWatcher
3
+ module Rainbows::Fiber::Coolio::Methods
4
+ class Watcher < Coolio::IOWatcher
5
5
  def initialize(fio, flag)
6
6
  @f = Fiber.current
7
7
  super(fio, flag)
8
- attach(Rev::Loop.default)
8
+ attach(Coolio::Loop.default)
9
9
  end
10
10
 
11
11
  def on_readable
@@ -31,7 +31,6 @@ module Rainbows::Fiber::Rev::Methods
31
31
  def kgio_wait_readable
32
32
  @r = Watcher.new(self, :r) unless defined?(@r)
33
33
  @r.enable unless @r.enabled?
34
- KATO << Fiber.current
35
34
  Fiber.yield
36
35
  @r.disable
37
36
  end
@@ -44,5 +43,5 @@ end
44
43
  Rainbows::Fiber::IO::Socket,
45
44
  Rainbows::Fiber::IO::Pipe
46
45
  ].each do |klass|
47
- klass.__send__(:include, Rainbows::Fiber::Rev::Methods)
46
+ klass.__send__(:include, Rainbows::Fiber::Coolio::Methods)
48
47
  end
@@ -1,6 +1,6 @@
1
1
  # -*- encoding: binary -*-
2
2
  # :enddoc:
3
- class Rainbows::Fiber::Rev::Server < Rev::IOWatcher
3
+ class Rainbows::Fiber::Coolio::Server < Coolio::IOWatcher
4
4
  G = Rainbows::G
5
5
  include Rainbows::ProcessClient
6
6
 
@@ -1,11 +1,11 @@
1
1
  # -*- encoding: binary -*-
2
2
  # :enddoc:
3
- class Rainbows::Fiber::Rev::Sleeper < Rev::TimerWatcher
3
+ class Rainbows::Fiber::Coolio::Sleeper < Coolio::TimerWatcher
4
4
 
5
5
  def initialize(seconds)
6
6
  @f = Fiber.current
7
7
  super(seconds, false)
8
- attach(Rev::Loop.default)
8
+ attach(Coolio::Loop.default)
9
9
  Fiber.yield
10
10
  end
11
11
 
@@ -0,0 +1,12 @@
1
+ # -*- encoding: binary -*-
2
+ # :enddoc:
3
+ require 'rainbows/coolio_support'
4
+ require 'rainbows/fiber'
5
+ require 'rainbows/fiber/io'
6
+
7
+ module Rainbows::Fiber::Coolio
8
+ autoload :Heartbeat, 'rainbows/fiber/coolio/heartbeat'
9
+ autoload :Server, 'rainbows/fiber/coolio/server'
10
+ autoload :Sleeper, 'rainbows/fiber/coolio/sleeper'
11
+ end
12
+ require 'rainbows/fiber/coolio/methods'
@@ -8,8 +8,13 @@
8
8
  module Rainbows::Fiber::IO::Methods
9
9
  RD = Rainbows::Fiber::RD
10
10
  WR = Rainbows::Fiber::WR
11
+ ZZ = Rainbows::Fiber::ZZ
11
12
  attr_accessor :f
12
13
 
14
+ def read_expire
15
+ ZZ[Fiber.current] = super
16
+ end
17
+
13
18
  # for wrapping output response bodies
14
19
  def each(&block)
15
20
  if buf = kgio_read(16384)
@@ -30,6 +35,7 @@ module Rainbows::Fiber::IO::Methods
30
35
  @f = Fiber.current
31
36
  RD[fd] = self
32
37
  Fiber.yield
38
+ ZZ.delete @f
33
39
  RD[fd] = nil
34
40
  end
35
41