rainbows 0.7.0 → 0.8.0
Sign up to get free protection for your applications and to get access to all the features.
- data/Documentation/comparison.haml +22 -5
- data/GIT-VERSION-GEN +1 -1
- data/TODO +3 -2
- data/lib/rainbows.rb +4 -1
- data/lib/rainbows/base.rb +5 -5
- data/lib/rainbows/const.rb +1 -1
- data/lib/rainbows/rev/master.rb +29 -0
- data/lib/rainbows/rev/thread.rb +53 -0
- data/lib/rainbows/rev_thread_pool.rb +75 -0
- data/lib/rainbows/rev_thread_spawn.rb +13 -70
- data/lib/rainbows/revactor.rb +38 -30
- data/rainbows.1 +0 -0
- data/t/GNUmakefile +1 -0
- data/t/simple-http_RevThreadPool.ru +9 -0
- metadata +11 -2
@@ -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
|
-
|
91
|
-
|
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
data/TODO
CHANGED
@@ -9,9 +9,10 @@ care about.
|
|
9
9
|
|
10
10
|
* EventMachine.spawn - should be like Revactor, maybe?
|
11
11
|
|
12
|
-
*
|
12
|
+
* conditional app.deferred?(env) support
|
13
|
+
Merb uses it, some other servers support it
|
13
14
|
|
14
|
-
* {
|
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
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
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)
|
data/lib/rainbows/const.rb
CHANGED
@@ -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
|
-
#
|
19
|
-
#
|
20
|
-
#
|
21
|
-
#
|
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
|
26
|
-
|
27
|
-
|
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
|
-
|
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
|
data/lib/rainbows/revactor.rb
CHANGED
@@ -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
|
-
|
87
|
-
clients = {}
|
83
|
+
actor_exit = Case[:exit, Actor, Object]
|
88
84
|
|
89
|
-
|
90
|
-
Actor.spawn(
|
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
|
93
|
-
|
94
|
-
|
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
|
-
|
107
|
-
|
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
|
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
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.
|
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-
|
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: []
|