rainbows 0.7.0 → 0.8.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -85,13 +85,17 @@
85
85
  %td.r19 Yes
86
86
  %td.rbx No
87
87
  %td.slow Yes
88
+ %tr.comp_row
89
+ %td.mod RevThreadPool
90
+ %td.tee No
91
+ %td.r18 Slow*
92
+ %td.r19 Yes
93
+ %td.rbx No
94
+ %td.slow Yes
88
95
  %ul
89
96
  %li
90
- RevThreadSpawn + 1.8 performance is being improved, follow
91
- the
92
- %a(href="http://rubyforge.org/mailman/listinfo/rev-talk")
93
- rev-talk mailing list
94
- for details.
97
+ RevThread* + 1.8 performance is bad with Rev <= 0.3.1.
98
+ Rev 0.3.2 (when it is released) should be much faster under 1.8.
95
99
  %li
96
100
  waiting on Rubinius for better signal handling
97
101
  %li
@@ -180,6 +184,13 @@
180
184
  %a(href="http://rubyeventmachine.com") EventMachine
181
185
  %td.thr No
182
186
  %td.reent Yes
187
+ %tr.comp_row
188
+ %td.mod RevThreadPool
189
+ %td.slowio
190
+ thread-safe Ruby,
191
+ %a(href="http://rev.rubyforge.org/") Rev
192
+ %td.thr Yes
193
+ %td.reent No
183
194
 
184
195
  %ul
185
196
  %li
@@ -276,6 +287,12 @@
276
287
  %td.app_pool Yes*
277
288
  %td.lock Yes*
278
289
  %td.async NeverBlock, async_sinatra
290
+ %tr.comp_row
291
+ %td.mod RevThreadPool
292
+ %td.devfd Yes
293
+ %td.app_pool Yes
294
+ %td.lock Dumb
295
+ %td.async standard Ruby
279
296
 
280
297
  %ul
281
298
  %li
data/GIT-VERSION-GEN CHANGED
@@ -1,7 +1,7 @@
1
1
  #!/bin/sh
2
2
 
3
3
  GVF=GIT-VERSION-FILE
4
- DEF_VER=v0.7.0.GIT
4
+ DEF_VER=v0.8.0.GIT
5
5
 
6
6
  LF='
7
7
  '
data/TODO CHANGED
@@ -9,9 +9,10 @@ care about.
9
9
 
10
10
  * EventMachine.spawn - should be like Revactor, maybe?
11
11
 
12
- * {Packet,Rev,EventMachine}+Fibers
12
+ * conditional app.deferred?(env) support
13
+ Merb uses it, some other servers support it
13
14
 
14
- * {Packet,Rev}ThreadPool
15
+ * {Rev,EventMachine}+Fibers+streaming input
15
16
 
16
17
  * Rev + callcc - current Rev model with callcc (should work with MBARI)
17
18
 
data/lib/rainbows.rb CHANGED
@@ -8,14 +8,16 @@ module Rainbows
8
8
  # global vars because class/instance variables are confusing me :<
9
9
  # this struct is only accessed inside workers and thus private to each
10
10
  # G.cur may not be used in the network concurrency model
11
- class State < Struct.new(:alive,:m,:cur,:kato,:server,:tmp)
11
+ class State < Struct.new(:alive,:m,:cur,:kato,:server,:tmp,:expire)
12
12
  def tick
13
13
  tmp.chmod(self.m = m == 0 ? 1 : 0)
14
+ exit!(2) if expire && Time.now >= expire
14
15
  alive && server.master_pid == Process.ppid or quit!
15
16
  end
16
17
 
17
18
  def quit!
18
19
  self.alive = false
20
+ self.expire ||= Time.now + (server.timeout * 2.0)
19
21
  server.class.const_get(:LISTENERS).map! { |s| s.close rescue nil }
20
22
  false
21
23
  end
@@ -78,6 +80,7 @@ module Rainbows
78
80
  :ThreadPool => 10,
79
81
  :Rev => 50,
80
82
  :RevThreadSpawn => 50,
83
+ :RevThreadPool => 50,
81
84
  :EventMachine => 50,
82
85
  :FiberSpawn => 50,
83
86
  :FiberPool => 50,
