redis-client 0.24.0 → 0.26.2
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 +4 -4
- data/CHANGELOG.md +42 -1
- data/README.md +23 -0
- data/lib/redis_client/circuit_breaker.rb +2 -1
- data/lib/redis_client/config.rb +10 -3
- data/lib/redis_client/connection_mixin.rb +22 -2
- data/lib/redis_client/pooled.rb +1 -1
- data/lib/redis_client/ruby_connection.rb +12 -10
- data/lib/redis_client/sentinel_config.rb +4 -0
- data/lib/redis_client/version.rb +1 -1
- data/lib/redis_client.rb +62 -5
- metadata +3 -3
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: f75c9f8b8e4542079642b30d284bd0fbda23d27249e7846bc65c0b002ce61732
|
|
4
|
+
data.tar.gz: cdc1d818e27551467b14e53692b5e61da762d1f21de78be00d6650830fdb9d13
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 15d23f1645fc7bc86c3f58ff0bfebcce62a3aec4783fd5434123277c0445a8d7553fea6f7fc89a36c23c433d7fcd5716b88a79d5131d3a3452d58707e61b965d
|
|
7
|
+
data.tar.gz: 7447f4f33e056072445d5d0dad650f3e648af69f1a0690505cb6913d525c32b29f5954349e0ca4852b3bd8d47434ada0e8e1ffde09f8966a664cff08acc5a443
|
data/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,46 @@
|
|
|
1
1
|
# Unreleased
|
|
2
2
|
|
|
3
|
+
# 0.26.2
|
|
4
|
+
|
|
5
|
+
- Fix compatibility with `connection_pool` version 3+.
|
|
6
|
+
|
|
7
|
+
# 0.26.1
|
|
8
|
+
|
|
9
|
+
- Fix a few corner cases where `RedisClient::Error#final?` was innacurate.
|
|
10
|
+
- hiredis-client: Properly reconnect to the new leader after a sentinel failover.
|
|
11
|
+
|
|
12
|
+
# 0.26.0
|
|
13
|
+
|
|
14
|
+
- Add `RedisClient::Error#final?` and `#retriable?` to allow middleware to filter out non-final errors.
|
|
15
|
+
- Fix precedence of `db: nil` initialization parameter.
|
|
16
|
+
|
|
17
|
+
```ruby
|
|
18
|
+
Redis.new(url: "redis://localhost:6379/3", db: nil).db
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
Before: `0`
|
|
22
|
+
After: `3`
|
|
23
|
+
|
|
24
|
+
# 0.25.3
|
|
25
|
+
|
|
26
|
+
- Fix `hiredis-client` compilation with `clang 21`.
|
|
27
|
+
|
|
28
|
+
# 0.25.2
|
|
29
|
+
|
|
30
|
+
- Fix circuit breakers to respect the `error_threshold_timeout` config is provided.
|
|
31
|
+
- Fix circuit breakers to clear errors when closing back.
|
|
32
|
+
|
|
33
|
+
# 0.25.1
|
|
34
|
+
|
|
35
|
+
- Fix Ruby driver TCP keep alive TTL. It was intended to be 120 seconds but was mistakenly set to 15 seconds.
|
|
36
|
+
|
|
37
|
+
# 0.25.0
|
|
38
|
+
|
|
39
|
+
- Fix `hiredis-client` compilation with GCC 15.
|
|
40
|
+
- Fix `hiredis-client` from a work directory with spaces.
|
|
41
|
+
- Add `CommandError#code`.
|
|
42
|
+
- Add `RedisClient::NoScriptError` for `EVALSHA`.
|
|
43
|
+
|
|
3
44
|
# 0.24.0
|
|
4
45
|
|
|
5
46
|
- Allow `sentinel_password` to be provided as a `Proc`.
|
|
@@ -185,7 +226,7 @@
|
|
|
185
226
|
- Added `_v` versions of `call` methods to make it easier to pass commands as arrays without splating.
|
|
186
227
|
- Fix calling `blocking_call` with a block in a pipeline.
|
|
187
228
|
- `blocking_call` now raise `ReadTimeoutError` if the command didn't complete in time.
|
|
188
|
-
- Fix `blocking_call` to not respect `
|
|
229
|
+
- Fix `blocking_call` to not respect `reconnect_attempts` on timeout.
|
|
189
230
|
- Stop parsing RESP3 sets as Ruby Set instances.
|
|
190
231
|
- Fix `SystemStackError` when parsing very large hashes. Fix: #30
|
|
191
232
|
- `hiredis` now more properly release the GVL when doing IOs.
|
data/README.md
CHANGED
|
@@ -459,6 +459,29 @@ RedisClient.register(MyGlobalRedisInstrumentation)
|
|
|
459
459
|
|
|
460
460
|
redis_config = RedisClient.config(custom: { tags: { "environment": Rails.env }})
|
|
461
461
|
```
|
|
462
|
+
|
|
463
|
+
### Instrumenting Errors
|
|
464
|
+
|
|
465
|
+
It is important to note that when `reconnect_attempts` is enabled, all network errors are reported to the middlewares,
|
|
466
|
+
even the ones that will be retried.
|
|
467
|
+
|
|
468
|
+
In many cases you may want to ignore retriable errors, or report them differently:
|
|
469
|
+
|
|
470
|
+
```ruby
|
|
471
|
+
module MyGlobalRedisInstrumentation
|
|
472
|
+
def call(command, redis_config)
|
|
473
|
+
super
|
|
474
|
+
rescue RedisClient::Error => error
|
|
475
|
+
if error.final?
|
|
476
|
+
# Error won't be retried.
|
|
477
|
+
else
|
|
478
|
+
# Error will be retried.
|
|
479
|
+
end
|
|
480
|
+
raise
|
|
481
|
+
end
|
|
482
|
+
end
|
|
483
|
+
```
|
|
484
|
+
|
|
462
485
|
### Timeouts
|
|
463
486
|
|
|
464
487
|
The client allows you to configure connect, read, and write timeouts.
|
|
@@ -79,7 +79,7 @@ class RedisClient
|
|
|
79
79
|
|
|
80
80
|
def record_error
|
|
81
81
|
now = RedisClient.now
|
|
82
|
-
expiry = now - @
|
|
82
|
+
expiry = now - @error_threshold_timeout
|
|
83
83
|
@lock.synchronize do
|
|
84
84
|
if @state == :closed
|
|
85
85
|
@errors.reject! { |t| t < expiry }
|
|
@@ -100,6 +100,7 @@ class RedisClient
|
|
|
100
100
|
|
|
101
101
|
@successes += 1
|
|
102
102
|
if @successes >= @success_threshold
|
|
103
|
+
@errors.clear
|
|
103
104
|
@state = :closed
|
|
104
105
|
end
|
|
105
106
|
end
|
data/lib/redis_client/config.rb
CHANGED
|
@@ -140,6 +140,10 @@ class RedisClient
|
|
|
140
140
|
@client_implementation.new(self, **kwargs)
|
|
141
141
|
end
|
|
142
142
|
|
|
143
|
+
def retriable?(attempt)
|
|
144
|
+
@reconnect_attempts && @reconnect_attempts[attempt]
|
|
145
|
+
end
|
|
146
|
+
|
|
143
147
|
def retry_connecting?(attempt, _error)
|
|
144
148
|
if @reconnect_attempts
|
|
145
149
|
if (pause = @reconnect_attempts[attempt])
|
|
@@ -182,7 +186,7 @@ class RedisClient
|
|
|
182
186
|
|
|
183
187
|
include Common
|
|
184
188
|
|
|
185
|
-
attr_reader :host, :port, :path
|
|
189
|
+
attr_reader :host, :port, :path, :server_key
|
|
186
190
|
|
|
187
191
|
def initialize(
|
|
188
192
|
url: nil,
|
|
@@ -191,14 +195,15 @@ class RedisClient
|
|
|
191
195
|
path: nil,
|
|
192
196
|
username: nil,
|
|
193
197
|
password: nil,
|
|
198
|
+
db: nil,
|
|
194
199
|
**kwargs
|
|
195
200
|
)
|
|
196
201
|
if url
|
|
197
202
|
url_config = URLConfig.new(url)
|
|
198
203
|
kwargs = {
|
|
199
204
|
ssl: url_config.ssl?,
|
|
200
|
-
db: url_config.db,
|
|
201
205
|
}.compact.merge(kwargs)
|
|
206
|
+
db ||= url_config.db
|
|
202
207
|
host ||= url_config.host
|
|
203
208
|
port ||= url_config.port
|
|
204
209
|
path ||= url_config.path
|
|
@@ -206,7 +211,7 @@ class RedisClient
|
|
|
206
211
|
password ||= url_config.password
|
|
207
212
|
end
|
|
208
213
|
|
|
209
|
-
super(username: username, password: password, **kwargs)
|
|
214
|
+
super(username: username, password: password, db: db, **kwargs)
|
|
210
215
|
|
|
211
216
|
if @path = path
|
|
212
217
|
@host = nil
|
|
@@ -215,6 +220,8 @@ class RedisClient
|
|
|
215
220
|
@host = host || DEFAULT_HOST
|
|
216
221
|
@port = Integer(port || DEFAULT_PORT)
|
|
217
222
|
end
|
|
223
|
+
|
|
224
|
+
@server_key = [@path, @host, @port].freeze
|
|
218
225
|
end
|
|
219
226
|
end
|
|
220
227
|
end
|
|
@@ -2,8 +2,14 @@
|
|
|
2
2
|
|
|
3
3
|
class RedisClient
|
|
4
4
|
module ConnectionMixin
|
|
5
|
-
|
|
5
|
+
attr_accessor :retry_attempt
|
|
6
|
+
attr_reader :config
|
|
7
|
+
|
|
8
|
+
def initialize(config)
|
|
6
9
|
@pending_reads = 0
|
|
10
|
+
@retry_attempt = nil
|
|
11
|
+
@config = config
|
|
12
|
+
@server_key = nil
|
|
7
13
|
end
|
|
8
14
|
|
|
9
15
|
def reconnect
|
|
@@ -17,7 +23,7 @@ class RedisClient
|
|
|
17
23
|
end
|
|
18
24
|
|
|
19
25
|
def revalidate
|
|
20
|
-
if @pending_reads > 0
|
|
26
|
+
if @pending_reads > 0 || @server_key != @config.server_key
|
|
21
27
|
close
|
|
22
28
|
false
|
|
23
29
|
else
|
|
@@ -33,6 +39,7 @@ class RedisClient
|
|
|
33
39
|
if result.is_a?(Error)
|
|
34
40
|
result._set_command(command)
|
|
35
41
|
result._set_config(config)
|
|
42
|
+
result._set_retry_attempt(@retry_attempt)
|
|
36
43
|
raise result
|
|
37
44
|
else
|
|
38
45
|
result
|
|
@@ -61,6 +68,7 @@ class RedisClient
|
|
|
61
68
|
elsif result.is_a?(Error)
|
|
62
69
|
result._set_command(commands[index])
|
|
63
70
|
result._set_config(config)
|
|
71
|
+
result._set_retry_attempt(@retry_attempt)
|
|
64
72
|
first_exception ||= result
|
|
65
73
|
end
|
|
66
74
|
|
|
@@ -82,5 +90,17 @@ class RedisClient
|
|
|
82
90
|
# to account for the network delay.
|
|
83
91
|
timeout + config.read_timeout
|
|
84
92
|
end
|
|
93
|
+
|
|
94
|
+
def protocol_error(message)
|
|
95
|
+
error = ProtocolError.with_config(message, config)
|
|
96
|
+
error._set_retry_attempt(@retry_attempt)
|
|
97
|
+
error
|
|
98
|
+
end
|
|
99
|
+
|
|
100
|
+
def connection_error(message)
|
|
101
|
+
error = ConnectionError.with_config(message, config)
|
|
102
|
+
error._set_retry_attempt(@retry_attempt)
|
|
103
|
+
error
|
|
104
|
+
end
|
|
85
105
|
end
|
|
86
106
|
end
|
data/lib/redis_client/pooled.rb
CHANGED
|
@@ -40,11 +40,8 @@ class RedisClient
|
|
|
40
40
|
|
|
41
41
|
SUPPORTS_RESOLV_TIMEOUT = Socket.method(:tcp).parameters.any? { |p| p.last == :resolv_timeout }
|
|
42
42
|
|
|
43
|
-
attr_reader :config
|
|
44
|
-
|
|
45
43
|
def initialize(config, connect_timeout:, read_timeout:, write_timeout:)
|
|
46
|
-
super()
|
|
47
|
-
@config = config
|
|
44
|
+
super(config)
|
|
48
45
|
@connect_timeout = connect_timeout
|
|
49
46
|
@read_timeout = read_timeout
|
|
50
47
|
@write_timeout = write_timeout
|
|
@@ -75,7 +72,11 @@ class RedisClient
|
|
|
75
72
|
begin
|
|
76
73
|
@io.write(buffer)
|
|
77
74
|
rescue SystemCallError, IOError, OpenSSL::SSL::SSLError => error
|
|
78
|
-
raise
|
|
75
|
+
raise connection_error(error.message)
|
|
76
|
+
rescue Error => error
|
|
77
|
+
error._set_config(config)
|
|
78
|
+
error._set_retry_attempt(@retry_attempt)
|
|
79
|
+
raise error
|
|
79
80
|
end
|
|
80
81
|
end
|
|
81
82
|
|
|
@@ -87,7 +88,7 @@ class RedisClient
|
|
|
87
88
|
begin
|
|
88
89
|
@io.write(buffer)
|
|
89
90
|
rescue SystemCallError, IOError, OpenSSL::SSL::SSLError => error
|
|
90
|
-
raise
|
|
91
|
+
raise connection_error(error.message)
|
|
91
92
|
end
|
|
92
93
|
end
|
|
93
94
|
|
|
@@ -97,10 +98,10 @@ class RedisClient
|
|
|
97
98
|
else
|
|
98
99
|
@io.with_timeout(timeout) { RESP3.load(@io) }
|
|
99
100
|
end
|
|
100
|
-
rescue RedisClient::RESP3::
|
|
101
|
-
raise
|
|
101
|
+
rescue RedisClient::RESP3::Error => error
|
|
102
|
+
raise protocol_error(error.message)
|
|
102
103
|
rescue SystemCallError, IOError, OpenSSL::SSL::SSLError => error
|
|
103
|
-
raise
|
|
104
|
+
raise connection_error(error.message)
|
|
104
105
|
end
|
|
105
106
|
|
|
106
107
|
def measure_round_trip_delay
|
|
@@ -112,6 +113,7 @@ class RedisClient
|
|
|
112
113
|
private
|
|
113
114
|
|
|
114
115
|
def connect
|
|
116
|
+
@server_key = @config.server_key
|
|
115
117
|
socket = if @config.path
|
|
116
118
|
UNIXSocket.new(@config.path)
|
|
117
119
|
else
|
|
@@ -169,7 +171,7 @@ class RedisClient
|
|
|
169
171
|
if %i[SOL_TCP SOL_SOCKET TCP_KEEPIDLE TCP_KEEPINTVL TCP_KEEPCNT].all? { |c| Socket.const_defined? c } # Linux
|
|
170
172
|
def enable_socket_keep_alive(socket)
|
|
171
173
|
socket.setsockopt(Socket::SOL_SOCKET, Socket::SO_KEEPALIVE, true)
|
|
172
|
-
socket.setsockopt(Socket::SOL_TCP, Socket::TCP_KEEPIDLE,
|
|
174
|
+
socket.setsockopt(Socket::SOL_TCP, Socket::TCP_KEEPIDLE, KEEP_ALIVE_TTL)
|
|
173
175
|
socket.setsockopt(Socket::SOL_TCP, Socket::TCP_KEEPINTVL, KEEP_ALIVE_INTERVAL)
|
|
174
176
|
socket.setsockopt(Socket::SOL_TCP, Socket::TCP_KEEPCNT, KEEP_ALIVE_PROBES)
|
|
175
177
|
end
|
data/lib/redis_client/version.rb
CHANGED
data/lib/redis_client.rb
CHANGED
|
@@ -99,13 +99,49 @@ class RedisClient
|
|
|
99
99
|
end
|
|
100
100
|
end
|
|
101
101
|
|
|
102
|
+
module Retriable
|
|
103
|
+
def _set_retry_attempt(retry_attempt)
|
|
104
|
+
@retry_attempt = retry_attempt
|
|
105
|
+
end
|
|
106
|
+
|
|
107
|
+
def retry_attempt
|
|
108
|
+
@retry_attempt || 0
|
|
109
|
+
end
|
|
110
|
+
|
|
111
|
+
def retriable?
|
|
112
|
+
!!@retry_attempt
|
|
113
|
+
end
|
|
114
|
+
|
|
115
|
+
def final?
|
|
116
|
+
!@retry_attempt
|
|
117
|
+
end
|
|
118
|
+
end
|
|
119
|
+
|
|
120
|
+
module Final
|
|
121
|
+
def _set_retry_attempt(_retry_attempt)
|
|
122
|
+
end
|
|
123
|
+
|
|
124
|
+
def retry_attempt
|
|
125
|
+
0
|
|
126
|
+
end
|
|
127
|
+
|
|
128
|
+
def retriable?
|
|
129
|
+
false
|
|
130
|
+
end
|
|
131
|
+
|
|
132
|
+
def final?
|
|
133
|
+
true
|
|
134
|
+
end
|
|
135
|
+
end
|
|
136
|
+
|
|
102
137
|
class Error < StandardError
|
|
103
138
|
include HasConfig
|
|
139
|
+
include Retriable
|
|
104
140
|
|
|
105
141
|
def self.with_config(message, config = nil)
|
|
106
|
-
new(message)
|
|
107
|
-
|
|
108
|
-
|
|
142
|
+
error = new(message)
|
|
143
|
+
error._set_config(config)
|
|
144
|
+
error
|
|
109
145
|
end
|
|
110
146
|
end
|
|
111
147
|
|
|
@@ -130,8 +166,19 @@ class RedisClient
|
|
|
130
166
|
end
|
|
131
167
|
end
|
|
132
168
|
|
|
169
|
+
module HasCode
|
|
170
|
+
attr_reader :code
|
|
171
|
+
|
|
172
|
+
def initialize(message = nil, code = nil)
|
|
173
|
+
super(message)
|
|
174
|
+
@code = code
|
|
175
|
+
end
|
|
176
|
+
end
|
|
177
|
+
|
|
133
178
|
class CommandError < Error
|
|
134
179
|
include HasCommand
|
|
180
|
+
include HasCode
|
|
181
|
+
include Final
|
|
135
182
|
|
|
136
183
|
class << self
|
|
137
184
|
def parse(error_message)
|
|
@@ -144,7 +191,7 @@ class RedisClient
|
|
|
144
191
|
end
|
|
145
192
|
code ||= error_message.split(' ', 2).first
|
|
146
193
|
klass = ERRORS.fetch(code, self)
|
|
147
|
-
klass.new(error_message.strip)
|
|
194
|
+
klass.new(error_message.strip, code.freeze)
|
|
148
195
|
end
|
|
149
196
|
end
|
|
150
197
|
end
|
|
@@ -153,12 +200,15 @@ class RedisClient
|
|
|
153
200
|
PermissionError = Class.new(CommandError)
|
|
154
201
|
WrongTypeError = Class.new(CommandError)
|
|
155
202
|
OutOfMemoryError = Class.new(CommandError)
|
|
203
|
+
NoScriptError = Class.new(CommandError)
|
|
156
204
|
|
|
157
205
|
ReadOnlyError = Class.new(ConnectionError)
|
|
158
206
|
ReadOnlyError.include(HasCommand)
|
|
207
|
+
ReadOnlyError.include(HasCode)
|
|
159
208
|
|
|
160
209
|
MasterDownError = Class.new(ConnectionError)
|
|
161
210
|
MasterDownError.include(HasCommand)
|
|
211
|
+
MasterDownError.include(HasCode)
|
|
162
212
|
|
|
163
213
|
CommandError::ERRORS = {
|
|
164
214
|
"WRONGPASS" => AuthenticationError,
|
|
@@ -167,6 +217,7 @@ class RedisClient
|
|
|
167
217
|
"MASTERDOWN" => MasterDownError,
|
|
168
218
|
"WRONGTYPE" => WrongTypeError,
|
|
169
219
|
"OOM" => OutOfMemoryError,
|
|
220
|
+
"NOSCRIPT" => NoScriptError,
|
|
170
221
|
}.freeze
|
|
171
222
|
|
|
172
223
|
class << self
|
|
@@ -198,6 +249,7 @@ class RedisClient
|
|
|
198
249
|
@middlewares = config.middlewares_stack.new(self)
|
|
199
250
|
@raw_connection = nil
|
|
200
251
|
@disable_reconnection = false
|
|
252
|
+
@retry_attempt = nil
|
|
201
253
|
end
|
|
202
254
|
|
|
203
255
|
def inspect
|
|
@@ -692,6 +744,7 @@ class RedisClient
|
|
|
692
744
|
close if !config.inherit_socket && @pid != PIDCache.pid
|
|
693
745
|
|
|
694
746
|
if @disable_reconnection
|
|
747
|
+
@raw_connection.retry_attempt = nil
|
|
695
748
|
if block_given?
|
|
696
749
|
yield @raw_connection
|
|
697
750
|
else
|
|
@@ -702,6 +755,7 @@ class RedisClient
|
|
|
702
755
|
connection = nil
|
|
703
756
|
preferred_error = nil
|
|
704
757
|
begin
|
|
758
|
+
@retry_attempt = config.retriable?(tries) ? tries : nil
|
|
705
759
|
connection = raw_connection
|
|
706
760
|
if block_given?
|
|
707
761
|
yield connection
|
|
@@ -730,6 +784,7 @@ class RedisClient
|
|
|
730
784
|
connection = ensure_connected
|
|
731
785
|
begin
|
|
732
786
|
@disable_reconnection = true
|
|
787
|
+
@raw_connection.retry_attempt = nil
|
|
733
788
|
yield connection
|
|
734
789
|
rescue ConnectionError, ProtocolError
|
|
735
790
|
close
|
|
@@ -744,13 +799,14 @@ class RedisClient
|
|
|
744
799
|
if @raw_connection.nil? || !@raw_connection.revalidate
|
|
745
800
|
connect
|
|
746
801
|
end
|
|
802
|
+
@raw_connection.retry_attempt = @retry_attempt
|
|
747
803
|
@raw_connection
|
|
748
804
|
end
|
|
749
805
|
|
|
750
806
|
def connect
|
|
751
807
|
@pid = PIDCache.pid
|
|
752
808
|
|
|
753
|
-
if @raw_connection
|
|
809
|
+
if @raw_connection&.revalidate
|
|
754
810
|
@middlewares.connect(config) do
|
|
755
811
|
@raw_connection.reconnect
|
|
756
812
|
end
|
|
@@ -764,6 +820,7 @@ class RedisClient
|
|
|
764
820
|
)
|
|
765
821
|
end
|
|
766
822
|
end
|
|
823
|
+
@raw_connection.retry_attempt = @retry_attempt
|
|
767
824
|
|
|
768
825
|
prelude = config.connection_prelude.dup
|
|
769
826
|
|
metadata
CHANGED
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: redis-client
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.
|
|
4
|
+
version: 0.26.2
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Jean Boussier
|
|
8
8
|
bindir: bin
|
|
9
9
|
cert_chain: []
|
|
10
|
-
date:
|
|
10
|
+
date: 1980-01-02 00:00:00.000000000 Z
|
|
11
11
|
dependencies:
|
|
12
12
|
- !ruby/object:Gem::Dependency
|
|
13
13
|
name: connection_pool
|
|
@@ -70,7 +70,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
|
70
70
|
- !ruby/object:Gem::Version
|
|
71
71
|
version: '0'
|
|
72
72
|
requirements: []
|
|
73
|
-
rubygems_version: 3.6.
|
|
73
|
+
rubygems_version: 3.6.9
|
|
74
74
|
specification_version: 4
|
|
75
75
|
summary: Simple low-level client for Redis 6+
|
|
76
76
|
test_files: []
|