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
@@ -30,61 +30,67 @@ require 'thread'
30
30
  # Timed-out requests will cause this middleware to return with a
31
31
  # "408 Request Timeout" response.
32
32
 
33
- class Rainbows::ThreadTimeout < Struct.new(:app, :timeout,
34
- :threshold, :watchdog,
35
- :active, :lock)
33
+ class Rainbows::ThreadTimeout
36
34
 
37
35
  # :stopdoc:
38
- class ExecutionExpired < ::Exception
36
+ class ExecutionExpired < Exception
39
37
  end
40
38
 
41
39
  def initialize(app, opts)
42
- timeout = opts[:timeout]
43
- Numeric === timeout or
44
- raise TypeError, "timeout=#{timeout.inspect} is not numeric"
40
+ @timeout = opts[:timeout]
41
+ Numeric === @timeout or
42
+ raise TypeError, "timeout=#{@timeout.inspect} is not numeric"
45
43
 
46
- if threshold = opts[:threshold]
47
- Integer === threshold or
48
- raise TypeError, "threshold=#{threshold.inspect} is not an integer"
49
- threshold == 0 and
44
+ if @threshold = opts[:threshold]
45
+ Integer === @threshold or
46
+ raise TypeError, "threshold=#{@threshold.inspect} is not an integer"
47
+ @threshold == 0 and
50
48
  raise ArgumentError, "threshold=0 does not make sense"
51
- threshold < 0 and
52
- threshold += Rainbows::G.server.worker_connections
49
+ @threshold < 0 and
50
+ @threshold += Rainbows::G.server.worker_connections
53
51
  end
54
- super(app, timeout, threshold, nil, {}, Mutex.new)
52
+ @app = app
53
+ @active = {}
54
+ @lock = Mutex.new
55
55
  end
56
56
 
57
57
  def call(env)
58
- lock.synchronize do
59
- start_watchdog unless watchdog
60
- active[Thread.current] = Time.now + timeout
58
+ @lock.synchronize do
59
+ start_watchdog unless @watchdog
60
+ @active[Thread.current] = Time.now + @timeout
61
61
  end
62
62
  begin
63
- app.call(env)
63
+ @app.call(env)
64
64
  ensure
65
- lock.synchronize { active.delete(Thread.current) }
65
+ @lock.synchronize { @active.delete(Thread.current) }
66
66
  end
67
67
  rescue ExecutionExpired
68
68
  [ 408, { 'Content-Type' => 'text/plain', 'Content-Length' => '0' }, [] ]
69
69
  end
70
70
 
71
71
  def start_watchdog
72
- self.watchdog = Thread.new do
72
+ @watchdog = Thread.new do
73
73
  begin
74
- if next_wake = lock.synchronize { active.values }.min
74
+ if next_wake = @lock.synchronize { @active.values }.min
75
75
  next_wake -= Time.now
76
- sleep(next_wake) if next_wake > 0
76
+
77
+ # because of the lack of GVL-releasing syscalls in this branch
78
+ # of the thread loop, we need Thread.pass to ensure other threads
79
+ # get scheduled appropriately under 1.9. This is likely a threading
80
+ # bug in 1.9 that warrants further investigation when we're in a
81
+ # better mood.
82
+ next_wake > 0 ? sleep(next_wake) : Thread.pass
77
83
  else
78
- sleep(timeout)
84
+ sleep(@timeout)
79
85
  end
80
86
 
81
87
  # "active.size" is atomic in MRI 1.8 and 1.9
82
- next if threshold && active.size < threshold
88
+ next if @threshold && @active.size < @threshold
83
89
 
84
90
  now = Time.now
85
- lock.synchronize do
86
- active.delete_if do |thread, time|
87
- time >= now and thread.raise(ExecutionExpired).nil?
91
+ @lock.synchronize do
92
+ @active.delete_if do |thread, time|
93
+ now >= time and thread.raise(ExecutionExpired).nil?
88
94
  end
89
95
  end
90
96
  end while true
@@ -3,6 +3,10 @@
3
3
  module Rainbows::TimedRead
4
4
  G = Rainbows::G # :nodoc:
5
5
 
6
+ def read_expire
7
+ Time.now + G.kato
8
+ end
9
+
6
10
  def kgio_wait_readable
7
11
  IO.select([self], nil, nil, G.kato)
8
12
  end
@@ -14,7 +18,7 @@ module Rainbows::TimedRead
14
18
  case rv = kgio_tryread(16384, buf)
