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