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
@@ -1,76 +1,22 @@
1
1
  # -*- encoding: binary -*-
2
- require 'rainbows/rev/thread'
3
-
4
- module Rainbows
5
-
6
- # A combination of the Rev and ThreadPool models. This allows Ruby
7
- # Thread-based concurrency for application processing. It DOES NOT
8
- # expose a streamable "rack.input" for upload processing within the
9
- # app. DevFdResponse should be used with this class to proxy
10
- # asynchronous responses. All network I/O between the client and
11
- # server are handled by the main thread and outside of the core
12
- # application dispatch.
13
- #
14
- # Unlike ThreadPool, Rev makes this model highly suitable for
15
- # slow clients and applications with medium-to-slow response times
16
- # (I/O bound), but less suitable for sleepy applications.
17
- #
18
- # This concurrency model is designed for Ruby 1.9, and Ruby 1.8
19
- # users are NOT advised to use this due to high CPU usage.
20
-
21
- module RevThreadPool
22
-
23
- # :stopdoc:
24
- DEFAULTS = {
25
- :pool_size => 20, # same default size as ThreadPool (w/o Rev)
26
- }
27
- #:startdoc:
28
-
29
- def self.setup # :nodoc:
30
- DEFAULTS.each { |k,v| O[k] ||= v }
31
- Integer === O[:pool_size] && O[:pool_size] > 0 or
32
- raise ArgumentError, "pool_size must a be an Integer > 0"
33
- end
34
-
35
- class PoolWatcher < ::Rev::TimerWatcher # :nodoc: all
36
- def initialize(threads)
37
- @threads = threads
38
- super(G.server.timeout, true)
39
- end
40
-
41
- def on_timer
42
- @threads.each { |t| t.join(0) and G.quit! }
43
- end
44
- end
45
-
46
- class Client < Rainbows::Rev::ThreadClient # :nodoc:
47
- def app_dispatch
48
- QUEUE << self
49
- end
50
- end
51
-
52
- include Rainbows::Rev::Core
53
-
54
- def init_worker_threads(master, queue) # :nodoc:
55
- O[:pool_size].times.map do
56
- Thread.new do
57
- begin
58
- client = queue.pop
59
- master << [ client, client.app_response ]
60
- rescue => e
61
- Error.listen_loop(e)
62
- end while true
63
- end
64
- end
65
- end
66
-
67
- def init_worker_process(worker) # :nodoc:
68
- super
69
- master = Rev::Master.new(Queue.new).attach(::Rev::Loop.default)
70
- queue = Client.const_set(:QUEUE, Queue.new)
71
- threads = init_worker_threads(master, queue)
72
- PoolWatcher.new(threads).attach(::Rev::Loop.default)
73
- logger.info "RevThreadPool pool_size=#{O[:pool_size]}"
74
- end
75
- end
76
- end
2
+ # :stopdoc:
3
+ Rainbows.const_set(:RevThreadPool, Rainbows::CoolioThreadPool)
4
+ # :startdoc:
5
+
6
+ # CoolioThreadPool is the new version of this, use that instead.
7
+ #
8
+ # A combination of the Rev and ThreadPool models. This allows Ruby
9
+ # Thread-based concurrency for application processing. It DOES NOT
10
+ # expose a streamable "rack.input" for upload processing within the
11
+ # app. DevFdResponse should be used with this class to proxy
12
+ # asynchronous responses. All network I/O between the client and
13
+ # server are handled by the main thread and outside of the core
14
+ # application dispatch.
15
+ #
16
+ # Unlike ThreadPool, Rev makes this model highly suitable for
17
+ # slow clients and applications with medium-to-slow response times
18
+ # (I/O bound), but less suitable for sleepy applications.
19
+ #
20
+ # This concurrency model is designed for Ruby 1.9, and Ruby 1.8
21
+ # users are NOT advised to use this due to high CPU usage.
22
+ module Rainbows::RevThreadPool; end
@@ -1,38 +1,20 @@
1
1
  # -*- encoding: binary -*-
2
- require 'rainbows/rev/thread'
2
+ Rainbows.const_set(:RevThreadSpawn, Rainbows::CoolioThreadSpawn)
3
3
 