data/lib/rainbows/base.rb CHANGED
@@ -70,11 +70,11 @@ module Rainbows
70
70
  end
71
71
 
72
72
  def join_threads(threads)
73
- expire = Time.now + (timeout * 2.0)
74
- until threads.empty? || Time.now >= expire
75
- threads.delete_if { |thr| thr.alive? ? thr.join(0.01) : true }
76
- end
77
- exit!(0) unless threads.empty?
73
+ G.quit!
74
+ threads.delete_if do |thr|
75
+ G.tick
76
+ thr.alive? ? thr.join(0.01) : true
77
+ end until threads.empty?
78
78
  end
79
79
 
80
80
  def self.included(klass)
@@ -3,7 +3,7 @@
3
3
  module Rainbows
4
4
 
5
5
  module Const
6
- RAINBOWS_VERSION = '0.7.0'
6
+ RAINBOWS_VERSION = '0.8.0'
7
7
 
8
8
  include Unicorn::Const
9
9
 
@@ -0,0 +1,29 @@
1
+ # -*- encoding: binary -*-
2
+ require 'rainbows/rev'
3
+
4
+ RUBY_VERSION =~ %r{\A1\.8} && ::Rev::VERSION < "0.3.2" and
5
+ warn "Rainbows::RevThreadSpawn + Rev (< 0.3.2)" \
6
+ " does not work well under Ruby 1.8"
7
+
8
+ module Rainbows
9
+
10
+ module Rev
11
+ class Master < ::Rev::AsyncWatcher
12
+
13
+ def initialize(queue)
14
+ super()
15
+ @queue = queue
16
+ end
17
+
18
+ def <<(output)
19
+ @queue << output
20
+ signal
21
+ end
22
+
23
+ def on_signal
24
+ client, response = @queue.pop
25
+ client.response_write(response)
26
+ end
27
+ end
28
+ end
29
+ end
@@ -0,0 +1,53 @@
1
+ # -*- encoding: binary -*-
2
+ require 'thread'
3
+ require 'rainbows/rev/master'
4
+
5
+ module Rainbows
6
+ module Rev
7
+
8
+ class ThreadClient < Client
9
+
10
+ def app_call
11
+ KATO.delete(self)
12
+ disable
13
+ @env[RACK_INPUT] = @input
14
+ @input = nil # not sure why, @input seems to get closed otherwise...
15
+ app_dispatch # must be implemented by subclass
16
+ end
17
+
18
+ # this is only called in the master thread
19
+ def response_write(response)
20
+ enable
21
+ alive = @hp.keepalive? && G.alive
22
+ out = [ alive ? CONN_ALIVE : CONN_CLOSE ] if @hp.headers?
23
+ DeferredResponse.write(self, response, out)
24
+ return quit unless alive && G.alive
25
+
26
+ @env.clear
27
+ @hp.reset
28
+ @state = :headers
29
+ # keepalive requests are always body-less, so @input is unchanged
30
+ if @hp.headers(@env, @buf)
31
+ @input = HttpRequest::NULL_IO
32
+ app_call
33
+ else
34
+ KATO[self] = Time.now
35
+ end
36
+ end
37
+
38
+ # fails-safe application dispatch, we absolutely cannot
39
+ # afford to fail or raise an exception (killing the thread)
40
+ # here because that could cause a deadlock and we'd leak FDs
41
+ def app_response
42
+ begin
43
+ @env[REMOTE_ADDR] = @remote_addr
44
+ APP.call(@env.update(RACK_DEFAULTS))
45
+ rescue => e
46
+ Error.app(e) # we guarantee this does not raise
47
+ [ 500, {}, [] ]
48
+ end
49
+ end
50
+
51
+ end
52
+ end
53
+ end
@@ -0,0 +1,75 @@
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
+ # WARNING: this model does not currently perform well under 1.8 with
19
+ # Rev 0.3.1. Rev 0.3.2 should include significant performance
20
+ # improvements under Ruby 1.8.
21
+
22
+ module RevThreadPool
23
+
24
+ DEFAULTS = {
25
+ :pool_size => 10, # same default size as ThreadPool (w/o Rev)
26
+ }
27
+
28
+ def self.setup
29
+ DEFAULTS.each { |k,v| O[k] ||= v }
30
+ Integer === O[:pool_size] && O[:pool_size] > 0 or
31
+ raise ArgumentError, "pool_size must a be an Integer > 0"
32
+ end
33
+
34
+ class PoolWatcher < ::Rev::TimerWatcher
35
+ def initialize(threads)
36
+ @threads = threads
37
+ super(G.server.timeout, true)
38
+ end
39
+
40
+ def on_timer
41
+ @threads.each { |t| t.join(0) and G.quit! }
42
+ end
43
+ end
44
+
45
+ class Client < Rainbows::Rev::ThreadClient
46
+ def app_dispatch
47
+ QUEUE << self
48
+ end
49
+ end
50
+
51
+ include Rainbows::Rev::Core
52
+
53
+ def init_worker_threads(master, queue)
54
+ O[:pool_size].times.map do
55
+ Thread.new do
56
+ begin
57
+ client = queue.pop
58
+ master << [ client, client.app_response ]
59
+ rescue => e
60
+ Error.listen_loop(e)
61
+ end while true
62
+ end
63
+ end
64
+ end
65
+
66
+ def init_worker_process(worker)
67
+ super
68
+ master = Rev::Master.new(Queue.new).attach(::Rev::Loop.default)
69
+ queue = Client.const_set(:QUEUE, Queue.new)
70
+ threads = init_worker_threads(master, queue)
71
+ PoolWatcher.new(threads).attach(::Rev::Loop.default)
72
+ logger.info "RevThreadPool pool_size=#{O[:pool_size]}"
73
+ end
74
+ end
75
+ end
@@ -1,9 +1,5 @@
1
1
  # -*- encoding: binary -*-
