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