4
- module Rainbows
5
-
6
- # A combination of the Rev and ThreadSpawn models. This allows Ruby
7
- # Thread-based concurrency for application processing. It DOES NOT
8
- # expose a streamable "rack.input" for upload processing within the
9
- # app. DevFdResponse should be used with this class to proxy
10
- # asynchronous responses. All network I/O between the client and
11
- # server are handled by the main thread and outside of the core
12
- # application dispatch.
13
- #
14
- # Unlike ThreadSpawn, Rev makes this model highly suitable for
15
- # slow clients and applications with medium-to-slow response times
16
- # (I/O bound), but less suitable for sleepy applications.
17
- #
18
- # Ruby 1.8 users are strongly advised to use Rev >= 0.3.2 to get
19
- # usable performance.
20
-
21
- module RevThreadSpawn
22
-
23
- class Client < Rainbows::Rev::ThreadClient # :nodoc: all
24
- def app_dispatch
25
- Thread.new(self) { |client| MASTER << [ client, app_response ] }
26
- end
27
- end
28
-
29
- include Rainbows::Rev::Core
30
-
31
- def init_worker_process(worker) # :nodoc:
32
- super
33
- master = Rev::Master.new(Queue.new).attach(::Rev::Loop.default)
34
- Client.const_set(:MASTER, master)
35
- end
36
-
37
- end
38
- end
4
+ # CoolioThreadPool is the new version of this, use that instead.
5
+ #
6
+ # A combination of the Rev and ThreadSpawn models. This allows Ruby
7
+ # Thread-based concurrency for application processing. It DOES NOT
8
+ # expose a streamable "rack.input" for upload processing within the
9
+ # app. DevFdResponse should be used with this class to proxy
10
+ # asynchronous responses. All network I/O between the client and
11
+ # server are handled by the main thread and outside of the core
12
+ # application dispatch.
13
+ #
14
+ # Unlike ThreadSpawn, Rev makes this model highly suitable for
15
+ # slow clients and applications with medium-to-slow response times
16
+ # (I/O bound), but less suitable for sleepy applications.
17
+ #
18
+ # This concurrency model is designed for Ruby 1.9, and Ruby 1.8
19
+ # users are NOT advised to use this due to high CPU usage.
20
+ module Rainbows::RevThreadSpawn; end
@@ -8,8 +8,9 @@ module Rainbows::Revactor::Body
8
8
 
9
9
  if IO.method_defined?(:sendfile_nonblock)
10
10
  def write_body_file(client, body, range)
11
+ body = body_to_io(body)
11
12
  sock = client.instance_variable_get(:@_io)
12
- pfx = ::Revactor::TCP::Socket === client ? :tcp : :unix
13
+ pfx = Revactor::TCP::Socket === client ? :tcp : :unix
13
14
  write_complete = T[:"#{pfx}_write_complete", client]
14
15
  closed = T[:"#{pfx}_closed", client]
15
16
  offset, count = range ? range : [ 0, body.stat.size ]
@@ -29,6 +30,8 @@ module Rainbows::Revactor::Body
29
30
  rescue EOFError
30
31
  break
31
32
  end while (count -= n) > 0
33
+ ensure
34
+ close_if_private(body)
32
35
  end
33
36
  else
34
37
  ALIASES[:write_body] = :write_body_each
@@ -0,0 +1,44 @@
1
+ # -*- encoding: binary -*-
2
+ # :enddoc:
3
+ #
4
+ # Revactor Sockets do not implement readpartial, so we emulate just
5
+ # enough to avoid mucking with TeeInput internals. Fortunately
6
+ # this code is not heavily used so we can usually avoid the overhead
7
+ # of adding a userspace buffer.
8
+ class Rainbows::Revactor::TeeSocket
9
+ def initialize(socket)
10
+ # IO::Buffer is used internally by Rev which Revactor is based on
11
+ # so we'll always have it available
12
+ @socket, @rbuf = socket, IO::Buffer.new
13
+ end
14
+
15
+ def leftover
16
+ @rbuf.read
17
+ end
18
+
19
+ # Revactor socket reads always return an unspecified amount,
20
+ # sometimes too much
21
+ def kgio_read(length, dst = "")
22
+ return dst.replace("") if length == 0
23
+
24
+ # always check and return from the userspace buffer first
25
+ @rbuf.size > 0 and return dst.replace(@rbuf.read(length))
26
+
27
+ # read off the socket since there was nothing in rbuf
28
+ tmp = @socket.read
29
+
30
+ # we didn't read too much, good, just return it straight back
31
+ # to avoid needlessly wasting memory bandwidth
32
+ tmp.size <= length and return dst.replace(tmp)
33
+
34
+ # ugh, read returned too much
35
+ @rbuf << tmp[length, tmp.size]
36
+ dst.replace(tmp[0, length])
37
+ rescue EOFError
38
+ end
39
+
40
+ # just proxy any remaining methods TeeInput may use
41
+ def close
42
+ @socket.close
43
+ end
44
+ end
@@ -24,10 +24,11 @@ module Rainbows::Revactor
24
24
  RD_ARGS = {}
