rainbows 2.0.1 → 2.1.0

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