redis-cluster-client 0.7.6 → 0.7.7

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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 071a90a4437e12104fba1359e1a88fd64a0aa253f53ec1fdfd5c8b1fa62c63a5
4
- data.tar.gz: 773243dc28547cc114730cbef7f0f971dd7bb178d4ea9a8cb2e8c4924f99bf40
3
+ metadata.gz: 158cfc3693b4057ae42249cbddcca2e5bd53bb19bb62416e44f1d1358b55c711
4
+ data.tar.gz: 59917a893698b9d0fec7235821e4f9680b9e3ab4ebfd74554c48c50455a87186
5
5
  SHA512:
6
- metadata.gz: d345690443bc35cf19935ae9e886e0833bee49b772f21abdfb4752629b7c436100e6a367b730055fbd155d59924cfd4d76ad3df2ee1a0ed3aff65f097a82a208
7
- data.tar.gz: d76e747b3eec56184f253f40f82599f492338fe86d4d3c3d30e9cda22df087e6817673b7bf9f02c07192b77322efd7d204ff60ced77be7fe2e8764cf9d104f63
6
+ metadata.gz: 116c7f397e557fc0b6a336e3481cc3e5458bac590afe454ecbfbfcafddc67ea3641f9cd020ab7b9f2cb41f3fe54188a13d4324b9496e43a27b1f58f97163363d
7
+ data.tar.gz: e7ada90bdb11f0d113093792b627853a429d182c1e531d6c968fff530bbd34ed8df76655fd40223b91bc3c5d6524316302fa435438e731a8de1dcb0bc5c4ad24
@@ -31,6 +31,10 @@ class RedisClient
31
31
  def build_from_host_port(host, port)
32
32
  "#{host}#{DELIMITER}#{port}"
33
33
  end
34
+
35
+ def build_from_client(client)
36
+ "#{client.config.host}#{DELIMITER}#{client.config.port}"
37
+ end
34
38
  end
35
39
  end
36
40
  end
@@ -0,0 +1,48 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'redis_client'
4
+ require 'redis_client/cluster/key_slot_converter'
5
+ require 'redis_client/cluster/transaction'
6
+
7
+ class RedisClient
8
+ class Cluster
9
+ class OptimisticLocking
10
+ def initialize(keys, router)
11
+ @node = find_node!(keys, router)
12
+ @keys = keys
13
+ end
14
+
15
+ def watch
16
+ @node.with do |c|
17
+ c.call('WATCH', *@keys)
18
+ reply = yield(c)
19
+ c.call('UNWATCH')
20
+ reply
21
+ end
22
+ end
23
+
24
+ private
25
+
26
+ def find_node!(keys, router)
27
+ raise ::RedisClient::Cluster::Transaction::ConsistencyError, "unsafe watch: #{keys.join(' ')}" unless safe?(keys)
28
+
29
+ node_key = router.find_primary_node_key(['WATCH', *keys])
30
+ raise ::RedisClient::Cluster::Transaction::ConsistencyError, "couldn't determine the node" if node_key.nil?
31
+
32
+ router.find_node(node_key)
33
+ end
34
+
35
+ def safe?(keys)
36
+ return false if keys.empty?
37
+
38
+ slots = keys.map do |k|
39
+ return false if k.nil? || k.empty?
40
+
41
+ ::RedisClient::Cluster::KeySlotConverter.convert(k)
42
+ end
43
+
44
+ slots.uniq.size == 1
45
+ end
46
+ end
47
+ end
48
+ end
@@ -2,21 +2,21 @@
2
2
 
3
3
  require 'redis_client'
4
4
  require 'redis_client/cluster/pipeline'
5
- require 'redis_client/cluster/key_slot_converter'
5
+ require 'redis_client/cluster/node_key'
6
6
 
7
7
  class RedisClient
8
8
  class Cluster
9
9
  class Transaction
10
10
  ConsistencyError = Class.new(::RedisClient::Error)
11
11
 
