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
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