15
19
  when :wait_readable
16
20
  return if expire && expire < Time.now
17
- expire ||= Time.now + G.kato
21
+ expire ||= read_expire
18
22
  kgio_wait_readable
19
23
  else
20
24
  return rv
@@ -0,0 +1,16 @@
1
+ # -*- encoding: binary -*-
2
+ # :enddoc:
3
+ module Rainbows::WorkerYield
4
+
5
+ # Sleep if we're busy (and let other threads run). Another less busy
6
+ # worker process may take it for us if we sleep. This is gross but
7
+ # other options still suck because they require expensive/complicated
8
+ # synchronization primitives for _every_ case, not just this unlikely
9
+ # one. Since this case is (or should be) uncommon, just busy wait
10
+ # when we have to. We don't use Thread.pass because it needlessly
11
+ # spins the CPU during I/O wait, CPU cycles that can be better used by
12
+ # other worker _processes_.
13
+ def worker_yield
14
+ sleep(0.01)
15
+ end
16
+ end
@@ -0,0 +1,19 @@
1
+ # -*- encoding: binary -*-
2
+ # :enddoc:
3
+ # used to wrap a BasicSocket to use with +q+ for all writes
4
+ # this is compatible with IO.select
5
+ class Rainbows::WriterThreadPool::Client < Struct.new(:to_io, :q)
6
+ include Rainbows::SocketProxy
7
+
8
+ def write(buf)
9
+ q << [ to_io, buf ]
10
+ end
11
+
12
+ def close
13
+ q << [ to_io, :close ]
14
+ end
15
+
16
+ def closed?
17
+ false
18
+ end
19
+ end
@@ -1,105 +1,74 @@
1
1
  # -*- encoding: binary -*-
2
2
 
3
- module Rainbows
4
-
5
- # This concurrency model implements a single-threaded app dispatch
6
- # with a separate thread pool for writing responses.
7
- #
8
- # Unlike most \Rainbows! concurrency models, WriterThreadPool is
9
- # designed to run behind nginx just like Unicorn is. This concurrency
10
- # model may be useful for existing Unicorn users looking for more
11
- # output concurrency than socket buffers can provide while still
12
- # maintaining a single-threaded application dispatch (though if the
13
- # response body is dynamically generated, it must be thread safe).
14
- #
15
- # For serving large or streaming responses, using more threads (via
16
- # the +worker_connections+ setting) and setting "proxy_buffering off"
17
- # in nginx is recommended. If your application does not handle
18
- # uploads, then using any HTTP-aware proxy like haproxy is fine.
19
- # Using a non-HTTP-aware proxy will leave you vulnerable to
20
- # slow client denial-of-service attacks.
21
-
22
- module WriterThreadPool
23
- # :stopdoc:
24
- include Base
25
-
26
- # used to wrap a BasicSocket to use with +q+ for all writes
27
- # this is compatible with IO.select
28
- class QueueSocket < Struct.new(:to_io, :q) # :nodoc:
29
- def kgio_addr
30
- to_io.kgio_addr
31
- end
32
-
33
- def kgio_read(size, buf = "")
34
- to_io.kgio_read(size, buf)
35
- end
36
-
37
- def kgio_read!(size, buf = "")
38
- to_io.kgio_read!(size, buf)
3
+ # This concurrency model implements a single-threaded app dispatch
4
+ # with a separate thread pool for writing responses.
5
+ #
6
+ # Unlike most \Rainbows! concurrency models, WriterThreadPool is
7
+ # designed to run behind nginx just like Unicorn is. This concurrency
8
+ # model may be useful for existing Unicorn users looking for more
9
+ # output concurrency than socket buffers can provide while still
10
+ # maintaining a single-threaded application dispatch (though if the
11
+ # response body is dynamically generated, it must be thread safe).
12
+ #
13
+ # For serving large or streaming responses, using more threads (via
14
+ # the +worker_connections+ setting) and setting "proxy_buffering off"
15
+ # in nginx is recommended. If your application does not handle
16
+ # uploads, then using any HTTP-aware proxy like haproxy is fine.
17
+ # Using a non-HTTP-aware proxy will leave you vulnerable to
18
+ # slow client denial-of-service attacks.
19
+ module Rainbows::WriterThreadPool
20
+ # :stopdoc:
21
+ include Rainbows::Base
22
+
23
+ @@nr = 0
24
+ @@q = nil
25
+
26
+ def async_write_body(qclient, body, range)
27
+ if body.respond_to?(:close)
28
+ Rainbows::SyncClose.new(body) do |body|
29
+ qclient.q << [ qclient.to_io, :body, body, range ]
39
30
  end
