rainbows 0.7.0 → 0.8.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.
@@ -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: []