25
25
 
26
26
  autoload :Proxy, 'rainbows/revactor/proxy'
27
+ autoload :TeeSocket, 'rainbows/revactor/tee_socket'
27
28
 
28
29
  include Rainbows::Base
29
30
  LOCALHOST = Kgio::LOCALHOST
30
- TCP = ::Revactor::TCP::Socket
31
+ TCP = Revactor::TCP::Socket
31
32
 
32
33
  # once a client is accepted, it is processed in its entirety here
33
34
  # in 3 easy steps: read request, call app, write app response
@@ -46,13 +47,14 @@ module Rainbows::Revactor
46
47
  alive = false
47
48
 
48
49
  begin
50
+ ts = nil
49
51
  until env = hp.parse
50
52
  buf << client.read(*rd_args)
51
53
  end
52
54
 
53
55
  env[CLIENT_IO] = client
54
56
  env[RACK_INPUT] = 0 == hp.content_length ?
55
- NULL_IO : Unicorn::TeeInput.new(TeeSocket.new(client), hp)
57
+ NULL_IO : IC.new(ts = TeeSocket.new(client), hp)
56
58
  env[REMOTE_ADDR] = remote_addr
57
59
  status, headers, body = app.call(env.update(RACK_DEFAULTS))
58
60
 
@@ -68,10 +70,11 @@ module Rainbows::Revactor
68
70
  alive = hp.next? && G.alive && G.kato > 0
69
71
  headers[CONNECTION] = alive ? KEEP_ALIVE : CLOSE
70
72
  client.write(response_header(status, headers))
73
+ alive && ts and buf << ts.leftover
71
74
  end
72
75
  write_body(client, body, range)
73
76
  end while alive
74
- rescue ::Revactor::TCP::ReadError
77
+ rescue Revactor::TCP::ReadError
75
78
  rescue => e
76
79
  Rainbows::Error.write(io, e)
77
80
  ensure
@@ -85,6 +88,7 @@ module Rainbows::Revactor
85
88
  init_worker_process(worker)
86
89
  require 'rainbows/revactor/body'
87
90
  self.class.__send__(:include, Rainbows::Revactor::Body)
91
+ self.class.const_set(:IC, Unicorn::HttpRequest.input_class)
88
92
  RD_ARGS[:timeout] = G.kato if G.kato > 0
89
93
  nr = 0
90
94
  limit = worker_connections
@@ -133,54 +137,15 @@ module Rainbows::Revactor
133
137
  LISTENERS.map do |s|
134
138
  case s
135
139
  when TCPServer
136
- l = ::Revactor::TCP.listen(s, nil)
137
- [ l, T[:tcp_closed, ::Revactor::TCP::Socket],
138
- T[:tcp_connection, l, ::Revactor::TCP::Socket] ]
140
+ l = Revactor::TCP.listen(s, nil)
141
+ [ l, T[:tcp_closed, Revactor::TCP::Socket],
142
+ T[:tcp_connection, l, Revactor::TCP::Socket] ]
139
143
  when UNIXServer
140
- l = ::Revactor::UNIX.listen(s)
141
- [ l, T[:unix_closed, ::Revactor::UNIX::Socket ],
142
- T[:unix_connection, l, ::Revactor::UNIX::Socket] ]
144
+ l = Revactor::UNIX.listen(s)
145
+ [ l, T[:unix_closed, Revactor::UNIX::Socket ],
146
+ T[:unix_connection, l, Revactor::UNIX::Socket] ]
143
147
  end
144
148
  end
145
149
  end