40
-
41
- def kgio_trywrite(buf)
42
- to_io.kgio_trywrite(buf)
43
- end
44
-
45
- def timed_read(buf)
46
- to_io.timed_read(buf)
47
- end
48
-
49
- def write(buf)
50
- q << [ to_io, buf ]
51
- end
52
-
53
- def close
54
- q << [ to_io, :close ]
55
- end
56
-
57
- def closed?
58
- false
59
- end
60
- end
61
-
62
- @@nr = 0
63
- @@q = nil
64
-
65
- def async_write_body(qclient, body, range)
31
+ else
66
32
  qclient.q << [ qclient.to_io, :body, body, range ]
67
33
  end
34
+ end
68
35
 
69
- def process_client(client) # :nodoc:
70
- @@nr += 1
71
- super(QueueSocket.new(client, @@q[@@nr %= @@q.size]))
72
- end
36
+ def process_client(client) # :nodoc:
37
+ @@nr += 1
38
+ super(Client.new(client, @@q[@@nr %= @@q.size]))
39
+ end
73
40
 
74
- def init_worker_process(worker)
75
- super
76
- self.class.__send__(:alias_method, :sync_write_body, :write_body)
77
- WriterThreadPool.__send__(:alias_method, :write_body, :async_write_body)
78
- end
41
+ def init_worker_process(worker)
42
+ super
43
+ self.class.__send__(:alias_method, :sync_write_body, :write_body)
44
+ Rainbows::WriterThreadPool.__send__(
45
+ :alias_method, :write_body, :async_write_body)
46
+ end
79
47
 
80
- def worker_loop(worker) # :nodoc:
81
- # we have multiple, single-thread queues since we don't want to
82
- # interleave writes from the same client
83
- qp = (1..worker_connections).map do |n|
84
- QueuePool.new(1) do |response|
85
- begin
86
- io, arg1, arg2, arg3 = response
87
- case arg1
88
- when :body then sync_write_body(io, arg2, arg3)
89
- when :close then io.close unless io.closed?
90
- else
91
- io.write(arg1)
92
- end
93
- rescue => err
94
- Error.write(io, err)
48
+ def worker_loop(worker) # :nodoc:
49
+ # we have multiple, single-thread queues since we don't want to
50
+ # interleave writes from the same client
51
+ qp = (1..worker_connections).map do |n|
52
+ Rainbows::QueuePool.new(1) do |response|
53
+ begin
54
+ io, arg1, arg2, arg3 = response
55
+ case arg1
56
+ when :body then sync_write_body(io, arg2, arg3)
57
+ when :close then io.close unless io.closed?
58
+ else
59
+ io.write(arg1)
95
60
  end
61
+ rescue => err
62
+ Rainbows::Error.write(io, err)
96
63
  end
97
64
  end
98
-
99
- @@q = qp.map { |q| q.queue }
100
- super(worker) # accept loop from Unicorn
101
- qp.map { |q| q.quit! }
102
65
  end
103
- # :startdoc:
66
+
67
+ @@q = qp.map { |q| q.queue }
68
+ super(worker) # accept loop from Unicorn
69
+ qp.map { |q| q.quit! }
104
70
  end
71
+ # :startdoc:
105
72
  end
