redis-cluster-client 0.7.6 → 0.7.7

Sign up to get free protection for your applications and to get access to all the features.
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