2
- require 'rainbows/rev'
3
-
4
- RUBY_VERSION =~ %r{\A1\.8} && ::Rev::VERSION < "0.3.2" and
5
- warn "Rainbows::RevThreadSpawn + Rev (< 0.3.2)" \
6
- " does not work well under Ruby 1.8"
2
+ require 'rainbows/rev/thread'
7
3
 
8
4
  module Rainbows
9
5
 
@@ -15,73 +11,19 @@ module Rainbows
15
11
  # server are handled by the main thread and outside of the core
16
12
  # application dispatch.
17
13
  #
18
- # WARNING: this model does not currently perform well under 1.8. See the
19
- # {rev-talk mailing list}[http://rubyforge.org/mailman/listinfo/rev-talk]
20
- # for ongoing performance work that will hopefully make it into the
21
- # next release of {Rev}[http://rev.rubyforge.org/].
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
+ # WARNING: this model does not currently perform well under 1.8 with
19
+ # Rev 0.3.1. Rev 0.3.2 should include significant performance
20
+ # improvements under Ruby 1.8.
22
21
 
23
22
  module RevThreadSpawn
24
23
 
25
- class Master < ::Rev::AsyncWatcher
26
-
27
- def initialize
28
- super
29
- @queue = Queue.new
30
- end
31
-
32
- def <<(output)
33
- @queue << output
34
- signal
35
- end
36
-
37
- def on_signal
38
- client, response = @queue.pop
39
- client.response_write(response)
40
- end
41
- end
42
-
43
- class Client < Rainbows::Rev::Client
44
- DR = Rainbows::Rev::DeferredResponse
45
- KATO = Rainbows::Rev::KATO
46
-
47
- def response_write(response)
48
- enable
49
- alive = @hp.keepalive? && G.alive
50
- out = [ alive ? CONN_ALIVE : CONN_CLOSE ] if @hp.headers?
51
- DR.write(self, response, out)
52
- return quit unless alive && G.alive
53
-
54
- @env.clear
55
- @hp.reset
56
- @state = :headers
57
- # keepalive requests are always body-less, so @input is unchanged
58
- if @hp.headers(@env, @buf)
59
- @input = HttpRequest::NULL_IO
60
- app_call
61
- else
62
- KATO[self] = Time.now
63
- end
64
- end
65
-
66
- # fails-safe application dispatch, we absolutely cannot
67
- # afford to fail or raise an exception (killing the thread)
68
- # here because that could cause a deadlock and we'd leak FDs
69
- def app_response
70
- begin
71
- @env[REMOTE_ADDR] = @remote_addr
72
- APP.call(@env.update(RACK_DEFAULTS))
73
- rescue => e
74
- Error.app(e) # we guarantee this does not raise
75
- [ 500, {}, [] ]
76
- end
77
- end
78
-
79
- def app_call
80
- KATO.delete(client = self)
81
- disable
82
- @env[RACK_INPUT] = @input
83
- @input = nil # not sure why, @input seems to get closed otherwise...
84
- Thread.new { MASTER << [ client, app_response ] }
24
+ class Client < Rainbows::Rev::ThreadClient
25
+ def app_dispatch
26
+ Thread.new(self) { |client| MASTER << [ client, app_response ] }
85
27
  end