73
+ # :enddoc:
74
+ require 'rainbows/writer_thread_pool/client'
@@ -0,0 +1,69 @@
1
+ # -*- encoding: binary -*-
2
+ # :enddoc:
3
+ # used to wrap a BasicSocket to use with +q+ for all writes
4
+ # this is compatible with IO.select
5
+ class Rainbows::WriterThreadSpawn::Client < Struct.new(:to_io, :q, :thr)
6
+ include Rainbows::Response
7
+ include Rainbows::SocketProxy
8
+ include Rainbows::WorkerYield
9
+
10
+ CUR = {} # :nodoc:
11
+
12
+ def self.quit
13
+ g = Rainbows::G
14
+ CUR.delete_if do |t,q|
15
+ q << nil
16
+ g.tick
17
+ t.alive? ? t.join(0.01) : true
18
+ end until CUR.empty?
19
+ end
20
+
21
+ def queue_writer
22
+ until CUR.size < MAX
23
+ CUR.delete_if { |t,_|
24
+ t.alive? ? t.join(0) : true
25
+ }.size >= MAX and worker_yield
26
+ end
27
+
28
+ q = Queue.new
29
+ self.thr = Thread.new(to_io, q) do |io, q|
30
+ while response = q.shift
31
+ begin
32
+ arg1, arg2, arg3 = response
33
+ case arg1
34
+ when :body then write_body(io, arg2, arg3)
35
+ when :close
36
+ io.close unless io.closed?
37
+ break
38
+ else
39
+ io.write(arg1)
40
+ end
41
+ rescue => e
42
+ Rainbows::Error.write(io, e)
43
+ end
44
+ end
45
+ CUR.delete(Thread.current)
46
+ end
47
+ CUR[thr] = q
48
+ end
49
+
50
+ def write(buf)
51
+ (self.q ||= queue_writer) << buf
52
+ end
53
+
54
+ def queue_body(body, range)
55
+ (self.q ||= queue_writer) << [ :body, body, range ]
56
+ end
57
+
58
+ def close
59
+ if q
60
+ q << :close
61
+ else
62
+ to_io.close
63
+ end
64
+ end
65
+
66
+ def closed?
67
+ false
68
+ end
69
+ end
@@ -1,125 +1,45 @@
1
1
  # -*- encoding: binary -*-
2
2
  require 'thread'
3
- module Rainbows
4
-
5
- # This concurrency model implements a single-threaded app dispatch and
6
- # spawns a new thread for writing responses. This concurrency model
7
- # should be ideal for apps that serve large responses or stream
8
- # responses slowly.
9
- #
10
- # Unlike most \Rainbows! concurrency models, WriterThreadSpawn is
11
- # designed to run behind nginx just like Unicorn is. This concurrency
12
- # model may be useful for existing Unicorn users looking for more
13
- # output concurrency than socket buffers can provide while still
14
- # maintaining a single-threaded application dispatch (though if the
15
- # response body is generated on-the-fly, it must be thread safe).
16
- #
17
- # For serving large or streaming responses, setting
18
- # "proxy_buffering off" in nginx is recommended. If your application
19
- # does not handle uploads, then using any HTTP-aware proxy like
20
- # haproxy is fine. Using a non-HTTP-aware proxy will leave you
21
- # vulnerable to slow client denial-of-service attacks.
22
-
23
- module WriterThreadSpawn
24
- # :stopdoc:
25
- include Base
26
-
27
- CUR = {} # :nodoc:
28
-
29
- # used to wrap a BasicSocket to use with +q+ for all writes
30
- # this is compatible with IO.select
31
- class MySocket < Struct.new(:to_io, :q, :thr) # :nodoc: all
32
- include Rainbows::Response
33
-
34
- def kgio_addr
35
- to_io.kgio_addr
36
- end
37
-
38
- def kgio_read(size, buf = "")
39
- to_io.kgio_read(size, buf)
40
- end
41
-
42
- def kgio_read!(size, buf = "")
43
- to_io.kgio_read!(size, buf)
44
- end
45
-
46
- def kgio_trywrite(buf)
47
- to_io.kgio_trywrite(buf)
48
- end
49
-
50
- def timed_read(buf)
51
- to_io.timed_read(buf)
52
- end
53
-
54
- def queue_writer
55
- # not using Thread.pass here because that spins the CPU during
56
- # I/O wait and will eat cycles from other worker processes.
57
- until CUR.size < MAX
58
- CUR.delete_if { |t,_|
59
- t.alive? ? t.join(0) : true
60
- }.size >= MAX and sleep(0.01)
61
- end
62
-
63
- q = Queue.new
64
- self.thr = Thread.new(to_io, q) do |io, q|
65
- while response = q.shift
66
- begin
67
- arg1, arg2, arg3 = response
68
- case arg1
69
- when :body then write_body(io, arg2, arg3)
70
- when :close
71
- io.close unless io.closed?
72
- break
73
- else
74
- io.write(arg1)
75
- end
76
- rescue => e
77
- Error.write(io, e)
78
- end
79
- end
80
- CUR.delete(Thread.current)
81
- end
82
- CUR[thr] = q
83
- end
84
-
85
- def write(buf)
86
- (self.q ||= queue_writer) << buf
87
- end
88
-
89
- def queue_body(body, range)
90
- (self.q ||= queue_writer) << [ :body, body, range ]
91
- end
92
-
93
- def close
94
- if q
95
- q << :close
96
- else
97
- to_io.close
98
- end
99
- end
100
-
101
- def closed?
102
- false
103
- end
104
- end
105
-
106
- def write_body(my_sock, body, range) # :nodoc:
3
+ # This concurrency model implements a single-threaded app dispatch and
4
+ # spawns a new thread for writing responses. This concurrency model
5
+ # should be ideal for apps that serve large responses or stream
6
+ # responses slowly.
7
+ #
8
+ # Unlike most \Rainbows! concurrency models, WriterThreadSpawn is
9
+ # designed to run behind nginx just like Unicorn is. This concurrency
10
+ # model may be useful for existing Unicorn users looking for more
11
+ # output concurrency than socket buffers can provide while still
12
+ # maintaining a single-threaded application dispatch (though if the
13
+ # response body is generated on-the-fly, it must be thread safe).
14
+ #
15
+ # For serving large or streaming responses, setting
16
+ # "proxy_buffering off" in nginx is recommended. If your application
17
+ # does not handle uploads, then using any HTTP-aware proxy like
18
+ # haproxy is fine. Using a non-HTTP-aware proxy will leave you
19
+ # vulnerable to slow client denial-of-service attacks.
20
+
21
+ module Rainbows::WriterThreadSpawn
22
+ # :stopdoc:
23
+ include Rainbows::Base
24
+
25
+ def write_body(my_sock, body, range) # :nodoc:
26
+ if body.respond_to?(:close)
27
+ Rainbows::SyncClose.new(body) { |body| my_sock.queue_body(body, range) }
28
+ else
107
29
  my_sock.queue_body(body, range)
