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
data/Rakefile CHANGED
@@ -3,105 +3,8 @@ autoload :Gem, 'rubygems'
3
3
  autoload :Tempfile, 'tempfile'
4
4
 
5
5
  # most tasks are in the GNUmakefile which offers better parallelism
6
-
7
- def tags
8
- timefmt = '%Y-%m-%dT%H:%M:%SZ'
9
- @tags ||= `git tag -l`.split(/\n/).map do |tag|
10
- if %r{\Av[\d\.]+} =~ tag
11
- header, subject, body = `git cat-file tag #{tag}`.split(/\n\n/, 3)
12
- header = header.split(/\n/)
13
- tagger = header.grep(/\Atagger /).first
14
- body ||= "initial"
15
- {
16
- :time => Time.at(tagger.split(/ /)[-2].to_i).utc.strftime(timefmt),
17
- :tagger_name => %r{^tagger ([^<]+)}.match(tagger)[1].strip,
18
- :tagger_email => %r{<([^>]+)>}.match(tagger)[1].strip,
19
- :id => `git rev-parse refs/tags/#{tag}`.chomp!,
20
- :tag => tag,
21
- :subject => subject,
22
- :body => body,
23
- }
24
- end
25
- end.compact.sort { |a,b| b[:time] <=> a[:time] }
26
- end
27
-
28
6
  cgit_url = "http://git.bogomips.org/cgit/rainbows.git"
29
- git_url = ENV['GIT_URL'] || 'git://git.bogomips.org/rainbows.git'
30
-
31
- desc 'prints news as an Atom feed'
32
- task :news_atom do
33
- require 'nokogiri'
34
- new_tags = tags[0,10]
35
- puts(Nokogiri::XML::Builder.new do
36
- feed :xmlns => "http://www.w3.org/2005/Atom" do
37
- id! "http://rainbows.rubyforge.org/NEWS.atom.xml"
38
- title "Rainbows! news"
39
- subtitle "Unicorn for sleepy apps and slow clients"
40
- link! :rel => 'alternate', :type => 'text/html',
41
- :href => 'http://rainbows.rubyforge.org/NEWS.html'
42
- updated(new_tags.empty? ? "1970-01-01T00:00:00Z" : new_tags.first[:time])
43
- new_tags.each do |tag|
44
- entry do
45
- title tag[:subject]
46
- updated tag[:time]
47
- published tag[:time]
48
- author {
49
- name tag[:tagger_name]
50
- email tag[:tagger_email]
51
- }
52
- url = "#{cgit_url}/tag/?id=#{tag[:tag]}"
53
- link! :rel => "alternate", :type => "text/html", :href =>url
54
- id! url
55
- message_only = tag[:body].split(/\n.+\(\d+\):\n {6}/s).first.strip
56
- content({:type =>:text}, message_only)
57
- content(:type =>:xhtml) { pre tag[:body] }
58
- end
59
- end
60
- end
61
- end.to_xml)
62
- end
63
-
64
- desc 'prints RDoc-formatted news'
65
- task :news_rdoc do
66
- tags.each do |tag|
67
- time = tag[:time].tr!('T', ' ').gsub!(/:\d\dZ/, ' UTC')
68
- puts "=== #{tag[:tag].sub(/^v/, '')} / #{time}"
69
- puts ""
70
-
71
- body = tag[:body]
72
- puts tag[:body].gsub(/^/sm, " ").gsub(/[ \t]+$/sm, "")
73
- puts ""
74
- end
75
- end
76
-
77
- desc "print release changelog for Rubyforge"
78
- task :release_changes do
79
- version = ENV['VERSION'] or abort "VERSION= needed"
80
- version = "v#{version}"
81
- vtags = tags.map { |tag| tag[:tag] =~ /\Av/ and tag[:tag] }.sort
82
- prev = vtags[vtags.index(version) - 1]
83
- if prev
84
- system('git', 'diff', '--stat', prev, version) or abort $?
85
- puts ""
86
- system('git', 'log', "#{prev}..#{version}") or abort $?
87
- else
88
- system('git', 'log', version) or abort $?
89
- end
90
- end
91
-
92
- desc "print release notes for Rubyforge"
93
- task :release_notes do
94
- spec = Gem::Specification.load('rainbows.gemspec')
95
- puts spec.description.strip
96
- puts ""
97
- puts "* #{spec.homepage}"
98
- puts "* #{spec.email}"
99
- puts "* #{git_url}"
100
-
101
- _, _, body = `git cat-file tag v#{spec.version}`.split(/\n\n/, 3)
102
- print "\nChanges:\n\n"
103
- puts body
104
- end
7
+ git_url = 'git://git.bogomips.org/rainbows.git'
105
8
 