146
-
147
- # Revactor Sockets do not implement readpartial, so we emulate just
148
- # enough to avoid mucking with TeeInput internals. Fortunately
149
- # this code is not heavily used so we can usually avoid the overhead
150
- # of adding a userspace buffer.
151
- class TeeSocket
152
- def initialize(socket)
153
- # IO::Buffer is used internally by Rev which Revactor is based on
154
- # so we'll always have it available
155
- @socket, @rbuf = socket, IO::Buffer.new
156
- end
157
-
158
- # Revactor socket reads always return an unspecified amount,
159
- # sometimes too much
160
- def kgio_read(length, dst = "")
161
- return dst.replace("") if length == 0
162
-
163
- # always check and return from the userspace buffer first
164
- @rbuf.size > 0 and return dst.replace(@rbuf.read(length))
165
-
166
- # read off the socket since there was nothing in rbuf
167
- tmp = @socket.read
168
-
169
- # we didn't read too much, good, just return it straight back
170
- # to avoid needlessly wasting memory bandwidth
171
- tmp.size <= length and return dst.replace(tmp)
172
-
173
- # ugh, read returned too much
174
- @rbuf << tmp[length, tmp.size]
175
- dst.replace(tmp[0, length])
176
- rescue EOFError
177
- end
178
-
179
- # just proxy any remaining methods TeeInput may use
180
- def close
181
- @socket.close
182
- end
183
- end
184
-
185
150
  # :startdoc:
186
151
  end
@@ -0,0 +1,24 @@
1
+ # -*- encoding: binary -*-
2
+ # :enddoc:
3
+ #
4
+ module Rainbows::SocketProxy
5
+ def kgio_addr
6
+ to_io.kgio_addr
7
+ end
8
+
9
+ def kgio_read(size, buf = "")
10
+ to_io.kgio_read(size, buf)
11
+ end
12
+
13
+ def kgio_read!(size, buf = "")
14
+ to_io.kgio_read!(size, buf)
15
+ end
16
+
17
+ def kgio_trywrite(buf)
18
+ to_io.kgio_trywrite(buf)
19
+ end
20
+
21
+ def timed_read(buf)
22
+ to_io.timed_read(buf)
23
+ end
24
+ end
@@ -0,0 +1,37 @@
1
+ # -*- encoding: binary -*-
2
+ # :enddoc:
3
+ require 'thread'
4
+ class Rainbows::SyncClose
5
+ def initialize(body)
6
+ @body = body
7
+ @mutex = Mutex.new
8
+ @cv = ConditionVariable.new
9
+ @mutex.synchronize do
10
+ yield self
11
+ @cv.wait(@mutex)
12
+ end
13
+ end
14
+
15
+ def respond_to?(m)
16
+ @body.respond_to?(m)
17
+ end
18
+
19
+ def to_path
20
+ @body.to_path
21
+ end
22
+
23
+ def each(&block)
24
+ @body.each(&block)
25
+ end
26
+
27
+ def to_io
28
+ @body.to_io
29
+ end
30
+
31
+ # called by the writer thread to wake up the original thread (in #initialize)
32
+ def close
33
+ @body.close
34
+ ensure
35
+ @mutex.synchronize { @cv.signal }
36
+ end
37
+ end
@@ -1,82 +1,78 @@
1
1
  # -*- encoding: binary -*-
2
2
 
3
- module Rainbows
3
+ # Implements a worker thread pool model. This is suited for platforms
4
+ # like Ruby 1.9, where the cost of dynamically spawning a new thread
5
+ # for every new client connection is higher than with the ThreadSpawn
6
+ # model.
7
+ #
8
+ # This model should provide a high level of compatibility with all
9
+ # Ruby implementations, and most libraries and applications.
10
+ # Applications running under this model should be thread-safe
11
+ # but not necessarily reentrant.
12
+ #
13
+ # Applications using this model are required to be thread-safe.
14
+ # Threads are never spawned dynamically under this model. If you're
15
+ # connecting to external services and need to perform DNS lookups,
16
+ # consider using the "resolv-replace" library which replaces parts of
17
+ # the core Socket package with concurrent DNS lookup capabilities.
18
+ #
19
+ # This model probably less suited for many slow clients than the
20
+ # others and thus a lower +worker_connections+ setting is recommended.
4
21
 
