raindrops-maintained 0.21.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.
- checksums.yaml +7 -0
- data/.document +7 -0
- data/.gitattributes +4 -0
- data/.gitignore +16 -0
- data/.manifest +62 -0
- data/.olddoc.yml +16 -0
- data/COPYING +165 -0
- data/GIT-VERSION-FILE +1 -0
- data/GIT-VERSION-GEN +40 -0
- data/GNUmakefile +4 -0
- data/LATEST +9 -0
- data/LICENSE +16 -0
- data/NEWS +384 -0
- data/README +101 -0
- data/TODO +3 -0
- data/archive/.gitignore +3 -0
- data/archive/slrnpull.conf +4 -0
- data/examples/linux-listener-stats.rb +122 -0
- data/examples/middleware.ru +5 -0
- data/examples/watcher.ru +4 -0
- data/examples/watcher_demo.ru +13 -0
- data/examples/yahns.conf.rb +30 -0
- data/examples/zbatery.conf.rb +16 -0
- data/ext/raindrops/extconf.rb +163 -0
- data/ext/raindrops/linux_inet_diag.c +713 -0
- data/ext/raindrops/my_fileno.h +16 -0
- data/ext/raindrops/raindrops.c +487 -0
- data/ext/raindrops/raindrops_atomic.h +23 -0
- data/ext/raindrops/tcp_info.c +245 -0
- data/lib/raindrops/aggregate/last_data_recv.rb +94 -0
- data/lib/raindrops/aggregate/pmq.rb +245 -0
- data/lib/raindrops/aggregate.rb +8 -0
- data/lib/raindrops/last_data_recv.rb +102 -0
- data/lib/raindrops/linux.rb +77 -0
- data/lib/raindrops/middleware/proxy.rb +40 -0
- data/lib/raindrops/middleware.rb +153 -0
- data/lib/raindrops/struct.rb +62 -0
- data/lib/raindrops/watcher.rb +428 -0
- data/lib/raindrops.rb +72 -0
- data/pkg.mk +151 -0
- data/raindrops-maintained.gemspec +1 -0
- data/raindrops.gemspec +26 -0
- data/setup.rb +1586 -0
- data/test/ipv6_enabled.rb +9 -0
- data/test/rack_unicorn.rb +11 -0
- data/test/test_aggregate_pmq.rb +65 -0
- data/test/test_inet_diag_socket.rb +16 -0
- data/test/test_last_data_recv.rb +57 -0
- data/test/test_last_data_recv_unicorn.rb +69 -0
- data/test/test_linux.rb +281 -0
- data/test/test_linux_all_tcp_listen_stats.rb +66 -0
- data/test/test_linux_all_tcp_listen_stats_leak.rb +43 -0
- data/test/test_linux_ipv6.rb +166 -0
- data/test/test_linux_middleware.rb +64 -0
- data/test/test_linux_reuseport_tcp_listen_stats.rb +51 -0
- data/test/test_middleware.rb +128 -0
- data/test/test_middleware_unicorn.rb +37 -0
- data/test/test_middleware_unicorn_ipv6.rb +37 -0
- data/test/test_raindrops.rb +207 -0
- data/test/test_raindrops_gc.rb +38 -0
- data/test/test_struct.rb +54 -0
- data/test/test_tcp_info.rb +88 -0
- data/test/test_watcher.rb +186 -0
- metadata +193 -0
@@ -0,0 +1,166 @@
|
|
1
|
+
# -*- encoding: binary -*-
|
2
|
+
require 'test/unit'
|
3
|
+
require 'tempfile'
|
4
|
+
require 'raindrops'
|
5
|
+
require 'socket'
|
6
|
+
require 'pp'
|
7
|
+
require "./test/ipv6_enabled"
|
8
|
+
$stderr.sync = $stdout.sync = true
|
9
|
+
|
10
|
+
class TestLinuxIPv6 < Test::Unit::TestCase
|
11
|
+
include Raindrops::Linux
|
12
|
+
|
13
|
+
TEST_ADDR = ENV["TEST_HOST6"] || "::1"
|
14
|
+
|
15
|
+
def setup
|
16
|
+
@to_close = []
|
17
|
+
end
|
18
|
+
|
19
|
+
def teardown
|
20
|
+
@to_close.each { |io| io.close unless io.closed? }
|
21
|
+
end
|
22
|
+
|
23
|
+
def test_tcp
|
24
|
+
s = TCPServer.new(TEST_ADDR, 0)
|
25
|
+
port = s.addr[1]
|
26
|
+
addr = "[#{TEST_ADDR}]:#{port}"
|
27
|
+
addrs = [ addr ]
|
28
|
+
stats = tcp_listener_stats(addrs)
|
29
|
+
assert_equal 1, stats.size
|
30
|
+
assert_equal 0, stats[addr].queued
|
31
|
+
assert_equal 0, stats[addr].active
|
32
|
+
|
33
|
+
@to_close << TCPSocket.new(TEST_ADDR, port)
|
34
|
+
stats = tcp_listener_stats(addrs)
|
35
|
+
assert_equal 1, stats.size
|
36
|
+
assert_equal 1, stats[addr].queued
|
37
|
+
assert_equal 0, stats[addr].active
|
38
|
+
|
39
|
+
@to_close << s.accept
|
40
|
+
stats = tcp_listener_stats(addrs)
|
41
|
+
assert_equal 1, stats.size
|
42
|
+
assert_equal 0, stats[addr].queued
|
43
|
+
assert_equal 1, stats[addr].active
|
44
|
+
end
|
45
|
+
|
46
|
+
def test_tcp_multi
|
47
|
+
s1 = TCPServer.new(TEST_ADDR, 0)
|
48
|
+
s2 = TCPServer.new(TEST_ADDR, 0)
|
49
|
+
port1, port2 = s1.addr[1], s2.addr[1]
|
50
|
+
addr1, addr2 = "[#{TEST_ADDR}]:#{port1}", "[#{TEST_ADDR}]:#{port2}"
|
51
|
+
addrs = [ addr1, addr2 ]
|
52
|
+
stats = tcp_listener_stats(addrs)
|
53
|
+
assert_equal 2, stats.size
|
54
|
+
assert_equal 0, stats[addr1].queued
|
55
|
+
assert_equal 0, stats[addr1].active
|
56
|
+
assert_equal 0, stats[addr2].queued
|
57
|
+
assert_equal 0, stats[addr2].active
|
58
|
+
|
59
|
+
@to_close << TCPSocket.new(TEST_ADDR, port1)
|
60
|
+
stats = tcp_listener_stats(addrs)
|
61
|
+
assert_equal 2, stats.size
|
62
|
+
assert_equal 1, stats[addr1].queued
|
63
|
+
assert_equal 0, stats[addr1].active
|
64
|
+
assert_equal 0, stats[addr2].queued
|
65
|
+
assert_equal 0, stats[addr2].active
|
66
|
+
|
67
|
+
sc1 = s1.accept
|
68
|
+
stats = tcp_listener_stats(addrs)
|
69
|
+
assert_equal 2, stats.size
|
70
|
+
assert_equal 0, stats[addr1].queued
|
71
|
+
assert_equal 1, stats[addr1].active
|
72
|
+
assert_equal 0, stats[addr2].queued
|
73
|
+
assert_equal 0, stats[addr2].active
|
74
|
+
|
75
|
+
@to_close << TCPSocket.new(TEST_ADDR, port2)
|
76
|
+
stats = tcp_listener_stats(addrs)
|
77
|
+
assert_equal 2, stats.size
|
78
|
+
assert_equal 0, stats[addr1].queued
|
79
|
+
assert_equal 1, stats[addr1].active
|
80
|
+
assert_equal 1, stats[addr2].queued
|
81
|
+
assert_equal 0, stats[addr2].active
|
82
|
+
|
83
|
+
@to_close << TCPSocket.new(TEST_ADDR, port2)
|
84
|
+
stats = tcp_listener_stats(addrs)
|
85
|
+
assert_equal 2, stats.size
|
86
|
+
assert_equal 0, stats[addr1].queued
|
87
|
+
assert_equal 1, stats[addr1].active
|
88
|
+
assert_equal 2, stats[addr2].queued
|
89
|
+
assert_equal 0, stats[addr2].active
|
90
|
+
|
91
|
+
@to_close << s2.accept
|
92
|
+
stats = tcp_listener_stats(addrs)
|
93
|
+
assert_equal 2, stats.size
|
94
|
+
assert_equal 0, stats[addr1].queued
|
95
|
+
assert_equal 1, stats[addr1].active
|
96
|
+
assert_equal 1, stats[addr2].queued
|
97
|
+
assert_equal 1, stats[addr2].active
|
98
|
+
|
99
|
+
sc1.close
|
100
|
+
stats = tcp_listener_stats(addrs)
|
101
|
+
assert_equal 0, stats[addr1].queued
|
102
|
+
assert_equal 0, stats[addr1].active
|
103
|
+
assert_equal 1, stats[addr2].queued
|
104
|
+
assert_equal 1, stats[addr2].active
|
105
|
+
end
|
106
|
+
|
107
|
+
def test_invalid_addresses
|
108
|
+
assert_raises(ArgumentError) { tcp_listener_stats(%w([1:::5)) }
|
109
|
+
assert_raises(ArgumentError) { tcp_listener_stats(%w([1:::]5)) }
|
110
|
+
end
|
111
|
+
|
112
|
+
# tries to overflow buffers
|
113
|
+
def test_tcp_stress_test
|
114
|
+
nr_proc = 32
|
115
|
+
nr_sock = 500
|
116
|
+
s = TCPServer.new(TEST_ADDR, 0)
|
117
|
+
port = s.addr[1]
|
118
|
+
addr = "[#{TEST_ADDR}]:#{port}"
|
119
|
+
addrs = [ addr ]
|
120
|
+
rda, wra = IO.pipe
|
121
|
+
rdb, wrb = IO.pipe
|
122
|
+
|
123
|
+
nr_proc.times do
|
124
|
+
fork do
|
125
|
+
rda.close
|
126
|
+
wrb.close
|
127
|
+
@to_close.concat((1..nr_sock).map { s.accept })
|
128
|
+
wra.syswrite('.')
|
129
|
+
wra.close
|
130
|
+
rdb.sysread(1) # wait for parent to nuke us
|
131
|
+
end
|
132
|
+
end
|
133
|
+
|
134
|
+
nr_proc.times do
|
135
|
+
fork do
|
136
|
+
rda.close
|
137
|
+
wrb.close
|
138
|
+
@to_close.concat((1..nr_sock).map { TCPSocket.new(TEST_ADDR, port) })
|
139
|
+
wra.syswrite('.')
|
140
|
+
wra.close
|
141
|
+
rdb.sysread(1) # wait for parent to nuke us
|
142
|
+
end
|
143
|
+
end
|
144
|
+
|
145
|
+
assert_equal('.' * (nr_proc * 2), rda.read(nr_proc * 2))
|
146
|
+
|
147
|
+
rda.close
|
148
|
+
stats = tcp_listener_stats(addrs)
|
149
|
+
expect = { addr => Raindrops::ListenStats[nr_sock * nr_proc, 0] }
|
150
|
+
assert_equal expect, stats
|
151
|
+
|
152
|
+
@to_close << TCPSocket.new(TEST_ADDR, port)
|
153
|
+
stats = tcp_listener_stats(addrs)
|
154
|
+
expect = { addr => Raindrops::ListenStats[nr_sock * nr_proc, 1] }
|
155
|
+
assert_equal expect, stats
|
156
|
+
|
157
|
+
if ENV["BENCHMARK"].to_i != 0
|
158
|
+
require 'benchmark'
|
159
|
+
puts(Benchmark.measure{1000.times { tcp_listener_stats(addrs) }})
|
160
|
+
end
|
161
|
+
|
162
|
+
wrb.syswrite('.' * (nr_proc * 2)) # broadcast a wakeup
|
163
|
+
statuses = Process.waitall
|
164
|
+
statuses.each { |(_,status)| assert status.success?, status.inspect }
|
165
|
+
end if ENV["STRESS"].to_i != 0
|
166
|
+
end if RUBY_PLATFORM =~ /linux/ && ipv6_enabled?
|
@@ -0,0 +1,64 @@
|
|
1
|
+
# -*- encoding: binary -*-
|
2
|
+
require 'test/unit'
|
3
|
+
require 'tempfile'
|
4
|
+
require 'raindrops'
|
5
|
+
require 'socket'
|
6
|
+
$stderr.sync = $stdout.sync = true
|
7
|
+
|
8
|
+
class TestLinuxMiddleware < Test::Unit::TestCase
|
9
|
+
|
10
|
+
def setup
|
11
|
+
@resp_headers = { 'Content-Type' => 'text/plain', 'Content-Length' => '0' }
|
12
|
+
@response = [ 200, @resp_headers, [] ]
|
13
|
+
@app = lambda { |env| @response }
|
14
|
+
@to_close = []
|
15
|
+
end
|
16
|
+
|
17
|
+
def teardown
|
18
|
+
@to_close.each { |io| io.close unless io.closed? }
|
19
|
+
end
|
20
|
+
|
21
|
+
def test_unix_listener
|
22
|
+
tmp = Tempfile.new("")
|
23
|
+
File.unlink(tmp.path)
|
24
|
+
@to_close << UNIXServer.new(tmp.path)
|
25
|
+
app = Raindrops::Middleware.new(@app, :listeners => [tmp.path])
|
26
|
+
linux_extra = "#{tmp.path} active: 0\n#{tmp.path} queued: 0\n"
|
27
|
+
response = app.call("PATH_INFO" => "/_raindrops")
|
28
|
+
|
29
|
+
expect = [
|
30
|
+
200,
|
31
|
+
{
|
32
|
+
"Content-Type" => "text/plain",
|
33
|
+
"Content-Length" => (22 + linux_extra.size).to_s
|
34
|
+
},
|
35
|
+
[
|
36
|
+
"calling: 0\nwriting: 0\n#{linux_extra}" \
|
37
|
+
]
|
38
|
+
]
|
39
|
+
assert_equal expect, response
|
40
|
+
end
|
41
|
+
|
42
|
+
def test_unix_listener_queued
|
43
|
+
tmp = Tempfile.new("")
|
44
|
+
File.unlink(tmp.path)
|
45
|
+
@to_close << UNIXServer.new(tmp.path)
|
46
|
+
@to_close << UNIXSocket.new(tmp.path)
|
47
|
+
app = Raindrops::Middleware.new(@app, :listeners => [tmp.path])
|
48
|
+
linux_extra = "#{tmp.path} active: 0\n#{tmp.path} queued: 1\n"
|
49
|
+
response = app.call("PATH_INFO" => "/_raindrops")
|
50
|
+
|
51
|
+
expect = [
|
52
|
+
200,
|
53
|
+
{
|
54
|
+
"Content-Type" => "text/plain",
|
55
|
+
"Content-Length" => (22 + linux_extra.size).to_s
|
56
|
+
},
|
57
|
+
[
|
58
|
+
"calling: 0\nwriting: 0\n#{linux_extra}" \
|
59
|
+
]
|
60
|
+
]
|
61
|
+
assert_equal expect, response
|
62
|
+
end
|
63
|
+
|
64
|
+
end if RUBY_PLATFORM =~ /linux/
|
@@ -0,0 +1,51 @@
|
|
1
|
+
# -*- encoding: binary -*-
|
2
|
+
require "./test/rack_unicorn"
|
3
|
+
require 'test/unit'
|
4
|
+
require 'socket'
|
5
|
+
require 'raindrops'
|
6
|
+
$stderr.sync = $stdout.sync = true
|
7
|
+
|
8
|
+
class TestLinuxReuseportTcpListenStats < Test::Unit::TestCase
|
9
|
+
include Raindrops::Linux
|
10
|
+
include Unicorn::SocketHelper
|
11
|
+
TEST_ADDR = ENV['UNICORN_TEST_ADDR'] || '127.0.0.1'
|
12
|
+
DEFAULT_BACKLOG = 10
|
13
|
+
|
14
|
+
def setup
|
15
|
+
@socks = []
|
16
|
+
end
|
17
|
+
|
18
|
+
def teardown
|
19
|
+
@socks.each { |io| io.closed? or io.close }
|
20
|
+
end
|
21
|
+
|
22
|
+
def new_socket_server(**kwargs)
|
23
|
+
s = new_tcp_server TEST_ADDR, kwargs[:port] || 0, kwargs
|
24
|
+
s.listen(kwargs[:backlog] || DEFAULT_BACKLOG)
|
25
|
+
@socks << s
|
26
|
+
[ s, s.addr[1] ]
|
27
|
+
end
|
28
|
+
|
29
|
+
def new_client(port)
|
30
|
+
s = TCPSocket.new("127.0.0.1", port)
|
31
|
+
@socks << s
|
32
|
+
s
|
33
|
+
end
|
34
|
+
|
35
|
+
def test_reuseport_queue_stats
|
36
|
+
listeners = 10
|
37
|
+
_, port = new_socket_server(reuseport: true)
|
38
|
+
addr = "#{TEST_ADDR}:#{port}"
|
39
|
+
(listeners - 1).times do
|
40
|
+
new_socket_server(reuseport: true, port: port)
|
41
|
+
end
|
42
|
+
|
43
|
+
listeners.times do |i|
|
44
|
+
all = Raindrops::Linux.tcp_listener_stats
|
45
|
+
assert_equal [0, i], all[addr].to_a
|
46
|
+
new_client(port)
|
47
|
+
all = Raindrops::Linux.tcp_listener_stats
|
48
|
+
assert_equal [0, i+1], all[addr].to_a
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end if RUBY_PLATFORM =~ /linux/ && Object.const_defined?(:Unicorn)
|
@@ -0,0 +1,128 @@
|
|
1
|
+
# -*- encoding: binary -*-
|
2
|
+
require 'test/unit'
|
3
|
+
require 'raindrops'
|
4
|
+
|
5
|
+
class TestMiddleware < Test::Unit::TestCase
|
6
|
+
|
7
|
+
def setup
|
8
|
+
@resp_headers = { 'Content-Type' => 'text/plain', 'Content-Length' => '0' }
|
9
|
+
@response = [ 200, @resp_headers, [] ]
|
10
|
+
@app = lambda { |env| @response }
|
11
|
+
end
|
12
|
+
|
13
|
+
def test_setup
|
14
|
+
app = Raindrops::Middleware.new(@app)
|
15
|
+
response = app.call({})
|
16
|
+
assert_equal @response[0,2], response[0,2]
|
17
|
+
assert response.last.kind_of?(Raindrops::Middleware::Proxy)
|
18
|
+
assert response.last.object_id != app.object_id
|
19
|
+
tmp = []
|
20
|
+
response.last.each { |y| tmp << y }
|
21
|
+
assert tmp.empty?
|
22
|
+
end
|
23
|
+
|
24
|
+
def test_alt_stats
|
25
|
+
stats = Raindrops::Middleware::Stats.new
|
26
|
+
app = lambda { |env|
|
27
|
+
if (stats.writing == 0 && stats.calling == 1)
|
28
|
+
@app.call(env)
|
29
|
+
else
|
30
|
+
[ 500, @resp_headers, [] ]
|
31
|
+
end
|
32
|
+
}
|
33
|
+
app = Raindrops::Middleware.new(app, :stats => stats)
|
34
|
+
response = app.call({})
|
35
|
+
assert_equal 0, stats.calling
|
36
|
+
assert_equal 1, stats.writing
|
37
|
+
assert_equal 200, response[0]
|
38
|
+
assert response.last.kind_of?(Raindrops::Middleware::Proxy)
|
39
|
+
tmp = []
|
40
|
+
response.last.each do |y|
|
41
|
+
assert_equal 1, stats.writing
|
42
|
+
tmp << y
|
43
|
+
end
|
44
|
+
assert tmp.empty?
|
45
|
+
end
|
46
|
+
|
47
|
+
def test_default_endpoint
|
48
|
+
app = Raindrops::Middleware.new(@app)
|
49
|
+
response = app.call("PATH_INFO" => "/_raindrops")
|
50
|
+
expect = [
|
51
|
+
200,
|
52
|
+
{ "Content-Type" => "text/plain", "Content-Length" => "22" },
|
53
|
+
[ "calling: 0\nwriting: 0\n" ]
|
54
|
+
]
|
55
|
+
assert_equal expect, response
|
56
|
+
end
|
57
|
+
|
58
|
+
def test_alt_endpoint
|
59
|
+
app = Raindrops::Middleware.new(@app, :path => "/foo")
|
60
|
+
response = app.call("PATH_INFO" => "/foo")
|
61
|
+
expect = [
|
62
|
+
200,
|
63
|
+
{ "Content-Type" => "text/plain", "Content-Length" => "22" },
|
64
|
+
[ "calling: 0\nwriting: 0\n" ]
|
65
|
+
]
|
66
|
+
assert_equal expect, response
|
67
|
+
end
|
68
|
+
|
69
|
+
def test_concurrent
|
70
|
+
rda, wra = IO.pipe
|
71
|
+
rdb, wrb = IO.pipe
|
72
|
+
app = lambda do |env|
|
73
|
+
wrb.close
|
74
|
+
wra.syswrite('.')
|
75
|
+
wra.close
|
76
|
+
|
77
|
+
# wait until parent has run app.call for stats endpoint
|
78
|
+
rdb.read
|
79
|
+
@app.call(env)
|
80
|
+
end
|
81
|
+
app = Raindrops::Middleware.new(app)
|
82
|
+
|
83
|
+
pid = fork { app.call({}) }
|
84
|
+
rdb.close
|
85
|
+
|
86
|
+
# wait til child is running in app.call
|
87
|
+
assert_equal '.', rda.sysread(1)
|
88
|
+
rda.close
|
89
|
+
|
90
|
+
response = app.call("PATH_INFO" => "/_raindrops")
|
91
|
+
expect = [
|
92
|
+
200,
|
93
|
+
{ "Content-Type" => "text/plain", "Content-Length" => "22" },
|
94
|
+
[ "calling: 1\nwriting: 0\n" ]
|
95
|
+
]
|
96
|
+
assert_equal expect, response
|
97
|
+
wrb.close # unblock child process
|
98
|
+
assert Process.waitpid2(pid).last.success?
|
99
|
+
|
100
|
+
# we didn't call close the body in the forked child, so it'll always be
|
101
|
+
# marked as writing, a real server would close the body
|
102
|
+
response = app.call("PATH_INFO" => "/_raindrops")
|
103
|
+
expect = [
|
104
|
+
200,
|
105
|
+
{ "Content-Type" => "text/plain", "Content-Length" => "22" },
|
106
|
+
[ "calling: 0\nwriting: 1\n" ]
|
107
|
+
]
|
108
|
+
assert_equal expect, response
|
109
|
+
end
|
110
|
+
|
111
|
+
def test_middleware_proxy_to_path_missing
|
112
|
+
app = Raindrops::Middleware.new(@app)
|
113
|
+
response = app.call({})
|
114
|
+
body = response[2]
|
115
|
+
assert_kind_of Raindrops::Middleware::Proxy, body
|
116
|
+
assert ! body.respond_to?(:to_path)
|
117
|
+
assert body.respond_to?(:close)
|
118
|
+
orig_body = @response[2]
|
119
|
+
|
120
|
+
def orig_body.to_path; "/dev/null"; end
|
121
|
+
assert body.respond_to?(:to_path)
|
122
|
+
assert_equal "/dev/null", body.to_path
|
123
|
+
|
124
|
+
def orig_body.body; "this is a body"; end
|
125
|
+
assert body.respond_to?(:body)
|
126
|
+
assert_equal "this is a body", body.body
|
127
|
+
end
|
128
|
+
end
|
@@ -0,0 +1,37 @@
|
|
1
|
+
# -*- encoding: binary -*-
|
2
|
+
require "./test/rack_unicorn"
|
3
|
+
$stderr.sync = $stdout.sync = true
|
4
|
+
|
5
|
+
class TestMiddlewareUnicorn < Test::Unit::TestCase
|
6
|
+
|
7
|
+
def setup
|
8
|
+
@host = ENV["UNICORN_TEST_ADDR"] || "127.0.0.1"
|
9
|
+
@sock = TCPServer.new @host, 0
|
10
|
+
@port = @sock.addr[1]
|
11
|
+
ENV["UNICORN_FD"] = @sock.fileno.to_s
|
12
|
+
@host_with_port = "#@host:#@port"
|
13
|
+
@opts = { :listeners => [ @host_with_port ] }
|
14
|
+
@addr_regexp = Regexp.escape @host_with_port
|
15
|
+
end
|
16
|
+
|
17
|
+
def test_auto_listener
|
18
|
+
@app = Rack::Builder.new do
|
19
|
+
use Raindrops::Middleware
|
20
|
+
run Rack::Lobster.new
|
21
|
+
end
|
22
|
+
@srv = fork { Unicorn::HttpServer.new(@app, @opts).start.join }
|
23
|
+
|
24
|
+
s = TCPSocket.new @host, @port
|
25
|
+
s.write "GET /_raindrops HTTP/1.0\r\n\r\n"
|
26
|
+
resp = s.read
|
27
|
+
_, body = resp.split(/\r\n\r\n/, 2)
|
28
|
+
assert_match %r{^#@addr_regexp active: 1$}, body
|
29
|
+
assert_match %r{^#@addr_regexp queued: 0$}, body
|
30
|
+
end
|
31
|
+
|
32
|
+
def teardown
|
33
|
+
Process.kill :QUIT, @srv
|
34
|
+
_, status = Process.waitpid2 @srv
|
35
|
+
assert status.success?
|
36
|
+
end
|
37
|
+
end if defined?(Unicorn) && RUBY_PLATFORM =~ /linux/
|
@@ -0,0 +1,37 @@
|
|
1
|
+
# -*- encoding: binary -*-
|
2
|
+
require "./test/rack_unicorn"
|
3
|
+
require "./test/ipv6_enabled"
|
4
|
+
$stderr.sync = $stdout.sync = true
|
5
|
+
|
6
|
+
class TestMiddlewareUnicornIPv6 < Test::Unit::TestCase
|
7
|
+
|
8
|
+
def setup
|
9
|
+
@host = ENV["TEST_HOST6"] || "::1"
|
10
|
+
sock = TCPServer.new @host, 0
|
11
|
+
@port = sock.addr[1]
|
12
|
+
ENV["UNICORN_FD"] = sock.fileno.to_s
|
13
|
+
@host_with_port = "[#@host]:#@port"
|
14
|
+
@opts = { :listeners => [ @host_with_port ] }
|
15
|
+
@addr_regexp = Regexp.escape @host_with_port
|
16
|
+
end
|
17
|
+
|
18
|
+
def test_auto_listener
|
19
|
+
@app = Rack::Builder.new do
|
20
|
+
use Raindrops::Middleware
|
21
|
+
run Rack::Lobster.new
|
22
|
+
end
|
23
|
+
@srv = fork { Unicorn::HttpServer.new(@app, @opts).start.join }
|
24
|
+
s = TCPSocket.new @host, @port
|
25
|
+
s.write "GET /_raindrops HTTP/1.0\r\n\r\n"
|
26
|
+
resp = s.read
|
27
|
+
_, body = resp.split(/\r\n\r\n/, 2)
|
28
|
+
assert_match %r{^#@addr_regexp active: 1$}, body
|
29
|
+
assert_match %r{^#@addr_regexp queued: 0$}, body
|
30
|
+
end
|
31
|
+
|
32
|
+
def teardown
|
33
|
+
Process.kill :QUIT, @srv
|
34
|
+
_, status = Process.waitpid2 @srv
|
35
|
+
assert status.success?
|
36
|
+
end
|
37
|
+
end if defined?(Unicorn) && RUBY_PLATFORM =~ /linux/ && ipv6_enabled?
|
@@ -0,0 +1,207 @@
|
|
1
|
+
# -*- encoding: binary -*-
|
2
|
+
require 'test/unit'
|
3
|
+
require 'raindrops'
|
4
|
+
require 'tempfile'
|
5
|
+
|
6
|
+
class TestRaindrops < Test::Unit::TestCase
|
7
|
+
|
8
|
+
def test_raindrop_counter_max
|
9
|
+
assert_kind_of Integer, Raindrops::MAX
|
10
|
+
assert Raindrops::MAX > 0
|
11
|
+
printf "Raindrops::MAX = 0x%x\n", Raindrops::MAX
|
12
|
+
end
|
13
|
+
|
14
|
+
def test_raindrop_size
|
15
|
+
assert_kind_of Integer, Raindrops::SIZE
|
16
|
+
assert Raindrops::SIZE > 0
|
17
|
+
puts "Raindrops::SIZE = #{Raindrops::SIZE}"
|
18
|
+
end
|
19
|
+
|
20
|
+
def test_page_size
|
21
|
+
assert_kind_of Integer, Raindrops::PAGE_SIZE
|
22
|
+
assert Raindrops::PAGE_SIZE > Raindrops::SIZE
|
23
|
+
end
|
24
|
+
|
25
|
+
def test_size_and_capa
|
26
|
+
rd = Raindrops.new(4)
|
27
|
+
assert_equal 4, rd.size
|
28
|
+
assert rd.capa >= rd.size
|
29
|
+
end
|
30
|
+
|
31
|
+
def test_ary
|
32
|
+
rd = Raindrops.new(4)
|
33
|
+
assert_equal [0, 0, 0, 0] , rd.to_ary
|
34
|
+
end
|
35
|
+
|
36
|
+
def test_incr_no_args
|
37
|
+
rd = Raindrops.new(4)
|
38
|
+
assert_equal 1, rd.incr(0)
|
39
|
+
assert_equal [1, 0, 0, 0], rd.to_ary
|
40
|
+
end
|
41
|
+
|
42
|
+
def test_incr_args
|
43
|
+
rd = Raindrops.new(4)
|
44
|
+
assert_equal 6, rd.incr(3, 6)
|
45
|
+
assert_equal [0, 0, 0, 6], rd.to_ary
|
46
|
+
end
|
47
|
+
|
48
|
+
def test_decr_args
|
49
|
+
rd = Raindrops.new(4)
|
50
|
+
rd[3] = 6
|
51
|
+
assert_equal 5, rd.decr(3, 1)
|
52
|
+
assert_equal [0, 0, 0, 5], rd.to_ary
|
53
|
+
end
|
54
|
+
|
55
|
+
def test_incr_shared
|
56
|
+
rd = Raindrops.new(2)
|
57
|
+
5.times do
|
58
|
+
pid = fork { rd.incr(1) }
|
59
|
+
_, status = Process.waitpid2(pid)
|
60
|
+
assert status.success?
|
61
|
+
end
|
62
|
+
assert_equal [0, 5], rd.to_ary
|
63
|
+
end
|
64
|
+
|
65
|
+
def test_incr_decr
|
66
|
+
rd = Raindrops.new(1)
|
67
|
+
fork { 1000000.times { rd.incr(0) } }
|
68
|
+
1000.times { rd.decr(0) }
|
69
|
+
statuses = Process.waitall
|
70
|
+
statuses.each { |pid, status| assert status.success? }
|
71
|
+
assert_equal [999000], rd.to_ary
|
72
|
+
end
|
73
|
+
|
74
|
+
def test_bad_incr
|
75
|
+
rd = Raindrops.new(1)
|
76
|
+
assert_raises(ArgumentError) { rd.incr(-1) }
|
77
|
+
assert_raises(ArgumentError) { rd.incr(2) }
|
78
|
+
assert_raises(ArgumentError) { rd.incr(0xffffffff) }
|
79
|
+
end
|
80
|
+
|
81
|
+
def test_dup
|
82
|
+
@rd = Raindrops.new(1)
|
83
|
+
rd = @rd.dup
|
84
|
+
assert_equal 1, @rd.incr(0)
|
85
|
+
assert_equal 1, rd.incr(0)
|
86
|
+
assert_equal 2, rd.incr(0)
|
87
|
+
assert_equal 2, rd[0]
|
88
|
+
assert_equal 1, @rd[0]
|
89
|
+
end
|
90
|
+
|
91
|
+
def test_clone
|
92
|
+
@rd = Raindrops.new(1)
|
93
|
+
rd = @rd.clone
|
94
|
+
assert_equal 1, @rd.incr(0)
|
95
|
+
assert_equal 1, rd.incr(0)
|
96
|
+
assert_equal 2, rd.incr(0)
|
97
|
+
assert_equal 2, rd[0]
|
98
|
+
assert_equal 1, @rd[0]
|
99
|
+
end
|
100
|
+
|
101
|
+
def test_big
|
102
|
+
expect = (1..256).map { 0 }
|
103
|
+
rd = Raindrops.new(256)
|
104
|
+
assert_equal expect, rd.to_ary
|
105
|
+
assert_nothing_raised { rd[255] = 5 }
|
106
|
+
assert_equal 5, rd[255]
|
107
|
+
assert_nothing_raised { rd[2] = 2 }
|
108
|
+
|
109
|
+
expect[255] = 5
|
110
|
+
expect[2] = 2
|
111
|
+
assert_equal expect, rd.to_ary
|
112
|
+
end
|
113
|
+
|
114
|
+
def test_resize
|
115
|
+
rd = Raindrops.new(4)
|
116
|
+
assert_equal 4, rd.size
|
117
|
+
assert_equal rd.capa, rd.size = rd.capa
|
118
|
+
assert_equal rd.capa, rd.to_ary.size
|
119
|
+
assert_equal 0, rd[rd.capa - 1]
|
120
|
+
assert_equal 1, rd.incr(rd.capa - 1)
|
121
|
+
assert_raises(ArgumentError) { rd[rd.capa] }
|
122
|
+
end
|
123
|
+
|
124
|
+
def test_resize_mremap
|
125
|
+
rd = Raindrops.new(4)
|
126
|
+
assert_equal 4, rd.size
|
127
|
+
old_capa = rd.capa
|
128
|
+
rd.size = rd.capa + 1
|
129
|
+
assert_equal old_capa * 2, rd.capa
|
130
|
+
|
131
|
+
# mremap() is currently broken with MAP_SHARED
|
132
|
+
# https://bugzilla.kernel.org/show_bug.cgi?id=8691
|
133
|
+
assert_equal 0, rd[old_capa]
|
134
|
+
assert_equal rd.capa, rd.to_ary.size
|
135
|
+
assert_equal 0, rd[rd.capa - 1]
|
136
|
+
assert_equal 1, rd.incr(rd.capa - 1)
|
137
|
+
assert_raises(ArgumentError) { rd[rd.capa] }
|
138
|
+
rescue RangeError
|
139
|
+
end # if RUBY_PLATFORM =~ /linux/
|
140
|
+
|
141
|
+
def test_evaporate
|
142
|
+
rd = Raindrops.new 1
|
143
|
+
assert_nil rd.evaporate!
|
144
|
+
assert_raises(StandardError) { rd.evaporate! }
|
145
|
+
end
|
146
|
+
|
147
|
+
def test_evaporate_with_fork
|
148
|
+
tmp = Raindrops.new 2
|
149
|
+
pid = fork do
|
150
|
+
tmp.incr 0
|
151
|
+
exit(tmp.evaporate! == nil)
|
152
|
+
end
|
153
|
+
_, status = Process.waitpid2(pid)
|
154
|
+
assert status.success?
|
155
|
+
assert_equal [ 1, 0 ], tmp.to_ary
|
156
|
+
tmp.incr 1
|
157
|
+
assert_equal [ 1, 1 ], tmp.to_ary
|
158
|
+
pid = fork do
|
159
|
+
tmp.incr 1
|
160
|
+
exit([ 1, 2 ] == tmp.to_ary)
|
161
|
+
end
|
162
|
+
_, status = Process.waitpid2(pid)
|
163
|
+
assert status.success?
|
164
|
+
assert_equal [ 1, 2 ], tmp.to_ary
|
165
|
+
end
|
166
|
+
|
167
|
+
def test_io_backed
|
168
|
+
file = Tempfile.new('test_io_backed')
|
169
|
+
rd = Raindrops.new(4, io: file, zero: true)
|
170
|
+
rd[0] = 123
|
171
|
+
rd[1] = 456
|
172
|
+
|
173
|
+
assert_equal 123, rd[0]
|
174
|
+
assert_equal 456, rd[1]
|
175
|
+
|
176
|
+
rd.evaporate!
|
177
|
+
|
178
|
+
file.rewind
|
179
|
+
data = file.read
|
180
|
+
assert_equal 123, data.unpack('L!')[0]
|
181
|
+
assert_equal 456, data[Raindrops::SIZE..data.size].unpack('L!')[0]
|
182
|
+
end
|
183
|
+
|
184
|
+
def test_io_backed_reuse
|
185
|
+
file = Tempfile.new('test_io_backed')
|
186
|
+
rd = Raindrops.new(4, io: file, zero: true)
|
187
|
+
rd[0] = 123
|
188
|
+
rd[1] = 456
|
189
|
+
rd.evaporate!
|
190
|
+
|
191
|
+
rd = Raindrops.new(4, io: file, zero: false)
|
192
|
+
assert_equal 123, rd[0]
|
193
|
+
assert_equal 456, rd[1]
|
194
|
+
end
|
195
|
+
|
196
|
+
def test_iobacked_noreuse
|
197
|
+
file = Tempfile.new('test_io_backed')
|
198
|
+
rd = Raindrops.new(4, io: file, zero: true)
|
199
|
+
rd[0] = 123
|
200
|
+
rd[1] = 456
|
201
|
+
rd.evaporate!
|
202
|
+
|
203
|
+
rd = Raindrops.new(4, io: file, zero: true)
|
204
|
+
assert_equal 0, rd[0]
|
205
|
+
assert_equal 0, rd[1]
|
206
|
+
end
|
207
|
+
end
|