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.
Files changed (64) hide show
  1. checksums.yaml +7 -0
  2. data/.document +7 -0
  3. data/.gitattributes +4 -0
  4. data/.gitignore +16 -0
  5. data/.manifest +62 -0
  6. data/.olddoc.yml +16 -0
  7. data/COPYING +165 -0
  8. data/GIT-VERSION-FILE +1 -0
  9. data/GIT-VERSION-GEN +40 -0
  10. data/GNUmakefile +4 -0
  11. data/LATEST +9 -0
  12. data/LICENSE +16 -0
  13. data/NEWS +384 -0
  14. data/README +101 -0
  15. data/TODO +3 -0
  16. data/archive/.gitignore +3 -0
  17. data/archive/slrnpull.conf +4 -0
  18. data/examples/linux-listener-stats.rb +122 -0
  19. data/examples/middleware.ru +5 -0
  20. data/examples/watcher.ru +4 -0
  21. data/examples/watcher_demo.ru +13 -0
  22. data/examples/yahns.conf.rb +30 -0
  23. data/examples/zbatery.conf.rb +16 -0
  24. data/ext/raindrops/extconf.rb +163 -0
  25. data/ext/raindrops/linux_inet_diag.c +713 -0
  26. data/ext/raindrops/my_fileno.h +16 -0
  27. data/ext/raindrops/raindrops.c +487 -0
  28. data/ext/raindrops/raindrops_atomic.h +23 -0
  29. data/ext/raindrops/tcp_info.c +245 -0
  30. data/lib/raindrops/aggregate/last_data_recv.rb +94 -0
  31. data/lib/raindrops/aggregate/pmq.rb +245 -0
  32. data/lib/raindrops/aggregate.rb +8 -0
  33. data/lib/raindrops/last_data_recv.rb +102 -0
  34. data/lib/raindrops/linux.rb +77 -0
  35. data/lib/raindrops/middleware/proxy.rb +40 -0
  36. data/lib/raindrops/middleware.rb +153 -0
  37. data/lib/raindrops/struct.rb +62 -0
  38. data/lib/raindrops/watcher.rb +428 -0
  39. data/lib/raindrops.rb +72 -0
  40. data/pkg.mk +151 -0
  41. data/raindrops-maintained.gemspec +1 -0
  42. data/raindrops.gemspec +26 -0
  43. data/setup.rb +1586 -0
  44. data/test/ipv6_enabled.rb +9 -0
  45. data/test/rack_unicorn.rb +11 -0
  46. data/test/test_aggregate_pmq.rb +65 -0
  47. data/test/test_inet_diag_socket.rb +16 -0
  48. data/test/test_last_data_recv.rb +57 -0
  49. data/test/test_last_data_recv_unicorn.rb +69 -0
  50. data/test/test_linux.rb +281 -0
  51. data/test/test_linux_all_tcp_listen_stats.rb +66 -0
  52. data/test/test_linux_all_tcp_listen_stats_leak.rb +43 -0
  53. data/test/test_linux_ipv6.rb +166 -0
  54. data/test/test_linux_middleware.rb +64 -0
  55. data/test/test_linux_reuseport_tcp_listen_stats.rb +51 -0
  56. data/test/test_middleware.rb +128 -0
  57. data/test/test_middleware_unicorn.rb +37 -0
  58. data/test/test_middleware_unicorn_ipv6.rb +37 -0
  59. data/test/test_raindrops.rb +207 -0
  60. data/test/test_raindrops_gc.rb +38 -0
  61. data/test/test_struct.rb +54 -0
  62. data/test/test_tcp_info.rb +88 -0
  63. data/test/test_watcher.rb +186 -0
  64. 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