raindrops-maintained 0.21.0

Sign up to get free protection for your applications and to get access to all the features.
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