12
- def initialize(router, command_builder, watch)
12
+ def initialize(router, command_builder, node = nil)
13
13
  @router = router
14
14
  @command_builder = command_builder
15
- @watch = watch
16
15
  @retryable = true
17
16
  @pipeline = ::RedisClient::Pipeline.new(@command_builder)
18
- @buffer = []
19
- @node = nil
17
+ @pending_commands = []
18
+ @node = node
19
+ prepare_tx unless @node.nil?
20
20
  end
21
21
 
22
22
  def call(*command, **kwargs, &block)
@@ -24,7 +24,7 @@ class RedisClient
24
24
  if prepare(command)
25
25
  @pipeline.call_v(command, &block)
26
26
  else
27
- @buffer << -> { @pipeline.call_v(command, &block) }
27
+ defer { @pipeline.call_v(command, &block) }
28
28
  end
29
29
  end
30
30
 
@@ -33,7 +33,7 @@ class RedisClient
33
33
  if prepare(command)
34
34
  @pipeline.call_v(command, &block)
35
35
  else
36
- @buffer << -> { @pipeline.call_v(command, &block) }
36
+ defer { @pipeline.call_v(command, &block) }
37
37
  end
38
38
  end
39
39
 
@@ -43,7 +43,7 @@ class RedisClient
43
43
  if prepare(command)
44
44
  @pipeline.call_once_v(command, &block)
45
45
  else
46
- @buffer << -> { @pipeline.call_once_v(command, &block) }
46
+ defer { @pipeline.call_once_v(command, &block) }
47
47
  end
48
48
  end
49
49
 
@@ -53,39 +53,24 @@ class RedisClient
53
53
  if prepare(command)
54
54
  @pipeline.call_once_v(command, &block)
55
55
  else
56
- @buffer << -> { @pipeline.call_once_v(command, &block) }
56
+ defer { @pipeline.call_once_v(command, &block) }
57
57
  end
58
58
  end
59
59
 
60
60
  def execute
61
- @buffer.each(&:call)
61
+ @pending_commands.each(&:call)
62
62
 
63
63
  raise ArgumentError, 'empty transaction' if @pipeline._empty?
64
64
  raise ConsistencyError, "couldn't determine the node: #{@pipeline._commands}" if @node.nil?
65
- raise ConsistencyError, "unsafe watch: #{@watch.join(' ')}" unless safe_watch?
66
65
 
67
66
  settle
68
67
  end
69
68
 
70
69
  private
71
70
 
72
- def watch?
73
- !@watch.nil? && !@watch.empty?
74
- end
75
-
76
- def safe_watch?
77
- return true unless watch?
78
- return false if @node.nil?
79
-
80
- slots = @watch.map do |k|
81
- return false if k.nil? || k.empty?
82
-
83
- ::RedisClient::Cluster::KeySlotConverter.convert(k)
84
- end
85
-
86
- return false if slots.uniq.size != 1
87
-
88
- @router.find_primary_node_by_slot(slots.first) == @node
71
+ def defer(&block)
72
+ @pending_commands << block
73
+ nil
89
74
  end
90
75
 
91
76
  def prepare(command)
@@ -95,16 +80,18 @@ class RedisClient
95
80
  return false if node_key.nil?
96
81
 
97
82
  @node = @router.find_node(node_key)
98
- @pipeline.call('WATCH', *@watch) if watch?
99
- @pipeline.call('MULTI')
100
- @buffer.each(&:call)
101
- @buffer.clear
83
+ prepare_tx
102
84
  true
103
85
  end
104
86
 
87
+ def prepare_tx
88
+ @pipeline.call('MULTI')
89
+ @pending_commands.each(&:call)
90
+ @pending_commands.clear
91
+ end
92
+
105
93
  def settle
106
94
  @pipeline.call('EXEC')
107
- @pipeline.call('UNWATCH') if watch?
108
95
  send_transaction(@node, redirect: true)
109
96
  end
110
97
 
@@ -128,11 +115,12 @@ class RedisClient
128
115
  end
129
116
  end
130
117
 