5
- # Implements a worker thread pool model. This is suited for platforms
6
- # like Ruby 1.9, where the cost of dynamically spawning a new thread
7
- # for every new client connection is higher than with the ThreadSpawn
8
- # model.
9
- #
10
- # This model should provide a high level of compatibility with all
11
- # Ruby implementations, and most libraries and applications.
12
- # Applications running under this model should be thread-safe
13
- # but not necessarily reentrant.
14
- #
15
- # Applications using this model are required to be thread-safe.
16
- # Threads are never spawned dynamically under this model. If you're
17
- # connecting to external services and need to perform DNS lookups,
18
- # consider using the "resolv-replace" library which replaces parts of
19
- # the core Socket package with concurrent DNS lookup capabilities.
20
- #
21
- # This model probably less suited for many slow clients than the
22
- # others and thus a lower +worker_connections+ setting is recommended.
22
+ module Rainbows::ThreadPool
23
+ include Rainbows::Base
23
24
 
24
- module ThreadPool
25
- include Base
26
-
27
- def worker_loop(worker) # :nodoc:
28
- init_worker_process(worker)
29
- pool = (1..worker_connections).map do
30
- Thread.new { LISTENERS.size == 1 ? sync_worker : async_worker }
31
- end
32
-
33
- while G.alive
34
- # if any worker dies, something is serious wrong, bail
35
- pool.each do |thr|
36
- G.tick or break
37
- thr.join(1) and G.quit!
38
- end
39
- end
40
- join_threads(pool)
25
+ def worker_loop(worker) # :nodoc:
26
+ init_worker_process(worker)
27
+ pool = (1..worker_connections).map do
28
+ Thread.new { LISTENERS.size == 1 ? sync_worker : async_worker }
41
29
  end
42
30
 
43
- def sync_worker # :nodoc:
44
- s = LISTENERS[0]
45
- begin
46
- c = s.kgio_accept and process_client(c)
47
- rescue => e
48
- Error.listen_loop(e)
49
- end while G.alive
31
+ while G.alive
32
+ # if any worker dies, something is serious wrong, bail
33
+ pool.each do |thr|
34
+ G.tick or break
35
+ thr.join(1) and G.quit!
36
+ end
50
37
  end
38
+ join_threads(pool)
39
+ end
51
40
 
52
- def async_worker # :nodoc:
53
- begin
54
- # TODO: check if select() or accept() is a problem on large
55
- # SMP systems under Ruby 1.9. Hundreds of native threads
56
- # all working off the same socket could be a thundering herd
57
- # problem. On the other hand, a thundering herd may not
58
- # even incur as much overhead as an extra Mutex#synchronize
59
- ret = IO.select(LISTENERS, nil, nil, 1) and ret[0].each do |s|
60
- s = s.kgio_tryaccept and process_client(s)
61
- end
62
- rescue Errno::EINTR
63
- rescue => e
64
- Error.listen_loop(e)
65
- end while G.alive
66
- end
41
+ def sync_worker # :nodoc:
42
+ s = LISTENERS[0]
43
+ begin
44
+ c = s.kgio_accept and process_client(c)
45
+ rescue => e
46
+ Rainbows::Error.listen_loop(e)
47
+ end while G.alive
48
+ end
67
49
 
68
- def join_threads(threads) # :nodoc:
69
- G.quit!
70
- threads.delete_if do |thr|
71
- G.tick
72
- begin
73
- thr.run
74
- thr.join(0.01)
75
- rescue
76
- true
77
- end
78
- end until threads.empty?
79
- end
50
+ def async_worker # :nodoc:
51
+ begin
52
+ # TODO: check if select() or accept() is a problem on large
53
+ # SMP systems under Ruby 1.9. Hundreds of native threads
54
+ # all working off the same socket could be a thundering herd
55
+ # problem. On the other hand, a thundering herd may not
56
+ # even incur as much overhead as an extra Mutex#synchronize
57
+ ret = select(LISTENERS) and ret[0].each do |s|
58
+ s = s.kgio_tryaccept and process_client(s)
59
+ end
60
+ rescue Errno::EINTR
61
+ rescue => e
62
+ Rainbows::Error.listen_loop(e)
63
+ end while G.alive
64
+ end
80
65
 