106
9
  desc "read news article from STDIN and post to rubyforge"
107
10
  task :publish_news do
data/Summary CHANGED
@@ -1,7 +0,0 @@
1
- = \Rainbows! at a glance
2
-
3
- Confused by all the options we give you? So are we! Here's some tables
4
- to help keep your head straight. Remember, engineering is all about
5
- trade-offs.
6
-
7
- INCLUDE
data/TODO CHANGED
@@ -24,11 +24,11 @@ care about.
24
24
  (those who do not require streaming input can use
25
25
  {rack-fiber_pool}[http://github.com/mperham/rack-fiber_pool])
26
26
 
27
- * RevFiberPool
27
+ * CoolioFiberPool
28
28
 
29
29
  * ThreadPoolRevFiber{Spawn,Pool}: just because
30
30
 
31
- * Rev + callcc - current Rev model with callcc (should work with MBARI)
31
+ * Coolio + callcc - current Coolio model with callcc (should work with MBARI)
32
32
 
33
33
  * Omnibus - haven't looked into it, probably like Revactor with 1.8?
34
34
 
@@ -88,7 +88,8 @@ class Rainbows::AppPool < Struct.new(:pool, :re)
88
88
  # concurrency models
89
89
  self.re ||= begin
90
90
  case env["rainbows.model"]
91
- when :FiberSpawn, :FiberPool, :Revactor, :NeverBlock, :RevFiberSpawn
91
+ when :FiberSpawn, :FiberPool, :Revactor, :NeverBlock,
92
+ :RevFiberSpawn, :CoolioFiberSpawn
92
93
  self.pool = Rainbows::Fiber::Queue.new(pool)
93
94
  end
94
95
  true
data/lib/rainbows/base.rb CHANGED
@@ -18,6 +18,7 @@ module Rainbows::Base
18
18
  super(worker)
19
19
  Rainbows::Response.setup(self.class)
20
20
  Rainbows::MaxBody.setup
21
+ Rainbows::RackInput.setup
21
22
  G.tmp = worker.tmp
22
23
 
23
24
  listeners = Rainbows::HttpServer::LISTENERS
@@ -13,6 +13,7 @@ module Rainbows::Configurator
13
13
  # worker_connections 400
14
14
  # keepalive_timeout 0 # zero disables keepalives entirely
15
15
  # client_max_body_size 5*1024*1024 # 5 megabytes
16
+ # keepalive_requests 666 # default:100
16
17
  # end
17
18
  #
18
19
  # # the rest of the Unicorn configuration
@@ -33,6 +34,14 @@ module Rainbows::Configurator
33
34
  # The default +client_max_body_size+ is 1 megabyte (1024 * 1024 bytes),
34
35
  # setting this to +nil+ will disable body size checks and allow any
35
36
  # size to be specified.
37
+ #
38
+ # The default +keepalive_requests+ is 100, meaning a client may
39
+ # complete 100 keepalive requests after the initial request before
40
+ # \Rainbows! forces a disconnect. Lowering this can improve
41
+ # load-balancing characteristics as it forces HTTP/1.1 clients to
42
+ # reconnect after the specified number of requests, hopefully to a
43
+ # less busy host or worker process. This may also be used to mitigate
44
+ # denial-of-service attacks that use HTTP pipelining.
36
45
  def Rainbows!(&block)
37
46
  block_given? or raise ArgumentError, "Rainbows! requires a block"
38
47
  Rainbows::HttpServer.setup(block)
@@ -2,7 +2,7 @@
2
2
  # :enddoc:
3
3
  module Rainbows::Const
4
4
 
5
- RAINBOWS_VERSION = '2.0.1'
5
+ RAINBOWS_VERSION = '2.1.0'
6
6
 
7
7
  include Unicorn::Const
8
8
 
@@ -0,0 +1,191 @@
1
+ # -*- encoding: binary -*-
2
+ # :enddoc:
3
+ class Rainbows::Coolio::Client < Coolio::IO
4
+ include Rainbows::EvCore
5
+ G = Rainbows::G
6
+ SF = Rainbows::StreamFile
7
+ CONN = Rainbows::Coolio::CONN
8
+ KATO = Rainbows::Coolio::KATO
9
+ DeferredResponse = Rainbows::Coolio::DeferredResponse
10
+ DeferredChunkResponse = Rainbows::Coolio::DeferredChunkResponse
11
+
12
+ def initialize(io)
13
+ CONN[self] = false
14
+ super(io)
15
+ post_init
16
+ @deferred = nil
17
+ end
18
+
19
+ def want_more
20
+ enable unless enabled?
21
+ end
22
+
23
+ def quit
24
+ super
25
+ close if @deferred.nil? && @_write_buffer.empty?
26
+ end
27
+
28
+ # override the Coolio::IO#write method try to write directly to the
29
+ # kernel socket buffers to avoid an extra userspace copy if
30
+ # possible.
31
+ def write(buf)
32
+ if @_write_buffer.empty?
33
+ begin
34
+ case rv = @_io.kgio_trywrite(buf)
35
+ when nil
36
+ return enable_write_watcher
37
+ when :wait_writable
38
+ break # fall through to super(buf)
39
+ when String
40
+ buf = rv # retry, skb could grow or been drained
41
+ end
42
+ rescue => e
43
+ return handle_error(e)
44
+ end while true
45
+ end
46
+ super(buf)
47
+ end
48
+
49
+ def on_readable
50
+ buf = @_io.kgio_tryread(16384)
51
+ case buf
52
+ when :wait_readable
53
+ when nil # eof
54
+ close
55
+ else
56
+ on_read buf
57
+ end
58
+ rescue Errno::ECONNRESET
59
+ close
60
+ end
61
+
62
+ # queued, optional response bodies, it should only be unpollable "fast"
63
+ # devices where read(2) is uninterruptable. Unfortunately, NFS and ilk
64
+ # are also part of this. We'll also stick DeferredResponse bodies in
65
+ # here to prevent connections from being closed on us.
66
+ def defer_body(io)
67
+ @deferred = io
68
+ enable_write_watcher
69
+ end
70
+
71
+ # allows enabling of write watcher even when read watcher is disabled
72
+ def evloop
73
+ LOOP # this constant is set in when a worker starts
74
+ end
75
+
76
+ def next!
77
+ attached? or return
78
+ @deferred = nil
79
+ enable_write_watcher
80
+ end
81
+
82
+ def timeout?
83
+ @deferred.nil? && @_write_buffer.empty? and close.nil?
84
+ end
85
+
86
+ # used for streaming sockets and pipes
87
+ def stream_response(status, headers, io, body)
88
+ c = stream_response_headers(status, headers) if headers
89
+ # we only want to attach to the Coolio::Loop belonging to the
90
+ # main thread in Ruby 1.9
91
+ io = (c ? DeferredChunkResponse : DeferredResponse).new(io, self, body)
92
+ defer_body(io.attach(LOOP))
93
+ end
94
+
95
+ def coolio_write_response(response, alive)
96
+ status, headers, body = response
97
+ headers = @hp.headers? ? HH.new(headers) : nil
98
+
99
+ headers[CONNECTION] = alive ? KEEP_ALIVE : CLOSE if headers
100
+ if body.respond_to?(:to_path)
101
+ io = body_to_io(body)
102
+ st = io.stat
103
+
104
+ if st.file?
105
+ offset, count = 0, st.size
106
+ if headers
107
+ if range = make_range!(@env, status, headers)
108
+ status, offset, count = range
109
+ end
110
+ write(response_header(status, headers))
111
+ end
112
+ return defer_body(SF.new(offset, count, io, body))
113
+ elsif st.socket? || st.pipe?
114
+ return stream_response(status, headers, io, body)
115
+ end
116
+ # char or block device... WTF? fall through to body.each
117
+ end
118
+ write(response_header(status, headers)) if headers
119
+ write_body_each(self, body, nil)
120
+ end
121
+
122
+ def app_call
123
+ KATO.delete(self)
124
+ @env[RACK_INPUT] = @input
125
+ @env[REMOTE_ADDR] = @_io.kgio_addr
126
+ response = APP.call(@env.update(RACK_DEFAULTS))
127
+
128
+ coolio_write_response(response, alive = @hp.next? && G.alive)
129
+ return quit unless alive && :close != @state
130
+ @state = :headers
131
+ disable if enabled?
132
+ end
133
+
134
+ def on_write_complete
135
+ case @deferred
136
+ when DeferredResponse then return
137
+ when NilClass # fall through
138
+ else
139
+ begin
140
+ return rev_sendfile(@deferred)
141
+ rescue EOFError # expected at file EOF
142
+ close_deferred
143
+ end
144
+ end
145
+
146
+ case @state
147
+ when :close
148
+ close if @_write_buffer.empty?
149
+ when :headers
150
+ if @buf.empty?
151
+ unless enabled?
152
+ enable
153
+ KATO[self] = Time.now
154
+ end
155
+ else
156
+ on_read("")
157
+ end
158
+ end
159
+ rescue => e
160
+ handle_error(e)
161
+ end
162
+
163
+ def handle_error(e)
164
+ close_deferred
165
+ if msg = Rainbows::Error.response(e)
166
+ @_io.kgio_trywrite(msg) rescue nil
167
+ end
168
+ @_write_buffer.clear
169
+ ensure
170
+ quit
171
+ end
172
+
173
+ def close_deferred
174
+ case @deferred
175
+ when DeferredResponse, NilClass
176
+ else
177
+ begin
178
+ @deferred.close
179
+ rescue => e
180
+ G.server.logger.error("closing #@deferred: #{e}")
181
+ end
182
+ @deferred = nil
183
+ end
184
+ end
185
+
186
+ def on_close
187
+ close_deferred
188
+ CONN.delete(self)
189
+ KATO.delete(self)
190
+ end
191
+ end
@@ -0,0 +1,25 @@
1
+ # -*- encoding: binary -*-
2
+ # :enddoc:
3
+ module Rainbows::Coolio::Core
4
+ include Rainbows::Base
5
+
6
+ # runs inside each forked worker, this sits around and waits
7
+ # for connections and doesn't die until the parent dies (or is
8
+ # given a INT, QUIT, or TERM signal)
9
+ def worker_loop(worker)
10
+ Rainbows::Response.setup(Rainbows::Coolio::Client)
11
+ require 'rainbows/coolio/sendfile'
12
+ Rainbows::Coolio::Client.__send__(:include, Rainbows::Coolio::Sendfile)
13
+ init_worker_process(worker)
14
+ mod = Rainbows.const_get(@use)
15
+ rloop = Rainbows::Coolio::Server.const_set(:LOOP, Coolio::Loop.default)
16
+ Rainbows::Coolio::Client.const_set(:LOOP, rloop)
17
+ Rainbows::Coolio::Server.const_set(:MAX, @worker_connections)
18
+ Rainbows::Coolio::Server.const_set(:CL, mod.const_get(:Client))
19
+ Rainbows::EvCore.const_set(:APP, G.server.app)
20
+ Rainbows::EvCore.setup
21
+ Rainbows::Coolio::Heartbeat.new(1, true).attach(rloop)
22
+ LISTENERS.map! { |s| Rainbows::Coolio::Server.new(s).attach(rloop) }
23
+ rloop.run
24
+ end
25
+ end
@@ -1,8 +1,9 @@
1
1
  # -*- encoding: binary -*-
2
2
  # :enddoc:
3
3
  #
4
- # this is class is specific to Rev for proxying IO-derived objects
5
- class Rainbows::Rev::DeferredChunkResponse < Rainbows::Rev::DeferredResponse
4
+ # this is class is specific to Coolio for proxying IO-derived objects
5
+ class Rainbows::Coolio::DeferredChunkResponse <
6
+ Rainbows::Coolio::DeferredResponse
6
7
  def on_read(data)
7
8
  @client.write("#{data.size.to_s(16)}\r\n")
8
9
  @client.write(data)
@@ -1,9 +1,9 @@
1
1
  # -*- encoding: binary -*-
2
2
  # :enddoc:
3
3
  #
4
- # this is class is specific to Rev for writing large static files
4
+ # this is class is specific to Coolio for writing large static files
5
5
  # or proxying IO-derived objects
6
- class Rainbows::Rev::DeferredResponse < ::Rev::IO
6
+ class Rainbows::Coolio::DeferredResponse < Coolio::IO
7
7
  def initialize(io, client, body)
8
8
  super(io)
9
9
  @client, @body = client, body
@@ -14,7 +14,7 @@ class Rainbows::Rev::DeferredResponse < ::Rev::IO
14
14
  end
15
15
 
16
16
  def on_close
17
- @client.next! if @client.attached? # attached? is false if write fails
18
17
  @body.respond_to?(:close) and @body.close
18
+ @client.next!
19
19
  end
20
20
  end
@@ -0,0 +1,20 @@
1
+ # -*- encoding: binary -*-
2
+ # :enddoc:
3
+ # This class handles the Unicorn fchmod heartbeat mechanism
4
+ # in Coolio-based concurrency models to prevent the master
5
+ # process from killing us unless we're blocked. This class
6
+ # will also detect and execute the graceful exit if triggered
7
+ # by SIGQUIT
8
+ class Rainbows::Coolio::Heartbeat < Coolio::TimerWatcher
9
+ KATO = Rainbows::Coolio::KATO
10
+ CONN = Rainbows::Coolio::CONN
11
+ G = Rainbows::G
12
+
13
+ def on_timer
14
+ if (ot = G.kato) >= 0
15
+ ot = Time.now - ot
16
+ KATO.delete_if { |client, time| time < ot and client.timeout? }
17
+ end
18
+ exit if (! G.tick && CONN.size <= 0)
19
+ end
20
+ end
@@ -1,8 +1,7 @@
1
1
  # -*- encoding: binary -*-
2
2
  # :enddoc:
3
- require 'rainbows/rev'
4
-
5
- class Rainbows::Rev::Master < Rev::IOWatcher
3
+ require 'thread'
4
+ class Rainbows::Coolio::Master < Coolio::IOWatcher
6
5
 
7
6
  def initialize(queue)
8
7
  @reader, @writer = Kgio::Pipe.new
@@ -1,6 +1,6 @@
1
1
  # -*- encoding: binary -*-
2
2
  # :enddoc:
3
- module Rainbows::Rev::Sendfile
3
+ module Rainbows::Coolio::Sendfile
4
4
  if IO.method_defined?(:sendfile_nonblock)
5
5
  def rev_sendfile(sf) # +sf+ is a Rainbows::StreamFile object
6
6
  sf.offset += (n = @_io.sendfile_nonblock(sf, sf.offset, sf.count))
@@ -0,0 +1,11 @@
1
+ # -*- encoding: binary -*-
2
+ # :enddoc:
3
+ class Rainbows::Coolio::Server < Coolio::IO
4
+ CONN = Rainbows::Coolio::CONN
5
+ # CL and MAX will be defined in the corresponding worker loop
6
+
7
+ def on_readable
8
+ return if CONN.size >= MAX
9
+ io = @_io.kgio_tryaccept and CL.new(io).attach(LOOP)
10
+ end
11
+ end
@@ -0,0 +1,36 @@
1
+ # -*- encoding: binary -*-
2
+ # :enddoc:
3
+
4
+ RUBY_VERSION =~ %r{\A1\.8} and
5
+ warn "Coolio and Threads do not mix well under Ruby 1.8"
6
+
7
+ class Rainbows::Coolio::ThreadClient < Rainbows::Coolio::Client
8
+ def app_call
9
+ KATO.delete(self)
10
+ disable if enabled?
11
+ @env[RACK_INPUT] = @input
12
+ app_dispatch # must be implemented by subclass
13
+ end
14
+
15
+ # this is only called in the master thread
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
22
+ end
23
+
24
+ # fails-safe application dispatch, we absolutely cannot
25
+ # afford to fail or raise an exception (killing the thread)
26
+ # here because that could cause a deadlock and we'd leak FDs
27
+ def app_response
28
+ begin
29
+ @env[REMOTE_ADDR] = @_io.kgio_addr
30
+ APP.call(@env.update(RACK_DEFAULTS))
31
+ rescue => e
32
+ Rainbows::Error.app(e) # we guarantee this does not raise
33
+ [ 500, {}, [] ]
34
+ end
35
+ end
36
+ end
@@ -0,0 +1,45 @@
1
+ # -*- encoding: binary -*-
2
+ require 'rainbows/coolio_support'
3
+
4
+ # Implements a basic single-threaded event model with
5
+ # {Cool.io}[http://coolio.github.com/]. It is capable of handling
6
+ # thousands of simultaneous client connections, but with only a
7
+ # single-threaded app dispatch. It is suited for slow clients and
8
+ # fast applications (applications that do not have slow network
9
+ # dependencies) or applications that use DevFdResponse for deferrable
10
+ # response bodies. It does not require your Rack application to be
11
+ # thread-safe, reentrancy is only required for the DevFdResponse body
12
+ # generator.
13
+ #
14
+ # Compatibility: Whatever Cool.io itself supports, currently Ruby
15
+ # 1.8/1.9.
16
+ #
17
+ # This model does not implement as streaming "rack.input" which
18
+ # allows the Rack application to process data as it arrives. This
19
+ # means "rack.input" will be fully buffered in memory or to a
20
+ # temporary file before the application is entered.
21
+ module Rainbows::Coolio
22
+ # :stopdoc:
23
+ # keep-alive timeout scoreboard
24
+ KATO = {}
25
+
26
+ # all connected clients
27
+ CONN = {}
28
+
29
+ if {}.respond_to?(:compare_by_identity)
30
+ CONN.compare_by_identity
31
+ KATO.compare_by_identity
32
+ end
33
+
34
+ autoload :Master, 'rainbows/coolio/master'
35
+ autoload :ThreadClient, 'rainbows/coolio/thread_client'
36
+ autoload :DeferredChunkResponse, 'rainbows/coolio/deferred_chunk_response'
37
+ # :startdoc:
38
+ end
39
+ # :enddoc:
40
+ require 'rainbows/coolio/heartbeat'
41
+ require 'rainbows/coolio/server'
42
+ require 'rainbows/coolio/core'
43
+ require 'rainbows/coolio/deferred_response'
44
+ require 'rainbows/coolio/client'
45
+ Rainbows::Coolio.__send__ :include, Rainbows::Coolio::Core
@@ -0,0 +1,26 @@
1
+ # -*- encoding: binary -*-
2
+ require 'rainbows/fiber/coolio'
3
+
4
+ # A combination of the Coolio and FiberSpawn models. This allows Ruby
5
+ # 1.9 Fiber-based concurrency for application processing while
6
+ # exposing a synchronous execution model and using scalable network
7
+ # concurrency provided by Cool.io. A "rack.input" is exposed as well
8
+ # being Sunshowers-compatible. Applications are strongly advised to
9
+ # wrap all slow IO objects (sockets, pipes) using the
10
+ # Rainbows::Fiber::IO or a Cool.io-compatible class whenever possible.
11
+ module Rainbows::CoolioFiberSpawn
12
+
13
+ include Rainbows::Base
14
+ include Rainbows::Fiber::Coolio
15
+
16
+ def worker_loop(worker) # :nodoc:
17
+ Rainbows::Response.setup(Server)
18
+ init_worker_process(worker)
19
+ Server.const_set(:MAX, @worker_connections)
20
+ Rainbows::Fiber::Base.setup(Server, nil)
21
+ Server.const_set(:APP, G.server.app)
22
+ Heartbeat.new(1, true).attach(Coolio::Loop.default)
23
+ LISTENERS.map! { |s| Server.new(s).attach(Coolio::Loop.default) }
24
+ Coolio::Loop.default.run
25
+ end
26
+ end
@@ -0,0 +1,9 @@
1
+ # -*- encoding: binary -*-
2
+ # :enddoc:
3
+ begin
4
+ require "coolio"
5
+ Coolio::VERSION >= "1.0.0" or abort "cool.io >= 1.0.0 is required"
6
+ rescue LoadError
7
+ require "rev"
8
+ Rev::VERSION >= "0.3.0" or abort "rev >= 0.3.0 is required"
9
+ end
@@ -0,0 +1,8 @@
1
+ # -*- encoding: binary -*-
2
+ # :enddoc:
3
+ class Rainbows::CoolioThreadPool::Client < Rainbows::Coolio::ThreadClient
4
+ # QUEUE constant will be set in worker_loop
5
+ def app_dispatch
6
+ QUEUE << self
7
+ end
8
+ end
@@ -0,0 +1,14 @@
1
+ # -*- encoding: binary -*-
2
+ # :enddoc:
3
+ class Rainbows::CoolioThreadPool::Watcher < Coolio::TimerWatcher
4
+ G = Rainbows::G
5
+
6
+ def initialize(threads)
7
+ @threads = threads
8
+ super(G.server.timeout, true)
9
+ end
10
+
11
+ def on_timer
12
+ @threads.each { |t| t.join(0) and G.quit! }
13
+ end
14
+ end