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,11 @@
|
|
1
|
+
# -*- encoding: binary -*-
|
2
|
+
require "test/unit"
|
3
|
+
require "raindrops"
|
4
|
+
require "open-uri"
|
5
|
+
begin
|
6
|
+
require "unicorn"
|
7
|
+
require "rack"
|
8
|
+
require "rack/lobster"
|
9
|
+
rescue LoadError => e
|
10
|
+
warn "W: #{e} skipping test since Rack or Unicorn was not found"
|
11
|
+
end
|
@@ -0,0 +1,65 @@
|
|
1
|
+
require "test/unit"
|
2
|
+
require "raindrops"
|
3
|
+
pmq = begin
|
4
|
+
Raindrops::Aggregate::PMQ
|
5
|
+
rescue LoadError => e
|
6
|
+
warn "W: #{e} skipping #{__FILE__}"
|
7
|
+
false
|
8
|
+
end
|
9
|
+
if RUBY_VERSION.to_f < 1.9
|
10
|
+
pmq = false
|
11
|
+
warn "W: skipping #{__FILE__}, only Ruby 1.9 supported for now"
|
12
|
+
end
|
13
|
+
|
14
|
+
Thread.abort_on_exception = true
|
15
|
+
|
16
|
+
class TestAggregatePMQ < Test::Unit::TestCase
|
17
|
+
|
18
|
+
def setup
|
19
|
+
@queue = "/test.#{rand}"
|
20
|
+
end
|
21
|
+
|
22
|
+
def teardown
|
23
|
+
POSIX_MQ.unlink @queue
|
24
|
+
end
|
25
|
+
|
26
|
+
def test_run
|
27
|
+
pmq = Raindrops::Aggregate::PMQ.new :queue => @queue
|
28
|
+
thr = Thread.new { pmq.master_loop }
|
29
|
+
agg = Aggregate.new
|
30
|
+
(1..10).each { |i| pmq << i; agg << i }
|
31
|
+
pmq.stop_master_loop
|
32
|
+
assert thr.join
|
33
|
+
assert_equal agg.count, pmq.count
|
34
|
+
assert_equal agg.mean, pmq.mean
|
35
|
+
assert_equal agg.std_dev, pmq.std_dev
|
36
|
+
assert_equal agg.min, pmq.min
|
37
|
+
assert_equal agg.max, pmq.max
|
38
|
+
assert_equal agg.to_s, pmq.to_s
|
39
|
+
end
|
40
|
+
|
41
|
+
def test_multi_process
|
42
|
+
nr_workers = 4
|
43
|
+
nr = 100
|
44
|
+
pmq = Raindrops::Aggregate::PMQ.new :queue => @queue
|
45
|
+
pid = fork { pmq.master_loop }
|
46
|
+
workers = (1..nr_workers).map {
|
47
|
+
fork {
|
48
|
+
(1..nr).each { |i| pmq << i }
|
49
|
+
pmq.flush
|
50
|
+
}
|
51
|
+
}
|
52
|
+
workers.each { |wpid| assert Process.waitpid2(wpid).last.success? }
|
53
|
+
pmq.stop_master_loop
|
54
|
+
assert Process.waitpid2(pid).last.success?
|
55
|
+
assert_equal 400, pmq.count
|
56
|
+
agg = Aggregate.new
|
57
|
+
(1..nr_workers).map { (1..nr).each { |i| agg << i } }
|
58
|
+
assert_equal agg.to_s, pmq.to_s
|
59
|
+
assert_equal agg.mean, pmq.mean
|
60
|
+
assert_equal agg.std_dev, pmq.std_dev
|
61
|
+
assert_equal agg.min, pmq.min
|
62
|
+
assert_equal agg.max, pmq.max
|
63
|
+
assert_equal agg.to_s, pmq.to_s
|
64
|
+
end
|
65
|
+
end if pmq
|
@@ -0,0 +1,16 @@
|
|
1
|
+
# -*- encoding: binary -*-
|
2
|
+
require 'test/unit'
|
3
|
+
require 'raindrops'
|
4
|
+
require 'fcntl'
|
5
|
+
$stderr.sync = $stdout.sync = true
|
6
|
+
|
7
|
+
class TestInetDiagSocket < Test::Unit::TestCase
|
8
|
+
def test_new
|
9
|
+
sock = Raindrops::InetDiagSocket.new
|
10
|
+
assert_kind_of Socket, sock
|
11
|
+
assert_kind_of Integer, sock.fileno
|
12
|
+
flags = sock.fcntl(Fcntl::F_GETFD)
|
13
|
+
assert_equal Fcntl::FD_CLOEXEC, flags & Fcntl::FD_CLOEXEC
|
14
|
+
assert_nil sock.close
|
15
|
+
end
|
16
|
+
end if RUBY_PLATFORM =~ /linux/
|
@@ -0,0 +1,57 @@
|
|
1
|
+
begin
|
2
|
+
require 'aggregate'
|
3
|
+
have_aggregate = true
|
4
|
+
rescue LoadError => e
|
5
|
+
warn "W: #{e} skipping #{__FILE__}"
|
6
|
+
end
|
7
|
+
require 'test/unit'
|
8
|
+
require 'raindrops'
|
9
|
+
require 'io/wait'
|
10
|
+
|
11
|
+
class TestLastDataRecv < Test::Unit::TestCase
|
12
|
+
def setup
|
13
|
+
Raindrops::Aggregate::LastDataRecv.default_aggregate = []
|
14
|
+
end
|
15
|
+
|
16
|
+
def teardown
|
17
|
+
Raindrops::Aggregate::LastDataRecv.default_aggregate = nil
|
18
|
+
end
|
19
|
+
|
20
|
+
def test_accept_nonblock_agg
|
21
|
+
s = Socket.new(:INET, :STREAM, 0)
|
22
|
+
s.listen(128)
|
23
|
+
addr = s.connect_address
|
24
|
+
s.extend(Raindrops::Aggregate::LastDataRecv)
|
25
|
+
s.raindrops_aggregate = []
|
26
|
+
c = Socket.new(:INET, :STREAM, 0)
|
27
|
+
c.connect(addr)
|
28
|
+
c.write '.' # for TCP_DEFER_ACCEPT
|
29
|
+
client, ai = s.accept_nonblock(exception: false)
|
30
|
+
assert client.kind_of?(Socket)
|
31
|
+
assert ai.kind_of?(Addrinfo)
|
32
|
+
assert_equal 1, s.raindrops_aggregate.size
|
33
|
+
assert s.raindrops_aggregate[0].instance_of?(Integer)
|
34
|
+
client, ai = s.accept_nonblock(exception: false)
|
35
|
+
assert_equal :wait_readable, client
|
36
|
+
assert_nil ai
|
37
|
+
assert_equal 1, s.raindrops_aggregate.size
|
38
|
+
assert_raise(IO::WaitReadable) { s.accept_nonblock }
|
39
|
+
end
|
40
|
+
|
41
|
+
def test_accept_nonblock_one
|
42
|
+
s = TCPServer.new('127.0.0.1', 0)
|
43
|
+
s.extend(Raindrops::Aggregate::LastDataRecv)
|
44
|
+
s.raindrops_aggregate = []
|
45
|
+
addr = s.addr
|
46
|
+
c = TCPSocket.new(addr[3], addr[1])
|
47
|
+
c.write '.' # for TCP_DEFER_ACCEPT
|
48
|
+
client = s.accept_nonblock(exception: false)
|
49
|
+
assert client.kind_of?(TCPSocket)
|
50
|
+
assert_equal 1, s.raindrops_aggregate.size
|
51
|
+
assert s.raindrops_aggregate[0].instance_of?(Integer)
|
52
|
+
client = s.accept_nonblock(exception: false)
|
53
|
+
assert_equal :wait_readable, client
|
54
|
+
assert_equal 1, s.raindrops_aggregate.size
|
55
|
+
assert_raise(IO::WaitReadable) { s.accept_nonblock }
|
56
|
+
end
|
57
|
+
end if RUBY_PLATFORM =~ /linux/ && have_aggregate
|
@@ -0,0 +1,69 @@
|
|
1
|
+
# -*- encoding: binary -*-
|
2
|
+
require "./test/rack_unicorn"
|
3
|
+
require "tempfile"
|
4
|
+
require "net/http"
|
5
|
+
|
6
|
+
$stderr.sync = $stdout.sync = true
|
7
|
+
pmq = begin
|
8
|
+
Raindrops::Aggregate::PMQ
|
9
|
+
rescue LoadError => e
|
10
|
+
warn "W: #{e} skipping #{__FILE__}"
|
11
|
+
false
|
12
|
+
end
|
13
|
+
if RUBY_VERSION.to_f < 1.9
|
14
|
+
pmq = false
|
15
|
+
warn "W: skipping test=#{__FILE__}, only Ruby 1.9 supported for now"
|
16
|
+
end
|
17
|
+
|
18
|
+
class TestLastDataRecvUnicorn < Test::Unit::TestCase
|
19
|
+
def setup
|
20
|
+
@queue = "/test.#{rand}"
|
21
|
+
@host = ENV["UNICORN_TEST_ADDR"] || "127.0.0.1"
|
22
|
+
@sock = TCPServer.new @host, 0
|
23
|
+
@port = @sock.addr[1]
|
24
|
+
ENV["UNICORN_FD"] = @sock.fileno.to_s
|
25
|
+
@host_with_port = "#@host:#@port"
|
26
|
+
@cfg = Tempfile.new 'unicorn_config_file'
|
27
|
+
@cfg.puts "require 'raindrops'"
|
28
|
+
@cfg.puts "preload_app true"
|
29
|
+
ENV['RAINDROPS_MQUEUE'] = @queue
|
30
|
+
# @cfg.puts "worker_processes 4"
|
31
|
+
@opts = { :listeners => [ @host_with_port ], :config_file => @cfg.path }
|
32
|
+
end
|
33
|
+
|
34
|
+
def test_auto_listener
|
35
|
+
@srv = fork {
|
36
|
+
Thread.abort_on_exception = true
|
37
|
+
app = %q!Rack::Builder.new do
|
38
|
+
map("/ldr") { run Raindrops::LastDataRecv.new }
|
39
|
+
map("/") { run Rack::Lobster.new }
|
40
|
+
end.to_app!
|
41
|
+
def app.arity; 0; end
|
42
|
+
def app.call; eval self; end
|
43
|
+
Unicorn::HttpServer.new(app, @opts).start.join
|
44
|
+
}
|
45
|
+
400.times { assert_kind_of Net::HTTPSuccess, get("/") }
|
46
|
+
resp = get("/ldr")
|
47
|
+
# # p(resp.methods - Object.methods)
|
48
|
+
# resp.each_header { |k,v| p [k, "=" , v] }
|
49
|
+
assert resp.header["x-count"]
|
50
|
+
assert resp.header["x-min"]
|
51
|
+
assert resp.header["x-max"]
|
52
|
+
assert resp.header["x-mean"]
|
53
|
+
assert resp.header["x-std-dev"]
|
54
|
+
assert resp.header["x-outliers-low"]
|
55
|
+
assert resp.header["x-outliers-high"]
|
56
|
+
assert resp.body.size > 0
|
57
|
+
end
|
58
|
+
|
59
|
+
def get(path)
|
60
|
+
Net::HTTP.start(@host, @port) { |http| http.get path }
|
61
|
+
end
|
62
|
+
|
63
|
+
def teardown
|
64
|
+
Process.kill :QUIT, @srv
|
65
|
+
_, status = Process.waitpid2 @srv
|
66
|
+
assert status.success?
|
67
|
+
POSIX_MQ.unlink @queue
|
68
|
+
end
|
69
|
+
end if defined?(Unicorn) && RUBY_PLATFORM =~ /linux/ && pmq
|
data/test/test_linux.rb
ADDED
@@ -0,0 +1,281 @@
|
|
1
|
+
# -*- encoding: binary -*-
|
2
|
+
require 'test/unit'
|
3
|
+
require 'tempfile'
|
4
|
+
require 'raindrops'
|
5
|
+
require 'socket'
|
6
|
+
require 'pp'
|
7
|
+
$stderr.sync = $stdout.sync = true
|
8
|
+
|
9
|
+
class TestLinux < Test::Unit::TestCase
|
10
|
+
include Raindrops::Linux
|
11
|
+
|
12
|
+
TEST_ADDR = ENV['UNICORN_TEST_ADDR'] || '127.0.0.1'
|
13
|
+
|
14
|
+
def setup
|
15
|
+
@to_close = []
|
16
|
+
end
|
17
|
+
|
18
|
+
def teardown
|
19
|
+
@to_close.each { |io| io.close unless io.closed? }
|
20
|
+
end
|
21
|
+
|
22
|
+
def test_unix
|
23
|
+
tmp = Tempfile.new("\xde\xad\xbe\xef") # valid path, really :)
|
24
|
+
File.unlink(tmp.path)
|
25
|
+
us = UNIXServer.new(tmp.path)
|
26
|
+
stats = unix_listener_stats([tmp.path])
|
27
|
+
assert_equal 1, stats.size
|
28
|
+
assert_equal 0, stats[tmp.path].active
|
29
|
+
assert_equal 0, stats[tmp.path].queued
|
30
|
+
|
31
|
+
@to_close << UNIXSocket.new(tmp.path)
|
32
|
+
stats = unix_listener_stats([tmp.path])
|
33
|
+
assert_equal 1, stats.size
|
34
|
+
assert_equal 0, stats[tmp.path].active
|
35
|
+
assert_equal 1, stats[tmp.path].queued
|
36
|
+
|
37
|
+
@to_close << UNIXSocket.new(tmp.path)
|
38
|
+
stats = unix_listener_stats([tmp.path])
|
39
|
+
assert_equal 1, stats.size
|
40
|
+
assert_equal 0, stats[tmp.path].active
|
41
|
+
assert_equal 2, stats[tmp.path].queued
|
42
|
+
|
43
|
+
@to_close << us.accept
|
44
|
+
stats = unix_listener_stats([tmp.path])
|
45
|
+
assert_equal 1, stats.size
|
46
|
+
assert_equal 1, stats[tmp.path].active
|
47
|
+
assert_equal 1, stats[tmp.path].queued
|
48
|
+
end
|
49
|
+
|
50
|
+
def test_unix_all
|
51
|
+
tmp = Tempfile.new("\xde\xad\xbe\xef") # valid path, really :)
|
52
|
+
File.unlink(tmp.path)
|
53
|
+
us = UNIXServer.new(tmp.path)
|
54
|
+
@to_close << UNIXSocket.new(tmp.path)
|
55
|
+
stats = unix_listener_stats
|
56
|
+
assert_equal 0, stats[tmp.path].active
|
57
|
+
assert_equal 1, stats[tmp.path].queued
|
58
|
+
|
59
|
+
@to_close << UNIXSocket.new(tmp.path)
|
60
|
+
stats = unix_listener_stats
|
61
|
+
assert_equal 0, stats[tmp.path].active
|
62
|
+
assert_equal 2, stats[tmp.path].queued
|
63
|
+
|
64
|
+
@to_close << us.accept
|
65
|
+
stats = unix_listener_stats
|
66
|
+
assert_equal 1, stats[tmp.path].active
|
67
|
+
assert_equal 1, stats[tmp.path].queued
|
68
|
+
end
|
69
|
+
|
70
|
+
def test_unix_all_unused
|
71
|
+
tmp = Tempfile.new("\xde\xad\xbe\xef") # valid path, really :)
|
72
|
+
File.unlink(tmp.path)
|
73
|
+
us = UNIXServer.new(tmp.path)
|
74
|
+
stats = unix_listener_stats
|
75
|
+
assert stats.keys.include?(tmp.path), stats.inspect
|
76
|
+
|
77
|
+
assert_equal 0, stats[tmp.path].active
|
78
|
+
assert_equal 0, stats[tmp.path].queued
|
79
|
+
us.close
|
80
|
+
end
|
81
|
+
|
82
|
+
def test_unix_resolves_symlinks
|
83
|
+
tmp = Tempfile.new("\xde\xad\xbe\xef") # valid path, really :)
|
84
|
+
File.unlink(tmp.path)
|
85
|
+
us = UNIXServer.new(tmp.path)
|
86
|
+
|
87
|
+
# Create a symlink
|
88
|
+
link = Tempfile.new("somethingelse")
|
89
|
+
File.unlink(link.path) # We need an available name, not an actual file
|
90
|
+
File.symlink(tmp.path, link.path)
|
91
|
+
|
92
|
+
@to_close << UNIXSocket.new(tmp.path)
|
93
|
+
stats = unix_listener_stats
|
94
|
+
assert_equal 0, stats[tmp.path].active
|
95
|
+
assert_equal 1, stats[tmp.path].queued
|
96
|
+
|
97
|
+
@to_close << UNIXSocket.new(link.path)
|
98
|
+
stats = unix_listener_stats([link.path])
|
99
|
+
assert_equal 0, stats[link.path].active
|
100
|
+
assert_equal 2, stats[link.path].queued
|
101
|
+
|
102
|
+
assert_equal stats[link.path].object_id, stats[tmp.path].object_id
|
103
|
+
|
104
|
+
@to_close << us.accept
|
105
|
+
stats = unix_listener_stats
|
106
|
+
assert_equal 1, stats[tmp.path].active
|
107
|
+
assert_equal 1, stats[tmp.path].queued
|
108
|
+
end
|
109
|
+
|
110
|
+
def test_tcp
|
111
|
+
s = TCPServer.new(TEST_ADDR, 0)
|
112
|
+
port = s.addr[1]
|
113
|
+
addr = "#{TEST_ADDR}:#{port}"
|
114
|
+
addrs = [ addr ]
|
115
|
+
stats = tcp_listener_stats(addrs)
|
116
|
+
assert_equal 1, stats.size
|
117
|
+
assert_equal 0, stats[addr].queued
|
118
|
+
assert_equal 0, stats[addr].active
|
119
|
+
|
120
|
+
@to_close << TCPSocket.new(TEST_ADDR, port)
|
121
|
+
stats = tcp_listener_stats(addrs)
|
122
|
+
assert_equal 1, stats.size
|
123
|
+
assert_equal 1, stats[addr].queued
|
124
|
+
assert_equal 0, stats[addr].active
|
125
|
+
|
126
|
+
@to_close << s.accept
|
127
|
+
stats = tcp_listener_stats(addrs)
|
128
|
+
assert_equal 1, stats.size
|
129
|
+
assert_equal 0, stats[addr].queued
|
130
|
+
assert_equal 1, stats[addr].active
|
131
|
+
end
|
132
|
+
|
133
|
+
def test_tcp_reuse_sock
|
134
|
+
nlsock = Raindrops::InetDiagSocket.new
|
135
|
+
s = TCPServer.new(TEST_ADDR, 0)
|
136
|
+
port = s.addr[1]
|
137
|
+
addr = "#{TEST_ADDR}:#{port}"
|
138
|
+
addrs = [ addr ]
|
139
|
+
stats = tcp_listener_stats(addrs, nlsock)
|
140
|
+
assert_equal 1, stats.size
|
141
|
+
assert_equal 0, stats[addr].queued
|
142
|
+
assert_equal 0, stats[addr].active
|
143
|
+
|
144
|
+
@to_close << TCPSocket.new(TEST_ADDR, port)
|
145
|
+
stats = tcp_listener_stats(addrs, nlsock)
|
146
|
+
assert_equal 1, stats.size
|
147
|
+
assert_equal 1, stats[addr].queued
|
148
|
+
assert_equal 0, stats[addr].active
|
149
|
+
|
150
|
+
@to_close << s.accept
|
151
|
+
stats = tcp_listener_stats(addrs, nlsock)
|
152
|
+
assert_equal 1, stats.size
|
153
|
+
assert_equal 0, stats[addr].queued
|
154
|
+
assert_equal 1, stats[addr].active
|
155
|
+
ensure
|
156
|
+
nlsock.close
|
157
|
+
end
|
158
|
+
|
159
|
+
def test_tcp_multi
|
160
|
+
s1 = TCPServer.new(TEST_ADDR, 0)
|
161
|
+
s2 = TCPServer.new(TEST_ADDR, 0)
|
162
|
+
port1, port2 = s1.addr[1], s2.addr[1]
|
163
|
+
addr1, addr2 = "#{TEST_ADDR}:#{port1}", "#{TEST_ADDR}:#{port2}"
|
164
|
+
addrs = [ addr1, addr2 ]
|
165
|
+
stats = tcp_listener_stats(addrs)
|
166
|
+
assert_equal 2, stats.size
|
167
|
+
assert_equal 0, stats[addr1].queued
|
168
|
+
assert_equal 0, stats[addr1].active
|
169
|
+
assert_equal 0, stats[addr2].queued
|
170
|
+
assert_equal 0, stats[addr2].active
|
171
|
+
|
172
|
+
@to_close << TCPSocket.new(TEST_ADDR, port1)
|
173
|
+
stats = tcp_listener_stats(addrs)
|
174
|
+
assert_equal 2, stats.size
|
175
|
+
assert_equal 1, stats[addr1].queued
|
176
|
+
assert_equal 0, stats[addr1].active
|
177
|
+
assert_equal 0, stats[addr2].queued
|
178
|
+
assert_equal 0, stats[addr2].active
|
179
|
+
|
180
|
+
sc1 = s1.accept
|
181
|
+
stats = tcp_listener_stats(addrs)
|
182
|
+
assert_equal 2, stats.size
|
183
|
+
assert_equal 0, stats[addr1].queued
|
184
|
+
assert_equal 1, stats[addr1].active
|
185
|
+
assert_equal 0, stats[addr2].queued
|
186
|
+
assert_equal 0, stats[addr2].active
|
187
|
+
|
188
|
+
@to_close << TCPSocket.new(TEST_ADDR, port2)
|
189
|
+
stats = tcp_listener_stats(addrs)
|
190
|
+
assert_equal 2, stats.size
|
191
|
+
assert_equal 0, stats[addr1].queued
|
192
|
+
assert_equal 1, stats[addr1].active
|
193
|
+
assert_equal 1, stats[addr2].queued
|
194
|
+
assert_equal 0, stats[addr2].active
|
195
|
+
|
196
|
+
@to_close << TCPSocket.new(TEST_ADDR, port2)
|
197
|
+
stats = tcp_listener_stats(addrs)
|
198
|
+
assert_equal 2, stats.size
|
199
|
+
assert_equal 0, stats[addr1].queued
|
200
|
+
assert_equal 1, stats[addr1].active
|
201
|
+
assert_equal 2, stats[addr2].queued
|
202
|
+
assert_equal 0, stats[addr2].active
|
203
|
+
|
204
|
+
@to_close << s2.accept
|
205
|
+
stats = tcp_listener_stats(addrs)
|
206
|
+
assert_equal 2, stats.size
|
207
|
+
assert_equal 0, stats[addr1].queued
|
208
|
+
assert_equal 1, stats[addr1].active
|
209
|
+
assert_equal 1, stats[addr2].queued
|
210
|
+
assert_equal 1, stats[addr2].active
|
211
|
+
|
212
|
+
sc1.close
|
213
|
+
stats = tcp_listener_stats(addrs)
|
214
|
+
assert_equal 0, stats[addr1].queued
|
215
|
+
assert_equal 0, stats[addr1].active
|
216
|
+
assert_equal 1, stats[addr2].queued
|
217
|
+
assert_equal 1, stats[addr2].active
|
218
|
+
|
219
|
+
# make sure we don't leave "true" placeholders in results if a
|
220
|
+
# listener becomes invalid (even momentarily).
|
221
|
+
s2.close
|
222
|
+
stats = tcp_listener_stats(addrs)
|
223
|
+
assert stats.values.all? { |x| x.instance_of?(Raindrops::ListenStats) },
|
224
|
+
"placeholders left: #{stats.inspect}"
|
225
|
+
end
|
226
|
+
|
227
|
+
# tries to overflow buffers
|
228
|
+
def test_tcp_stress_test
|
229
|
+
nr_proc = 32
|
230
|
+
nr_sock = 500
|
231
|
+
s = TCPServer.new(TEST_ADDR, 0)
|
232
|
+
port = s.addr[1]
|
233
|
+
addr = "#{TEST_ADDR}:#{port}"
|
234
|
+
addrs = [ addr ]
|
235
|
+
rda, wra = IO.pipe
|
236
|
+
rdb, wrb = IO.pipe
|
237
|
+
|
238
|
+
nr_proc.times do
|
239
|
+
fork do
|
240
|
+
rda.close
|
241
|
+
wrb.close
|
242
|
+
@to_close.concat((1..nr_sock).map { s.accept })
|
243
|
+
wra.syswrite('.')
|
244
|
+
wra.close
|
245
|
+
rdb.sysread(1) # wait for parent to nuke us
|
246
|
+
end
|
247
|
+
end
|
248
|
+
|
249
|
+
nr_proc.times do
|
250
|
+
fork do
|
251
|
+
rda.close
|
252
|
+
wrb.close
|
253
|
+
@to_close.concat((1..nr_sock).map { TCPSocket.new(TEST_ADDR, port) })
|
254
|
+
wra.syswrite('.')
|
255
|
+
wra.close
|
256
|
+
rdb.sysread(1) # wait for parent to nuke us
|
257
|
+
end
|
258
|
+
end
|
259
|
+
|
260
|
+
assert_equal('.' * (nr_proc * 2), rda.read(nr_proc * 2))
|
261
|
+
|
262
|
+
rda.close
|
263
|
+
stats = tcp_listener_stats(addrs)
|
264
|
+
expect = { addr => Raindrops::ListenStats[nr_sock * nr_proc, 0] }
|
265
|
+
assert_equal expect, stats
|
266
|
+
|
267
|
+
@to_close << TCPSocket.new(TEST_ADDR, port)
|
268
|
+
stats = tcp_listener_stats(addrs)
|
269
|
+
expect = { addr => Raindrops::ListenStats[nr_sock * nr_proc, 1] }
|
270
|
+
assert_equal expect, stats
|
271
|
+
|
272
|
+
if ENV["BENCHMARK"].to_i != 0
|
273
|
+
require 'benchmark'
|
274
|
+
puts(Benchmark.measure{1000.times { tcp_listener_stats(addrs) }})
|
275
|
+
end
|
276
|
+
|
277
|
+
wrb.syswrite('.' * (nr_proc * 2)) # broadcast a wakeup
|
278
|
+
statuses = Process.waitall
|
279
|
+
statuses.each { |(_,status)| assert status.success?, status.inspect }
|
280
|
+
end if ENV["STRESS"].to_i != 0
|
281
|
+
end if RUBY_PLATFORM =~ /linux/
|
@@ -0,0 +1,66 @@
|
|
1
|
+
# -*- encoding: binary -*-
|
2
|
+
require 'test/unit'
|
3
|
+
require 'socket'
|
4
|
+
require 'raindrops'
|
5
|
+
require 'pp'
|
6
|
+
$stderr.sync = $stdout.sync = true
|
7
|
+
|
8
|
+
class TestLinuxAllTcpListenStats < Test::Unit::TestCase
|
9
|
+
include Raindrops::Linux
|
10
|
+
TEST_ADDR = ENV['UNICORN_TEST_ADDR'] || '127.0.0.1'
|
11
|
+
|
12
|
+
def test_print_all
|
13
|
+
puts "EVERYTHING"
|
14
|
+
pp Raindrops::Linux.tcp_listener_stats
|
15
|
+
puts("-" * 72)
|
16
|
+
end if $stdout.tty?
|
17
|
+
|
18
|
+
def setup
|
19
|
+
@socks = []
|
20
|
+
end
|
21
|
+
|
22
|
+
def teardown
|
23
|
+
@socks.each { |io| io.closed? or io.close }
|
24
|
+
end
|
25
|
+
|
26
|
+
def new_server
|
27
|
+
s = TCPServer.new TEST_ADDR, 0
|
28
|
+
@socks << s
|
29
|
+
[ s, s.addr[1] ]
|
30
|
+
end
|
31
|
+
|
32
|
+
def new_client(port)
|
33
|
+
s = TCPSocket.new("127.0.0.1", port)
|
34
|
+
@socks << s
|
35
|
+
s
|
36
|
+
end
|
37
|
+
|
38
|
+
def new_accept(srv)
|
39
|
+
c = srv.accept
|
40
|
+
@socks << c
|
41
|
+
c
|
42
|
+
end
|
43
|
+
|
44
|
+
def test_all_ports
|
45
|
+
srv, port = new_server
|
46
|
+
addr = "#{TEST_ADDR}:#{port}"
|
47
|
+
all = Raindrops::Linux.tcp_listener_stats
|
48
|
+
assert_equal [0,0], all[addr].to_a
|
49
|
+
|
50
|
+
new_client(port)
|
51
|
+
all = Raindrops::Linux.tcp_listener_stats
|
52
|
+
assert_equal [0,1], all[addr].to_a
|
53
|
+
|
54
|
+
new_client(port)
|
55
|
+
all = Raindrops::Linux.tcp_listener_stats
|
56
|
+
assert_equal [0,2], all[addr].to_a
|
57
|
+
|
58
|
+
new_accept(srv)
|
59
|
+
all = Raindrops::Linux.tcp_listener_stats
|
60
|
+
assert_equal [1,1], all[addr].to_a
|
61
|
+
|
62
|
+
new_accept(srv)
|
63
|
+
all = Raindrops::Linux.tcp_listener_stats
|
64
|
+
assert_equal [2,0], all[addr].to_a
|
65
|
+
end
|
66
|
+
end if RUBY_PLATFORM =~ /linux/
|
@@ -0,0 +1,43 @@
|
|
1
|
+
# -*- encoding: binary -*-
|
2
|
+
require 'test/unit'
|
3
|
+
require 'raindrops'
|
4
|
+
require 'socket'
|
5
|
+
require 'benchmark'
|
6
|
+
$stderr.sync = $stdout.sync = true
|
7
|
+
|
8
|
+
class TestLinuxAllTcpListenStatsLeak < Test::Unit::TestCase
|
9
|
+
|
10
|
+
TEST_ADDR = ENV['UNICORN_TEST_ADDR'] || '127.0.0.1'
|
11
|
+
|
12
|
+
|
13
|
+
def rss_kb
|
14
|
+
File.readlines("/proc/#$$/status").grep(/VmRSS:/)[0].split(/\s+/)[1].to_i
|
15
|
+
end
|
16
|
+
def test_leak
|
17
|
+
s = TCPServer.new(TEST_ADDR, 0)
|
18
|
+
start_kb = rss_kb
|
19
|
+
p [ :start_kb, start_kb ]
|
20
|
+
assert_nothing_raised do
|
21
|
+
p(Benchmark.measure {
|
22
|
+
1000.times { Raindrops::Linux.all_tcp_listener_stats }
|
23
|
+
})
|
24
|
+
end
|
25
|
+
cur_kb = rss_kb
|
26
|
+
p [ :cur_kb, cur_kb ]
|
27
|
+
now = Time.now.to_i
|
28
|
+
fin = now + 60
|
29
|
+
assert_nothing_raised do
|
30
|
+
1000000000.times { |i|
|
31
|
+
if (i % 1024) == 0
|
32
|
+
now = Time.now.to_i
|
33
|
+
break if now > fin
|
34
|
+
end
|
35
|
+
Raindrops::Linux.all_tcp_listener_stats
|
36
|
+
}
|
37
|
+
end
|
38
|
+
cur_kb = rss_kb
|
39
|
+
p [ :cur_kb, cur_kb ]
|
40
|
+
ensure
|
41
|
+
s.close
|
42
|
+
end
|
43
|
+
end if ENV["STRESS"].to_i != 0
|