66
+ def join_threads(threads) # :nodoc:
67
+ G.quit!
68
+ threads.delete_if do |thr|
69
+ G.tick
70
+ begin
71
+ thr.run
72
+ thr.join(0.01)
73
+ rescue
74
+ true
75
+ end
76
+ end until threads.empty?
81
77
  end
82
78
  end
@@ -1,62 +1,52 @@
1
1
  # -*- encoding: binary -*-
2
2
  require 'thread'
3
- module Rainbows
4
3
 
5
- # Spawns a new thread for every client connection we accept(). This
6
- # model is recommended for platforms like Ruby 1.8 where spawning new
7
- # threads is inexpensive.
8
- #
9
- # This model should provide a high level of compatibility with all
10
- # Ruby implementations, and most libraries and applications.
11
- # Applications running under this model should be thread-safe
12
- # but not necessarily reentrant.
13
- #
14
- # If you're connecting to external services and need to perform DNS
15
- # lookups, consider using the "resolv-replace" library which replaces
16
- # parts of the core Socket package with concurrent DNS lookup
17
- # capabilities
4
+ # Spawns a new thread for every client connection we accept(). This
5
+ # model is recommended for platforms like Ruby 1.8 where spawning new
6
+ # threads is inexpensive.
7
+ #
8
+ # This model should provide a high level of compatibility with all
9
+ # Ruby implementations, and most libraries and applications.
10
+ # Applications running under this model should be thread-safe
11
+ # but not necessarily reentrant.
12
+ #
13
+ # If you're connecting to external services and need to perform DNS
14
+ # lookups, consider using the "resolv-replace" library which replaces
15
+ # parts of the core Socket package with concurrent DNS lookup
16
+ # capabilities
18
17
 
19
- module ThreadSpawn
20
- include Base
18
+ module Rainbows::ThreadSpawn
19
+ include Rainbows::Base
20
+ include Rainbows::WorkerYield
21
21
 
22
- def accept_loop(klass) #:nodoc:
23
- lock = Mutex.new
24
- limit = worker_connections
25
- LISTENERS.each do |l|
26
- klass.new(l) do |l|
27
- begin
28
- if lock.synchronize { G.cur >= limit }
29
- # Sleep if we're busy, another less busy worker process may
30
- # take it for us if we sleep. This is gross but other options
31
- # still suck because they require expensive/complicated
32
- # synchronization primitives for _every_ case, not just this
33
- # unlikely one. Since this case is (or should be) uncommon,
34
- # just busy wait when we have to.
35
- # We don't use Thread.pass because it needlessly spins the
36
- # CPU during I/O wait, CPU cycles that can be better used
37
- # by other worker _processes_.
38
- sleep(0.01)
39
- elsif c = l.kgio_accept
40
- klass.new(c) do |c|
41
- begin
42
- lock.synchronize { G.cur += 1 }
43
- process_client(c)
44
- ensure
45
- lock.synchronize { G.cur -= 1 }
46
- end
22
+ def accept_loop(klass) #:nodoc:
23
+ lock = Mutex.new
24
+ limit = worker_connections
25
+ LISTENERS.each do |l|
26
+ klass.new(l) do |l|
27
+ begin
28
+ if lock.synchronize { G.cur >= limit }
29
+ worker_yield
30
+ elsif c = l.kgio_accept
31
+ klass.new(c) do |c|
32
+ begin
33
+ lock.synchronize { G.cur += 1 }
34
+ process_client(c)
35
+ ensure
36
+ lock.synchronize { G.cur -= 1 }
47
37
  end
48
38
  end
49
- rescue => e
50
- Error.listen_loop(e)
51
- end while G.alive
52
- end
39
+ end
40
+ rescue => e
41
+ Rainbows::Error.listen_loop(e)
42
+ end while G.alive
53
43
  end
54
- sleep 1 while G.tick || lock.synchronize { G.cur > 0 }
55
44
  end
45
+ sleep 1 while G.tick || lock.synchronize { G.cur > 0 }
46
+ end
56
47
 
57
- def worker_loop(worker) #:nodoc:
58
- init_worker_process(worker)
59
- accept_loop(Thread)
60
- end
48
+ def worker_loop(worker) #:nodoc:
49
+ init_worker_process(worker)
50
+ accept_loop(Thread)
61
51
  end
62
52
  end