108
30
  end
31
+ end
109
32
 
110
- def process_client(client) # :nodoc:
111
- super(MySocket[client])
112
- end
33
+ def process_client(client) # :nodoc:
34
+ super(Client.new(client))
35
+ end
113
36
 
114
- def worker_loop(worker) # :nodoc:
115
- MySocket.const_set(:MAX, worker_connections)
116
- super(worker) # accept loop from Unicorn
117
- CUR.delete_if do |t,q|
118
- q << nil
119
- G.tick
120
- t.alive? ? t.join(0.01) : true
121
- end until CUR.empty?
122
- end
123
- # :startdoc:
37
+ def worker_loop(worker) # :nodoc:
38
+ Client.const_set(:MAX, worker_connections)
39
+ super # accept loop from Unicorn
40
+ Client.quit
124
41
  end
42
+ # :startdoc:
125
43
  end
44
+ # :enddoc:
45
+ require 'rainbows/writer_thread_spawn/client'
data/lib/rainbows.rb CHANGED
@@ -46,20 +46,22 @@ module Rainbows
46
46
  autoload :DevFdResponse, 'rainbows/dev_fd_response'
47
47
  autoload :MaxBody, 'rainbows/max_body'
48
48
  autoload :QueuePool, 'rainbows/queue_pool'
49
+ autoload :EvCore, 'rainbows/ev_core'
50
+ autoload :SocketProxy, 'rainbows/socket_proxy'
49
51
 
50
52
  class << self
51
53
 
52
54
  # Sleeps the current application dispatch. This will pick the
53
55
  # optimal method to sleep depending on the concurrency model chosen
54
56
  # (which may still suck and block the entire process). Using this
55
- # with the basic :Rev or :EventMachine models is not recommended.
57
+ # with the basic :Coolio or :EventMachine models is not recommended.
56
58
  # This should be used within your Rack application.
57
59
  def sleep(nr)
58
60
  case G.server.use
59
61
  when :FiberPool, :FiberSpawn
60
62
  Rainbows::Fiber.sleep(nr)
61
- when :RevFiberSpawn
62
- Rainbows::Fiber::Rev::Sleeper.new(nr)
63
+ when :RevFiberSpawn, :CoolioFiberSpawn
64
+ Rainbows::Fiber::Coolio::Sleeper.new(nr)
63
65
  when :Revactor
64
66
  Actor.sleep(nr)
65
67
  else
@@ -96,12 +98,16 @@ module Rainbows
96
98
  :Rev => 50,
97
99
  :RevThreadSpawn => 50,
98
100
  :RevThreadPool => 50,
101
+ :RevFiberSpawn => 50,
102
+ :Coolio => 50,
103
+ :CoolioThreadSpawn => 50,
104
+ :CoolioThreadPool => 50,
105
+ :CoolioFiberSpawn => 50,
99
106
  :EventMachine => 50,