131
- offset = watch? ? 2 : 1
132
- coerce_results!(replies[-offset], offset)
118
+ return if replies.last.nil?
119
+
120
+ coerce_results!(replies.last)
133
121
  end
134
122
 
135
- def coerce_results!(results, offset)
123
+ def coerce_results!(results, offset: 1)
136
124
  results.each_with_index do |result, index|
137
125
  if result.is_a?(::RedisClient::CommandError)
138
126
  result._set_command(@pipeline._commands[index + offset])
@@ -162,12 +150,12 @@ class RedisClient
162
150
  end
163
151
 
164
152
  def ensure_the_same_node!(commands)
153
+ expected_node_key = NodeKey.build_from_client(@node)
154
+
165
155
  commands.each do |command|
166
156
  node_key = @router.find_primary_node_key(command)
167
157
  next if node_key.nil?
168
-
169
- node = @router.find_node(node_key)
170
- next if @node == node
158
+ next if node_key == expected_node_key
171
159
 
172
160
  raise ConsistencyError, "the transaction should be executed to a slot in a node: #{commands}"
173
161
  end
@@ -6,6 +6,7 @@ require 'redis_client/cluster/pub_sub'
6
6
  require 'redis_client/cluster/router'
7
7
  require 'redis_client/cluster/transaction'
8
8
  require 'redis_client/cluster/pinning_node'
9
+ require 'redis_client/cluster/optimistic_locking'
9
10
 
10
11
  class RedisClient
11
12
  class Cluster
@@ -91,9 +92,18 @@ class RedisClient
91
92
  end
92
93
 
93
94
  def multi(watch: nil)
94
- transaction = ::RedisClient::Cluster::Transaction.new(@router, @command_builder, watch)
95
- yield transaction
96
- transaction.execute
95
+ if watch.nil? || watch.empty?
96
+ transaction = ::RedisClient::Cluster::Transaction.new(@router, @command_builder)
97
+ yield transaction
98
+ transaction.execute
99
+ else
100
+ locking = ::RedisClient::Cluster::OptimisticLocking.new(watch, @router)
101
+ locking.watch do |c|
102
+ transaction = ::RedisClient::Cluster::Transaction.new(@router, @command_builder, c)
103
+ yield transaction
104
+ transaction.execute
105
+ end
106
+ end
97
107
  end
98
108
 
99
109
  def pubsub
@@ -102,12 +112,11 @@ class RedisClient
102
112
 
103
113
  # TODO: This isn't an official public interface yet. Don't use in your production environment.
104
114
  # @see https://github.com/redis-rb/redis-cluster-client/issues/299
105
- def with(key: nil, hashtag: nil, write: true, _retry_count: 0, &_)
115
+ def with(key: nil, hashtag: nil, write: true)
106
116
  key = process_with_arguments(key, hashtag)
107
-
108
117
  node_key = @router.find_node_key_by_key(key, primary: write)
109
118
  node = @router.find_node(node_key)
110
- yield ::RedisClient::Cluster::PinningNode.new(node)
119
+ node.with { |c| yield ::RedisClient::Cluster::PinningNode.new(c) }
111
120
  end
112
121
 
113
122
  def close
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: redis-cluster-client
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.7.6
4
+ version: 0.7.7
5
5
  platform: ruby
6
6
  authors:
7
7
  - Taishi Kasuga
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2024-02-17 00:00:00.000000000 Z
11
+ date: 2024-02-18 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: redis-client
@@ -49,6 +49,7 @@ files:
49
49
  - lib/redis_client/cluster/node/random_replica_or_primary.rb
50
50
  - lib/redis_client/cluster/node_key.rb
51
51
  - lib/redis_client/cluster/normalized_cmd_name.rb
52
+ - lib/redis_client/cluster/optimistic_locking.rb
52
53
  - lib/redis_client/cluster/pinning_node.rb
53
54
  - lib/redis_client/cluster/pipeline.rb
54
55
  - lib/redis_client/cluster/pub_sub.rb