Skip to content

Commit 123ba4b

Browse files
author
Taishi Kasuga
committed
Keep compatibilities as possible with a standalone client for the transaction feature in a cluster client
1 parent cdfe172 commit 123ba4b

File tree

5 files changed

+130
-24
lines changed

5 files changed

+130
-24
lines changed

cluster/lib/redis/cluster.rb

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -97,7 +97,7 @@ def cluster(subcommand, *args)
9797
end
9898

9999
def watch(*keys, &block)
100-
synchronize { |c| c.call_v([:watch] + keys, &block) }
100+
synchronize { |c| c.watch(*keys, &block) }
101101
end
102102

103103
private

cluster/lib/redis/cluster/client.rb

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

33
require 'redis-cluster-client'
4+
require 'redis/cluster/transaction_adapter'
45

56
class Redis
67
class Cluster
@@ -98,6 +99,22 @@ def multi(watch: nil, &block)
9899
handle_errors { super(watch: watch, &block) }
99100
end
100101

102+
def watch(*keys)
103+
unless block_given?
104+
raise Redis::Cluster::TransactionConsistencyError, 'A block is required if you use the cluster client.'
105+
end
106+
107+
handle_errors do
108+
RedisClient::Cluster::OptimisticLocking.new(@router).watch(keys) do |c, slot, asking|
109+
transaction = Redis::Cluster::TransactionAdapter.new(
110+
self, @router, @command_builder, node: c, slot: slot, asking: asking
111+
)
112+
yield transaction
113+
transaction.execute
114+
end
115+
end
116+
end
117+
101118
private
102119

103120
def handle_errors
Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
# frozen_string_literal: true
2+
3+
require 'redis-cluster-client'
4+
require 'redis_client/cluster/transaction'
5+
6+
class Redis
7+
class Cluster
8+
class TransactionAdapter < RedisClient::Cluster::Transaction
9+
def initialize(client, router, command_builder, node: nil, slot: nil, asking: false)
10+
@client = client
11+
super(router, command_builder, node: node, slot: slot, asking: asking)
12+
end
13+
14+
def multi
15+
yield self
16+
end
17+
18+
def exec
19+
# no need to do nothing
20+
end
21+
22+
def discard
23+
# no need to do nothing
24+
end
25+
26+
def watch(*_)
27+
# no need to do nothing
28+
end
29+
30+
def unwatch
31+
# no need to do nothing
32+
end
33+
34+
private
35+
36+
def method_missing(name, *args, **kwargs, &block)
37+
return call(name, *args, **kwargs, &block) if @client.respond_to?(name)
38+
39+
super
40+
end
41+
42+
def respond_to_missing?(name, include_private = false)
43+
return true if @client.respond_to?(name)
44+
45+
super
46+
end
47+
end
48+
end
49+
end

cluster/test/client_transactions_test.rb

Lines changed: 57 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -7,20 +7,20 @@ class TestClusterClientTransactions < Minitest::Test
77
include Helper::Cluster
88

99
def test_cluster_client_does_support_transaction_by_single_key
10-
actual = redis.multi do |r|
11-
r.set('counter', '0')
12-
r.incr('counter')
13-
r.incr('counter')
10+
actual = redis.multi do |tx|
11+
tx.set('counter', '0')
12+
tx.incr('counter')
13+
tx.incr('counter')
1414
end
1515

1616
assert_equal(['OK', 1, 2], actual)
1717
assert_equal('2', redis.get('counter'))
1818
end
1919

2020
def test_cluster_client_does_support_transaction_by_hashtag
21-
actual = redis.multi do |r|
22-
r.mset('{key}1', 1, '{key}2', 2)
23-
r.mset('{key}3', 3, '{key}4', 4)
21+
actual = redis.multi do |tx|
22+
tx.mset('{key}1', 1, '{key}2', 2)
23+
tx.mset('{key}3', 3, '{key}4', 4)
2424
end
2525

2626
assert_equal(%w[OK OK], actual)
@@ -29,18 +29,18 @@ def test_cluster_client_does_support_transaction_by_hashtag
2929

