Skip to content

Commit c1b7dd7

Browse files
authored
fix: delay initialization to the first query (#364)
1 parent a3dc26c commit c1b7dd7

File tree

5 files changed

+47
-24
lines changed

5 files changed

+47
-24
lines changed

lib/redis_client/cluster.rb

Lines changed: 37 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -16,42 +16,47 @@ class Cluster
1616
def initialize(config, pool: nil, concurrency: nil, **kwargs)
1717
@config = config
1818
@concurrent_worker = ::RedisClient::Cluster::ConcurrentWorker.create(**(concurrency || {}))
19-
@router = ::RedisClient::Cluster::Router.new(config, @concurrent_worker, pool: pool, **kwargs)
2019
@command_builder = config.command_builder
20+
21+
@pool = pool
22+
@kwargs = kwargs
23+
@router = nil
24+
@mutex = Mutex.new
2125
end
2226

2327
def inspect
24-
"#<#{self.class.name} #{@router.node_keys.join(', ')}>"
28+
node_keys = @router.nil? ? @config.startup_nodes.keys : router.node_keys
29+
"#<#{self.class.name} #{node_keys.join(', ')}>"
2530
end
2631

2732
def call(*args, **kwargs, &block)
2833
command = @command_builder.generate(args, kwargs)
29-
@router.send_command(:call_v, command, &block)
34+
router.send_command(:call_v, command, &block)
3035
end
3136

3237
def call_v(command, &block)
3338
command = @command_builder.generate(command)
34-
@router.send_command(:call_v, command, &block)
39+
router.send_command(:call_v, command, &block)
3540
end
3641

3742
def call_once(*args, **kwargs, &block)
3843
command = @command_builder.generate(args, kwargs)
39-
@router.send_command(:call_once_v, command, &block)
44+
router.send_command(:call_once_v, command, &block)
4045
end
4146

4247
def call_once_v(command, &block)
4348
command = @command_builder.generate(command)
44-
@router.send_command(:call_once_v, command, &block)
49+
router.send_command(:call_once_v, command, &block)
4550
end
4651

4752
def blocking_call(timeout, *args, **kwargs, &block)
4853
command = @command_builder.generate(args, kwargs)
49-
@router.send_command(:blocking_call_v, command, timeout, &block)
54+
router.send_command(:blocking_call_v, command, timeout, &block)
5055
end
5156

5257
def blocking_call_v(timeout, command, &block)
5358
command = @command_builder.generate(command)
54-
@router.send_command(:blocking_call_v, command, timeout, &block)
59+
router.send_command(:blocking_call_v, command, timeout, &block)
5560
end
5661

5762
def scan(*args, **kwargs, &block)
@@ -60,31 +65,31 @@ def scan(*args, **kwargs, &block)
6065
seed = Random.new_seed
6166
cursor = ZERO_CURSOR_FOR_SCAN
6267
loop do
63-
cursor, keys = @router.scan('SCAN', cursor, *args, seed: seed, **kwargs)
68+
cursor, keys = router.scan('SCAN', cursor, *args, seed: seed, **kwargs)
6469
keys.each(&block)
6570
break if cursor == ZERO_CURSOR_FOR_SCAN
6671
end
6772
end
6873

6974
def sscan(key, *args, **kwargs, &block)
70-
node = @router.assign_node(['SSCAN', key])
71-
@router.try_delegate(node, :sscan, key, *args, **kwargs, &block)
75+
node = router.assign_node(['SSCAN', key])
76+
router.try_delegate(node, :sscan, key, *args, **kwargs, &block)
7277
end
7378

7479
def hscan(key, *args, **kwargs, &block)
75-
node = @router.assign_node(['HSCAN', key])
76-
@router.try_delegate(node, :hscan, key, *args, **kwargs, &block)
80+
node = router.assign_node(['HSCAN', key])
81+
router.try_delegate(node, :hscan, key, *args, **kwargs, &block)
7782
end
7883

7984
def zscan(key, *args, **kwargs, &block)
80-
node = @router.assign_node(['ZSCAN', key])
81-
@router.try_delegate(node, :zscan, key, *args, **kwargs, &block)
85+
node = router.assign_node(['ZSCAN', key])
86+
router.try_delegate(node, :zscan, key, *args, **kwargs, &block)
8287
end
8388

8489
def pipelined(exception: true)
8590
seed = @config.use_replica? && @config.replica_affinity == :random ? nil : Random.new_seed
8691
pipeline = ::RedisClient::Cluster::Pipeline.new(
87-
@router,
92+
router,
8893
@command_builder,
8994
@concurrent_worker,
9095
exception: exception,
@@ -99,48 +104,56 @@ def pipelined(exception: true)
99104

100105
def multi(watch: nil)
101106
if watch.nil? || watch.empty?
102-
transaction = ::RedisClient::Cluster::Transaction.new(@router, @command_builder)
107+
transaction = ::RedisClient::Cluster::Transaction.new(router, @command_builder)
103108
yield transaction
104109
return transaction.execute
105110
end
106111

107-
::RedisClient::Cluster::OptimisticLocking.new(@router).watch(watch) do |c, slot, asking|
112+
::RedisClient::Cluster::OptimisticLocking.new(router).watch(watch) do |c, slot, asking|
108113
transaction = ::RedisClient::Cluster::Transaction.new(
109-
@router, @command_builder, node: c, slot: slot, asking: asking
114+
router, @command_builder, node: c, slot: slot, asking: asking
110115
)
111116
yield transaction
112117
transaction.execute
113118
end
114119
end
115120

116121
def pubsub
117-
::RedisClient::Cluster::PubSub.new(@router, @command_builder)
122+
::RedisClient::Cluster::PubSub.new(router, @command_builder)
118123
end
119124

120125
def with(...)
121126
raise NotImplementedError, 'No way to use'
122127
end
123128

124129
def close
130+
@router&.close
125131
@concurrent_worker.close
126-
@router.close
127132
nil
128133
end
129134

130135
private
131136

137+
def router
138+
return @router unless @router.nil?
139+
140+
@mutex.synchronize do
141+
@router ||= ::RedisClient::Cluster::Router.new(@config, @concurrent_worker, pool: @pool, **@kwargs)
142+
end
143+
end
144+
132145
def method_missing(name, *args, **kwargs, &block)
133-
if @router.command_exists?(name)
146+
if router.command_exists?(name)
134147
args.unshift(name)
135148
command = @command_builder.generate(args, kwargs)
136-
return @router.send_command(:call_v, command, &block)
149+
return router.send_command(:call_v, command, &block)
137150
end
138151

139152
super
140153
end
141154

142155
def respond_to_missing?(name, include_private = false)
143-
return true if @router.command_exists?(name)
156+
return true if router.command_exists?(name)
144157

145158
super
146159
end

test/redis_client/test_cluster.rb

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -863,6 +863,13 @@ def test_only_reshards_own_errors
863863
client2.close
864864
end
865865

866+
def test_initialization_delayed
867+
config = ::RedisClient::ClusterConfig.new(nodes: 'redis://127.0.0.1:11211')
868+
client = ::RedisClient::Cluster.new(config)
869+
assert_instance_of(::RedisClient::Cluster, client)
870+
assert_raises(RedisClient::Cluster::InitialSetupError) { client.call('PING') }
871+
end
872+
866873
private
867874

868875
def wait_for_replication

test/test_against_cluster_broken.rb

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ def setup
1212
fixed_hostname: TEST_FIXED_HOSTNAME,
1313
**TEST_GENERIC_OPTIONS
1414
).new_client
15+
@client.call('echo', 'init')
1516
@controller = ClusterController.new(
1617
TEST_NODE_URIS,
1718
replica_size: TEST_REPLICA_SIZE,

test/test_against_cluster_scale.rb

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ def setup
1616
fixed_hostname: TEST_FIXED_HOSTNAME,
1717
**TEST_GENERIC_OPTIONS
1818
).new_client
19+
@client.call('echo', 'init')
1920
end
2021

2122
def teardown

test/test_against_cluster_state.rb

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ def setup
1414
)
1515
@controller.rebuild
1616
@client = new_test_client
17+
@client.call('echo', 'init')
1718
end
1819

1920
def teardown

0 commit comments

Comments
 (0)