86
28
  end
87
29
 
@@ -89,7 +31,8 @@ module Rainbows
89
31
 
90
32
  def init_worker_process(worker)
91
33
  super
92
- Client.const_set(:MASTER, Master.new.attach(::Rev::Loop.default))
34
+ master = Rev::Master.new(Queue.new).attach(::Rev::Loop.default)
35
+ Client.const_set(:MASTER, master)
93
36
  end
94
37
 
95
38
  end
@@ -78,42 +78,46 @@ module Rainbows
78
78
  def worker_loop(worker)
79
79
  init_worker_process(worker)
80
80
  RD_ARGS[:timeout] = G.kato if G.kato > 0
81
-
82
- root = Actor.current
83
- root.trap_exit = true
84
-
81
+ nr = 0
85
82
  limit = worker_connections
86
- revactorize_listeners!
87
- clients = {}
83
+ actor_exit = Case[:exit, Actor, Object]
88
84
 
89
- listeners = LISTENERS.map do |s|
90
- Actor.spawn(s) do |l|
85
+ revactorize_listeners.each do |l, close, accept|
86
+ Actor.spawn(l, close, accept) do |l, close, accept|
87
+ Actor.current.trap_exit = true
88
+ l.controller = l.instance_eval { @receiver = Actor.current }
91
89
  begin
92
- while clients.size >= limit
93
- logger.info "busy: clients=#{clients.size} >= limit=#{limit}"
94
- Actor.receive { |filter| filter.when(:resume) {} }
90
+ while nr >= limit
91
+ l.disable if l.enabled?
92
+ logger.info "busy: clients=#{nr} >= limit=#{limit}"
93
+ Actor.receive do |f|
94
+ f.when(close) {}
95
+ f.when(actor_exit) { nr -= 1 }
96
+ f.after(0.01) {} # another listener could've gotten an exit
97
+ end
98
+ end
99
+
100
+ l.enable unless l.enabled?
101
+ Actor.receive do |f|
102
+ f.when(close) {}
103
+ f.when(actor_exit) { nr -= 1 }
104
+ f.when(accept) do |_, _, s|
105
+ nr += 1
106
+ Actor.spawn_link(s) { |c| process_client(c) }
107
+ end
95
108
  end
96
- actor = Actor.spawn(l.accept) { |c| process_client(c) }
97
- clients[actor.object_id] = actor
98
- root.link(actor)
99
- rescue Errno::EAGAIN, Errno::ECONNABORTED
100
109
  rescue => e
101
110
  Error.listen_loop(e)
102
111
  end while G.alive
112
+ Actor.receive do |f|
113
+ f.when(close) {}
114
+ f.when(actor_exit) { nr -= 1 }
115
+ end while nr > 0
103
116
  end
104
117
  end
105
118
 
106
- begin
107
- Actor.receive do |filter|
108
- filter.after(1) { G.tick }
109
- filter.when(Case[:exit, Actor, Object]) do |_,actor,_|
110
- orig = clients.size
111
- clients.delete(actor.object_id)
112
- orig >= limit and listeners.each { |l| l << :resume }
113
- G.tick
114
- end
115
- end
116
- end while G.alive || clients.size > 0
119
+ Actor.sleep 1 while G.tick || nr > 0
120
+ rescue Errno::EMFILE => e
117
121
  end
118
122
 
119
123
  # if we get any error, try to write something back to the client
@@ -127,13 +131,17 @@ module Rainbows
127
131
  rescue
128
132
  end
129
133
 
130
- def revactorize_listeners!
131
- LISTENERS.map! do |s|
134
+ def revactorize_listeners
135
+ LISTENERS.map do |s|
132
136
  case s