3030
def test_cluster_client_does_not_support_transaction_by_multiple_keys
3131
assert_raises(Redis::Cluster::TransactionConsistencyError) do
32-
redis.multi do |r|
33-
r.set('key1', 1)
34-
r.set('key2', 2)
35-
r.set('key3', 3)
36-
r.set('key4', 4)
32+
redis.multi do |tx|
33+
tx.set('key1', 1)
34+
tx.set('key2', 2)
35+
tx.set('key3', 3)
36+
tx.set('key4', 4)
3737
end
3838
end
3939

4040
assert_raises(Redis::Cluster::TransactionConsistencyError) do
41-
redis.multi do |r|
42-
r.mset('key1', 1, 'key2', 2)
43-
r.mset('key3', 3, 'key4', 4)
41+
redis.multi do |tx|
42+
tx.mset('key1', 1, 'key2', 2)
43+
tx.mset('key3', 3, 'key4', 4)
4444
end
4545
end
4646

@@ -63,10 +63,50 @@ def test_cluster_client_does_support_transaction_with_optimistic_locking
6363
another.resume
6464
v1 = redis.get('{key}1')
6565
v2 = redis.get('{key}2')
66-
tx.call('SET', '{key}1', v2)
67-
tx.call('SET', '{key}2', v1)
66+
tx.set('{key}1', v2)
67+
tx.set('{key}2', v1)
6868
end
6969

7070
assert_equal %w[3 4], redis.mget('{key}1', '{key}2')
7171
end
72+
73+
def test_cluster_client_can_be_used_compatible_with_standalone_client
74+
redis.set('{my}key', 'value')
75+
redis.set('{my}counter', '0')
76+
redis.watch('{my}key', '{my}counter') do |client|
77+
if redis.get('{my}key') == 'value'
78+
client.multi do |tx|
79+
tx.set('{my}key', 'updated value')
80+
tx.incr('{my}counter')
81+
end
82+
else
83+
client.unwatch
84+
end
85+
end
86+
87+
assert_equal('updated value', redis.get('{my}key'))
88+
assert_equal('1', redis.get('{my}counter'))
89+
90+
another = Fiber.new do
91+
cli = build_another_client
92+
cli.set('{my}key', 'another value')
93+
cli.close
94+
Fiber.yield
95+
end
96+
97+
redis.watch('{my}key', '{my}counter') do |client|
98+
another.resume
99+
if redis.get('{my}key') == 'value'
100+
client.multi do |tx|
101+
tx.set('{my}key', 'latest value')
102+
tx.incr('{my}counter')
103+
end
104+
else
105+
client.unwatch
106+
end
107+
end
108+
109+
assert_equal('another value', redis.get('{my}key'))
110+
assert_equal('1', redis.get('{my}counter'))
111+
end
72112
end

cluster/test/commands_on_transactions_test.rb

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -42,23 +42,23 @@ def test_watch
4242

4343
assert_raises(Redis::Cluster::TransactionConsistencyError) do
4444
redis.watch('key1', 'key2') do |tx|
45-
tx.call('SET', 'key1', '1')
46-
tx.call('SET', 'key2', '2')
45+
tx.set('key1', '1')
46+
tx.set('key2', '2')
4747
end
4848
end
4949

5050
assert_raises(Redis::Cluster::TransactionConsistencyError) do
5151
redis.watch('{hey}1', '{hey}2') do |tx|
52-
tx.call('SET', '{key}1', '1')
53-
tx.call('SET', '{key}2', '2')
52+
tx.set('{key}1', '1')
53+
tx.set('{key}2', '2')
5454
end
5555
end
5656

5757
assert_empty(redis.watch('{key}1', '{key}2') {})
5858

5959
redis.watch('{key}1', '{key}2') do |tx|
60-
tx.call('SET', '{key}1', '1')
61-
tx.call('SET', '{key}2', '2')
60+
tx.set('{key}1', '1')
61+
tx.set('{key}2', '2')
6262
end
6363

6464
assert_equal %w[1 2], redis.mget('{key}1', '{key}2')

0 commit comments

Comments
 (0)