Skip to content

Commit 3de8f27

Browse files
authored
test: add concurrency test (#138)
1 parent cabda86 commit 3de8f27

File tree

2 files changed

+107
-6
lines changed

2 files changed

+107
-6
lines changed

test/benchmark_mixin.rb

Lines changed: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
# frozen_string_literal: true
22

33
module BenchmarkMixin
4+
MIN_THRESHOLD = 0.95
5+
46
def setup
57
@client = new_test_client
68
@client.call('FLUSHDB')
@@ -14,31 +16,31 @@ def teardown
1416
end
1517

1618
def bench_echo
17-
assert_performance_linear do |n|
19+
assert_performance_linear(MIN_THRESHOLD) do |n|
1820
n.times do
1921
@client.call('ECHO', 'Hello world')
2022
end
2123
end
2224
end
2325

2426
def bench_set
25-
assert_performance_linear do |n|
27+
assert_performance_linear(MIN_THRESHOLD) do |n|
2628
n.times do |i|
2729
@client.call('SET', "key#{i}", i)
2830
end
2931
end
3032
end
3133

3234
def bench_get
33-
assert_performance_linear do |n|
35+
assert_performance_linear(MIN_THRESHOLD) do |n|
3436
n.times do |i|
3537
@client.call('GET', "key#{i}")
3638
end
3739
end
3840
end
3941

4042
def bench_pipeline_echo
41-
assert_performance_linear do |n|
43+
assert_performance_linear(MIN_THRESHOLD) do |n|
4244
@client.pipelined do |pi|
4345
n.times do
4446
pi.call('ECHO', 'Hello world')
@@ -48,7 +50,7 @@ def bench_pipeline_echo
4850
end
4951

5052
def bench_pipeline_set
51-
assert_performance_linear do |n|
53+
assert_performance_linear(MIN_THRESHOLD) do |n|
5254
@client.pipelined do |pi|
5355
n.times do |i|
5456
pi.call('SET', "key#{i}", i)
@@ -58,7 +60,7 @@ def bench_pipeline_set
5860
end
5961

6062
def bench_pipeline_get
61-
assert_performance_linear do |n|
63+
assert_performance_linear(MIN_THRESHOLD) do |n|
6264
@client.pipelined do |pi|
6365
n.times do |i|
6466
pi.call('GET', "key#{i}")

test/test_concurrency.rb

Lines changed: 99 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,99 @@
1+
# frozen_string_literal: true
2+
3+
require 'testing_helper'
4+
5+
class TestConcurrency < TestingWrapper
6+
MAX_THREADS = Integer(ENV.fetch('REDIS_CLIENT_MAX_THREADS', 5))
7+
ATTEMPTS = 200
8+
WANT = '1'
9+
10+
def setup
11+
@client = new_test_client
12+
MAX_THREADS.times { |i| @client.call('SET', "key#{i}", WANT) }
13+
end
14+
15+
def teardown
16+
@client.close
17+
end
18+
19+
def test_forking
20+
pids = Array.new(MAX_THREADS) do
21+
Process.fork do
22+
ATTEMPTS.times { MAX_THREADS.times { |i| @client.call('INCR', "key#{i}") } }
23+
sleep 0.1
24+
ATTEMPTS.times { MAX_THREADS.times { |i| @client.call('DECR', "key#{i}") } }
25+
end
26+
end
27+
28+
pids.each do |pid|
29+
_, status = Process.waitpid2(pid)
30+
assert_predicate(status, :success?)
31+
end
32+
33+
MAX_THREADS.times { |i| assert_equal(WANT, @client.call('GET', "key#{i}")) }
34+
end
35+
36+
def test_forking_with_pipelining
37+
pids = Array.new(MAX_THREADS) do
38+
Process.fork do
39+
@client.pipelined { |pi| ATTEMPTS.times { MAX_THREADS.times { |i| pi.call('INCR', "key#{i}") } } }
40+
sleep 0.1
41+
@client.pipelined { |pi| ATTEMPTS.times { MAX_THREADS.times { |i| pi.call('DECR', "key#{i}") } } }
42+
end
43+
end
44+
45+
pids.each do |pid|
46+
_, status = Process.waitpid2(pid)
47+
assert_predicate(status, :success?)
48+
end
49+
50+
MAX_THREADS.times { |i| assert_equal(WANT, @client.call('GET', "key#{i}")) }
51+
end
52+
53+
def test_threading
54+
threads = Array.new(MAX_THREADS) do
55+
Thread.new do
56+
Thread.pass
57+
ATTEMPTS.times { MAX_THREADS.times { |i| @client.call('INCR', "key#{i}") } }
58+
Thread.pass
59+
ATTEMPTS.times { MAX_THREADS.times { |i| @client.call('DECR', "key#{i}") } }
60+
rescue StandardError => e
61+
Thread.current.thread_variable_set(:error, e)
62+
end
63+
end
64+
65+
threads.each(&:join)
66+
threads.each { |t| assert_nil(t.thread_variable_get(:error)) }
67+
MAX_THREADS.times { |i| assert_equal(WANT, @client.call('GET', "key#{i}")) }
68+
end
69+
70+
def test_threading_with_pipelining
71+
threads = Array.new(MAX_THREADS) do
72+
Thread.new do
73+
Thread.pass
74+
@client.pipelined { |pi| ATTEMPTS.times { MAX_THREADS.times { |i| pi.call('INCR', "key#{i}") } } }
75+
Thread.pass
76+
@client.pipelined { |pi| ATTEMPTS.times { MAX_THREADS.times { |i| pi.call('DECR', "key#{i}") } } }
77+
rescue StandardError => e
78+
Thread.current.thread_variable_set(:error, e)
79+
end
80+
end
81+
82+
threads.each(&:join)
83+
threads.each { |t| assert_nil(t.thread_variable_get(:error)) }
84+
MAX_THREADS.times { |i| assert_equal(WANT, @client.call('GET', "key#{i}")) }
85+
end
86+
87+
private
88+
89+
def new_test_client
90+
::RedisClient.cluster(
91+
nodes: TEST_NODE_URIS,
92+
fixed_hostname: TEST_FIXED_HOSTNAME,
93+
**TEST_GENERIC_OPTIONS
94+
).new_pool(
95+
timeout: TEST_TIMEOUT_SEC,
96+
size: MAX_THREADS
97+
)
98+
end
99+
end

0 commit comments

Comments
 (0)