133
137
  when TCPServer
134
- ::Revactor::TCP.listen(s, nil)
138
+ l = ::Revactor::TCP.listen(s, nil)
139
+ [ l, T[:tcp_closed, ::Revactor::TCP::Socket],
140
+ T[:tcp_connection, l, ::Revactor::TCP::Socket] ]
135
141
  when UNIXServer
136
- ::Revactor::UNIX.listen(s)
142
+ l = ::Revactor::UNIX.listen(s)
143
+ [ l, T[:unix_closed, ::Revactor::UNIX::Socket ],
144
+ T[:unix_connection, l, ::Revactor::UNIX::Socket] ]
137
145
  end
138
146
  end
139
147
  end
data/rainbows.1 ADDED
File without changes
data/t/GNUmakefile CHANGED
@@ -32,6 +32,7 @@ ifeq ($(ONENINE),true)
32
32
 
33
33
  # technically this works under 1.8, but wait until rev 0.3.2
34
34
  models += RevThreadSpawn
35
+ models += RevThreadPool
35
36
  endif
36
37
  all_models := $(models) Base
37
38
 
@@ -0,0 +1,9 @@
1
+ use Rack::ContentLength
2
+ use Rack::ContentType
3
+ run lambda { |env|
4
+ if env['rack.multithread'] && env['rainbows.model'] == :RevThreadPool
5
+ [ 200, {}, [ env.inspect << "\n" ] ]
6
+ else
7
+ raise "rack.multithread is false"
8
+ end
9
+ }
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: rainbows
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.7.0
4
+ version: 0.8.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Rainbows! hackers
@@ -9,7 +9,7 @@ autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
11
 
12
- date: 2009-11-30 00:00:00 +00:00
12
+ date: 2009-12-02 00:00:00 +00:00
13
13
  default_executable:
14
14
  dependencies:
15
15
  - !ruby/object:Gem::Dependency
@@ -60,6 +60,9 @@ extra_rdoc_files:
60
60
  - lib/rainbows/rev/core.rb
61
61
  - lib/rainbows/rev/deferred_response.rb
62
62
  - lib/rainbows/rev/heartbeat.rb
63
+ - lib/rainbows/rev/master.rb
64
+ - lib/rainbows/rev/thread.rb
65
+ - lib/rainbows/rev_thread_pool.rb
63
66
  - lib/rainbows/rev_thread_spawn.rb
64
67
  - lib/rainbows/revactor.rb
65
68
  - lib/rainbows/revactor/tee_input.rb
@@ -67,6 +70,7 @@ extra_rdoc_files:
67
70
  - lib/rainbows/thread_spawn.rb
68
71
  - LICENSE
69
72
  - NEWS
73
+ - rainbows.1
70
74
  - README
71
75
  - SIGNALS
72
76
  - TODO
@@ -121,6 +125,9 @@ files:
121
125
  - lib/rainbows/rev/core.rb
122
126
  - lib/rainbows/rev/deferred_response.rb
123
127
  - lib/rainbows/rev/heartbeat.rb
128
+ - lib/rainbows/rev/master.rb
129
+ - lib/rainbows/rev/thread.rb
130
+ - lib/rainbows/rev_thread_pool.rb
124
131
  - lib/rainbows/rev_thread_spawn.rb
125
132
  - lib/rainbows/revactor.rb
126
133
  - lib/rainbows/revactor/tee_input.rb
@@ -155,6 +162,7 @@ files:
155
162
  - t/simple-http_FiberSpawn.ru
156
163
  - t/simple-http_NeverBlock.ru
157
164
  - t/simple-http_Rev.ru
165
+ - t/simple-http_RevThreadPool.ru
158
166
  - t/simple-http_RevThreadSpawn.ru
159
167
  - t/simple-http_Revactor.ru
160
168
  - t/simple-http_ThreadPool.ru
@@ -186,6 +194,7 @@ files:
186
194
  - t/test-lib.sh
187
195
  - t/worker-follows-master-to-death.ru
188
196
  - vs_Unicorn
197
+ - rainbows.1
189
198
  has_rdoc: true
190
199
  homepage: http://rainbows.rubyforge.org/
191
200
  licenses: []