100
107
  :FiberSpawn => 50,
101
108
  :FiberPool => 50,
102
109
  :ActorSpawn => 50,
103
110
  :NeverBlock => 50,
104
- :RevFiberSpawn => 50,
105
111
  }.each do |model, _|
106
112
  u = model.to_s.gsub(/([a-z0-9])([A-Z0-9])/) { "#{$1}_#{$2.downcase!}" }
107
113
  autoload model, "rainbows/#{u.downcase!}"
@@ -111,6 +117,8 @@ module Rainbows
111
117
  autoload :StreamFile, 'rainbows/stream_file'
112
118
  autoload :HttpResponse, 'rainbows/http_response' # deprecated
113
119
  autoload :ThreadTimeout, 'rainbows/thread_timeout'
120
+ autoload :WorkerYield, 'rainbows/worker_yield'
121
+ autoload :SyncClose, 'rainbows/sync_close'
114
122
  end
115
123
 
116
124
  require 'rainbows/error'
data/rainbows.gemspec CHANGED
@@ -9,32 +9,24 @@ manifest = File.readlines('.manifest').map! { |x| x.chomp! }
9
9
  test_files = manifest.grep(%r{\Atest/unit/test_.*\.rb\z}).map do |f|
10
10
  File.readlines(f).grep(/\bfork\b/).empty? ? f : nil
11
11
  end.compact
12
+ require 'wrongdoc'
13
+ extend Wrongdoc::Gemspec
14
+ name, summary, title = readme_metadata
12
15
 
13
16
  Gem::Specification.new do |s|
14
17
  s.name = %q{rainbows}
15
- s.version = ENV["VERSION"]
18
+ s.version = ENV["VERSION"].dup
16
19
 
17
- s.authors = ["Rainbows! hackers"]
20
+ s.authors = ["#{name} hackers"]
18
21
  s.date = Time.now.utc.strftime('%Y-%m-%d')
19
- s.description = File.read("README").split(/\n\n/)[1]
22
+ s.description = readme_description
20
23
  s.email = %q{rainbows-talk@rubyforge.org}
21
24
  s.executables = %w(rainbows)
22
-
23
- s.extra_rdoc_files = File.readlines('.document').map! do |x|
24
- x.chomp!
25
- if File.directory?(x)
26
- manifest.grep(%r{\A#{x}/})
27
- elsif File.file?(x)
28
- x
29
- else
30
- nil
31
- end
32
- end.flatten.compact
33
-
25
+ s.extra_rdoc_files = extra_rdoc_files(manifest)
34
26
  s.files = manifest
35
- s.homepage = %q{http://rainbows.rubyforge.org/}
36
- s.summary = %q{Unicorn for sleepy apps and slow clients}
37
- s.rdoc_options = [ "-t", "Rainbows! #{s.summary}" ]
27
+ s.homepage = Wrongdoc.config[:rdoc_url]
28
+ s.summary = summary
29
+ s.rdoc_options = rdoc_options
38
30
  s.require_paths = %w(lib)
39
31
  s.rubyforge_project = %q{rainbows}
40
32
 
@@ -44,8 +36,9 @@ Gem::Specification.new do |s|
44
36
  s.add_dependency(%q<rack>, ['~> 1.1'])
45
37
 
46
38
  # we need Unicorn for the HTTP parser and process management
47
- s.add_dependency(%q<unicorn>, ["~> 3.0.0"])
39
+ s.add_dependency(%q<unicorn>, ["~> 3.2"])
48
40
  s.add_development_dependency(%q<isolate>, "~> 3.0.0")
41
+ s.add_development_dependency(%q<wrongdoc>, "~> 1.0.1")
49
42
 
50
43
  # optional runtime dependencies depending on configuration
51
44
  # see t/test_isolate.rb for the exact versions we've tested with
@@ -56,6 +49,9 @@ Gem::Specification.new do |s|
56
49
  # Revactor depends on Rev, too, 0.3.0 got the ability to attach IOs
57
50
  # s.add_dependency(%q<rev>, [">= 0.3.2"])
58
51
  #
52
+ # Cool.io is the new Rev, but it doesn't work with Revactor
53
+ # s.add_dependency(%q<cool.io>, [">= 1.0"])
54
+ #
59
55
  # Rev depends on IOBuffer, which got faster in 0.1.3
60
56
  # s.add_dependency(%q<iobuffer>, [">= 0.1.3"])
61
57
  #