redis 4.1.3 → 4.2.3
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 +41 -0
- data/README.md +10 -5
- data/lib/redis.rb +388 -348
- data/lib/redis/client.rb +66 -69
- data/lib/redis/cluster.rb +10 -4
- data/lib/redis/cluster/node.rb +3 -0
- data/lib/redis/cluster/node_key.rb +3 -7
- data/lib/redis/cluster/option.rb +27 -14
- data/lib/redis/cluster/slot.rb +30 -13
- data/lib/redis/cluster/slot_loader.rb +4 -4
- data/lib/redis/connection.rb +2 -0
- data/lib/redis/connection/command_helper.rb +3 -2
- data/lib/redis/connection/hiredis.rb +4 -3
- data/lib/redis/connection/registry.rb +2 -1
- data/lib/redis/connection/ruby.rb +90 -96
- data/lib/redis/connection/synchrony.rb +9 -4
- data/lib/redis/distributed.rb +109 -57
- data/lib/redis/errors.rb +2 -0
- data/lib/redis/hash_ring.rb +15 -14
- data/lib/redis/pipeline.rb +16 -3
- data/lib/redis/subscribe.rb +11 -12
- data/lib/redis/version.rb +3 -1
- metadata +14 -9
data/lib/redis/cluster/slot.rb
CHANGED
@@ -1,7 +1,5 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
require 'set'
|
4
|
-
|
5
3
|
class Redis
|
6
4
|
class Cluster
|
7
5
|
# Keep slot and node key map for Redis Cluster Client
|
@@ -28,11 +26,20 @@ class Redis
|
|
28
26
|
return nil unless exists?(slot)
|
29
27
|
return find_node_key_of_master(slot) if replica_disabled?
|
30
28
|
|
31
|
-
@map[slot][:slaves].
|
29
|
+
@map[slot][:slaves].sample
|
32
30
|
end
|
33
31
|
|
34
32
|
def put(slot, node_key)
|
35
|
-
|
33
|
+
# Since we're sharing a hash for build_slot_node_key_map, duplicate it
|
34
|
+
# if it already exists instead of preserving as-is.
|
35
|
+
@map[slot] = @map[slot] ? @map[slot].dup : { master: nil, slaves: [] }
|
36
|
+
|
37
|
+
if master?(node_key)
|
38
|
+
@map[slot][:master] = node_key
|
39
|
+
elsif !@map[slot][:slaves].include?(node_key)
|
40
|
+
@map[slot][:slaves] << node_key
|
41
|
+
end
|
42
|
+
|
36
43
|
nil
|
37
44
|
end
|
38
45
|
|
@@ -50,19 +57,29 @@ class Redis
|
|
50
57
|
@node_flags[node_key] == ROLE_SLAVE
|
51
58
|
end
|
52
59
|
|
60
|
+
# available_slots is mapping of node_key to list of slot ranges
|
53
61
|
def build_slot_node_key_map(available_slots)
|
54
|
-
|
55
|
-
|
62
|
+
by_ranges = {}
|
63
|
+
available_slots.each do |node_key, slots_arr|
|
64
|
+
by_ranges[slots_arr] ||= { master: nil, slaves: [] }
|
65
|
+
|
66
|
+
if master?(node_key)
|
67
|
+
by_ranges[slots_arr][:master] = node_key
|
68
|
+
elsif !by_ranges[slots_arr][:slaves].include?(node_key)
|
69
|
+
by_ranges[slots_arr][:slaves] << node_key
|
70
|
+
end
|
56
71
|
end
|
57
|
-
end
|
58
72
|
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
73
|
+
by_slot = {}
|
74
|
+
by_ranges.each do |slots_arr, nodes|
|
75
|
+
slots_arr.each do |slots|
|
76
|
+
slots.each do |slot|
|
77
|
+
by_slot[slot] = nodes
|
78
|
+
end
|
79
|
+
end
|
65
80
|
end
|
81
|
+
|
82
|
+
by_slot
|
66
83
|
end
|
67
84
|
end
|
68
85
|
end
|
@@ -13,7 +13,7 @@ class Redis
|
|
13
13
|
info = {}
|
14
14
|
|
15
15
|
nodes.each do |node|
|
16
|
-
info =
|
16
|
+
info = fetch_slot_info(node)
|
17
17
|
info.empty? ? next : break
|
18
18
|
end
|
19
19
|
|
@@ -23,9 +23,10 @@ class Redis
|
|
23
23
|
end
|
24
24
|
|
25
25
|
def fetch_slot_info(node)
|
26
|
+
hash_with_default_arr = Hash.new { |h, k| h[k] = [] }
|
26
27
|
node.call(%i[cluster slots])
|
27
|
-
.
|
28
|
-
.
|
28
|
+
.flat_map { |arr| parse_slot_info(arr, default_ip: node.host) }
|
29
|
+
.each_with_object(hash_with_default_arr) { |arr, h| h[arr[0]] << arr[1] }
|
29
30
|
rescue CannotConnectError, ConnectionError, CommandError
|
30
31
|
{} # can retry on another node
|
31
32
|
end
|
@@ -34,7 +35,6 @@ class Redis
|
|
34
35
|
first_slot, last_slot = arr[0..1]
|
35
36
|
slot_range = (first_slot..last_slot).freeze
|
36
37
|
arr[2..-1].map { |addr| [stringify_node_key(addr, default_ip), slot_range] }
|
37
|
-
.flatten
|
38
38
|
end
|
39
39
|
|
40
40
|
def stringify_node_key(arr, default_ip)
|
data/lib/redis/connection.rb
CHANGED
@@ -1,7 +1,8 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
class Redis
|
2
4
|
module Connection
|
3
5
|
module CommandHelper
|
4
|
-
|
5
6
|
COMMAND_DELIMITER = "\r\n"
|
6
7
|
|
7
8
|
def build_command(args)
|
@@ -28,7 +29,7 @@ class Redis
|
|
28
29
|
command.join(COMMAND_DELIMITER)
|
29
30
|
end
|
30
31
|
|
31
|
-
|
32
|
+
protected
|
32
33
|
|
33
34
|
def encode(string)
|
34
35
|
string.force_encoding(Encoding.default_external)
|
@@ -1,3 +1,5 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
require_relative "registry"
|
2
4
|
require_relative "../errors"
|
3
5
|
require "hiredis/connection"
|
@@ -6,7 +8,6 @@ require "timeout"
|
|
6
8
|
class Redis
|
7
9
|
module Connection
|
8
10
|
class Hiredis
|
9
|
-
|
10
11
|
def self.connect(config)
|
11
12
|
connection = ::Hiredis::Connection.new
|
12
13
|
connect_timeout = (config.fetch(:connect_timeout, 0) * 1_000_000).to_i
|
@@ -31,7 +32,7 @@ class Redis
|
|
31
32
|
end
|
32
33
|
|
33
34
|
def connected?
|
34
|
-
@connection
|
35
|
+
@connection&.connected?
|
35
36
|
end
|
36
37
|
|
37
38
|
def timeout=(timeout)
|
@@ -57,7 +58,7 @@ class Redis
|
|
57
58
|
rescue Errno::EAGAIN
|
58
59
|
raise TimeoutError
|
59
60
|
rescue RuntimeError => err
|
60
|
-
raise ProtocolError
|
61
|
+
raise ProtocolError, err.message
|
61
62
|
end
|
62
63
|
end
|
63
64
|
end
|
@@ -1,6 +1,7 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
class Redis
|
2
4
|
module Connection
|
3
|
-
|
4
5
|
# Store a list of loaded connection drivers in the Connection module.
|
5
6
|
# Redis::Client uses the last required driver by default, and will be aware
|
6
7
|
# of the loaded connection drivers if the user chooses to override the
|
@@ -1,3 +1,5 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
require_relative "registry"
|
2
4
|
require_relative "command_helper"
|
3
5
|
require_relative "../errors"
|
@@ -13,8 +15,7 @@ end
|
|
13
15
|
class Redis
|
14
16
|
module Connection
|
15
17
|
module SocketMixin
|
16
|
-
|
17
|
-
CRLF = "\r\n".freeze
|
18
|
+
CRLF = "\r\n"
|
18
19
|
|
19
20
|
def initialize(*args)
|
20
21
|
super(*args)
|
@@ -24,84 +25,71 @@ class Redis
|
|
24
25
|
end
|
25
26
|
|
26
27
|
def timeout=(timeout)
|
27
|
-
if timeout && timeout > 0
|
28
|
-
@timeout = timeout
|
29
|
-
else
|
30
|
-
@timeout = nil
|
31
|
-
end
|
28
|
+
@timeout = (timeout if timeout && timeout > 0)
|
32
29
|
end
|
33
30
|
|
34
31
|
def write_timeout=(timeout)
|
35
|
-
if timeout && timeout > 0
|
36
|
-
@write_timeout = timeout
|
37
|
-
else
|
38
|
-
@write_timeout = nil
|
39
|
-
end
|
32
|
+
@write_timeout = (timeout if timeout && timeout > 0)
|
40
33
|
end
|
41
34
|
|
42
35
|
def read(nbytes)
|
43
36
|
result = @buffer.slice!(0, nbytes)
|
44
37
|
|
45
|
-
while result.bytesize < nbytes
|
46
|
-
result << _read_from_socket(nbytes - result.bytesize)
|
47
|
-
end
|
38
|
+
result << _read_from_socket(nbytes - result.bytesize) while result.bytesize < nbytes
|
48
39
|
|
49
40
|
result
|
50
41
|
end
|
51
42
|
|
52
43
|
def gets
|
53
|
-
crlf = nil
|
54
|
-
|
55
|
-
while (crlf = @buffer.index(CRLF)) == nil
|
56
|
-
@buffer << _read_from_socket(1024)
|
44
|
+
while (crlf = @buffer.index(CRLF)).nil?
|
45
|
+
@buffer << _read_from_socket(16_384)
|
57
46
|
end
|
58
47
|
|
59
48
|
@buffer.slice!(0, crlf + CRLF.bytesize)
|
60
49
|
end
|
61
50
|
|
62
51
|
def _read_from_socket(nbytes)
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
raise Redis::TimeoutError
|
52
|
+
loop do
|
53
|
+
case chunk = read_nonblock(nbytes, exception: false)
|
54
|
+
when :wait_readable
|
55
|
+
unless wait_readable(@timeout)
|
56
|
+
raise Redis::TimeoutError
|
57
|
+
end
|
58
|
+
when :wait_writable
|
59
|
+
unless wait_writable(@timeout)
|
60
|
+
raise Redis::TimeoutError
|
61
|
+
end
|
62
|
+
when nil
|
63
|
+
raise Errno::ECONNRESET
|
64
|
+
when String
|
65
|
+
return chunk
|
78
66
|
end
|
79
67
|
end
|
80
|
-
|
81
|
-
rescue EOFError
|
82
|
-
raise Errno::ECONNRESET
|
83
68
|
end
|
84
69
|
|
85
70
|
def _write_to_socket(data)
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
|
94
|
-
|
95
|
-
|
96
|
-
|
97
|
-
|
98
|
-
|
99
|
-
|
71
|
+
total_bytes_written = 0
|
72
|
+
loop do
|
73
|
+
case bytes_written = write_nonblock(data, exception: false)
|
74
|
+
when :wait_readable
|
75
|
+
unless wait_readable(@write_timeout)
|
76
|
+
raise Redis::TimeoutError
|
77
|
+
end
|
78
|
+
when :wait_writable
|
79
|
+
unless wait_writable(@write_timeout)
|
80
|
+
raise Redis::TimeoutError
|
81
|
+
end
|
82
|
+
when nil
|
83
|
+
raise Errno::ECONNRESET
|
84
|
+
when Integer
|
85
|
+
total_bytes_written += bytes_written
|
86
|
+
if bytes_written < data.bytesize
|
87
|
+
data.slice!(0, bytes_written)
|
88
|
+
else
|
89
|
+
return total_bytes_written
|
90
|
+
end
|
100
91
|
end
|
101
92
|
end
|
102
|
-
|
103
|
-
rescue EOFError
|
104
|
-
raise Errno::ECONNRESET
|
105
93
|
end
|
106
94
|
|
107
95
|
def write(data)
|
@@ -114,6 +102,7 @@ class Redis
|
|
114
102
|
|
115
103
|
total_count += count
|
116
104
|
return total_count if total_count >= length
|
105
|
+
|
117
106
|
data = data.byteslice(count..-1)
|
118
107
|
end
|
119
108
|
end
|
@@ -124,7 +113,6 @@ class Redis
|
|
124
113
|
require "timeout"
|
125
114
|
|
126
115
|
class TCPSocket < ::TCPSocket
|
127
|
-
|
128
116
|
include SocketMixin
|
129
117
|
|
130
118
|
def self.connect(host, port, timeout)
|
@@ -140,7 +128,6 @@ class Redis
|
|
140
128
|
if defined?(::UNIXSocket)
|
141
129
|
|
142
130
|
class UNIXSocket < ::UNIXSocket
|
143
|
-
|
144
131
|
include SocketMixin
|
145
132
|
|
146
133
|
def self.connect(path, timeout)
|
@@ -152,13 +139,12 @@ class Redis
|
|
152
139
|
raise TimeoutError
|
153
140
|
end
|
154
141
|
|
155
|
-
# JRuby raises Errno::EAGAIN on #read_nonblock even when
|
142
|
+
# JRuby raises Errno::EAGAIN on #read_nonblock even when it
|
156
143
|
# says it is readable (1.6.6, in both 1.8 and 1.9 mode).
|
157
144
|
# Use the blocking #readpartial method instead.
|
158
145
|
|
159
146
|
def _read_from_socket(nbytes)
|
160
147
|
readpartial(nbytes)
|
161
|
-
|
162
148
|
rescue EOFError
|
163
149
|
raise Errno::ECONNRESET
|
164
150
|
end
|
@@ -169,19 +155,16 @@ class Redis
|
|
169
155
|
else
|
170
156
|
|
171
157
|
class TCPSocket < ::Socket
|
172
|
-
|
173
158
|
include SocketMixin
|
174
159
|
|
175
|
-
def self.connect_addrinfo(
|
176
|
-
sock = new(::Socket.const_get(
|
177
|
-
sockaddr = ::Socket.pack_sockaddr_in(port,
|
160
|
+
def self.connect_addrinfo(addrinfo, port, timeout)
|
161
|
+
sock = new(::Socket.const_get(addrinfo[0]), Socket::SOCK_STREAM, 0)
|
162
|
+
sockaddr = ::Socket.pack_sockaddr_in(port, addrinfo[3])
|
178
163
|
|
179
164
|
begin
|
180
165
|
sock.connect_nonblock(sockaddr)
|
181
166
|
rescue Errno::EINPROGRESS
|
182
|
-
|
183
|
-
raise TimeoutError
|
184
|
-
end
|
167
|
+
raise TimeoutError unless sock.wait_writable(timeout)
|
185
168
|
|
186
169
|
begin
|
187
170
|
sock.connect_nonblock(sockaddr)
|
@@ -220,14 +203,13 @@ class Redis
|
|
220
203
|
return connect_addrinfo(ai, port, timeout)
|
221
204
|
rescue SystemCallError
|
222
205
|
# Raise if this was our last attempt.
|
223
|
-
raise if addrinfo.length == i+1
|
206
|
+
raise if addrinfo.length == i + 1
|
224
207
|
end
|
225
208
|
end
|
226
209
|
end
|
227
210
|
end
|
228
211
|
|
229
212
|
class UNIXSocket < ::Socket
|
230
|
-
|
231
213
|
include SocketMixin
|
232
214
|
|
233
215
|
def self.connect(path, timeout)
|
@@ -237,9 +219,7 @@ class Redis
|
|
237
219
|
begin
|
238
220
|
sock.connect_nonblock(sockaddr)
|
239
221
|
rescue Errno::EINPROGRESS
|
240
|
-
|
241
|
-
raise TimeoutError
|
242
|
-
end
|
222
|
+
raise TimeoutError unless sock.wait_writable(timeout)
|
243
223
|
|
244
224
|
begin
|
245
225
|
sock.connect_nonblock(sockaddr)
|
@@ -257,12 +237,26 @@ class Redis
|
|
257
237
|
class SSLSocket < ::OpenSSL::SSL::SSLSocket
|
258
238
|
include SocketMixin
|
259
239
|
|
240
|
+
unless method_defined?(:wait_readable)
|
241
|
+
def wait_readable(timeout = nil)
|
242
|
+
to_io.wait_readable(timeout)
|
243
|
+
end
|
244
|
+
end
|
245
|
+
|
246
|
+
unless method_defined?(:wait_writable)
|
247
|
+
def wait_writable(timeout = nil)
|
248
|
+
to_io.wait_writable(timeout)
|
249
|
+
end
|
250
|
+
end
|
251
|
+
|
260
252
|
def self.connect(host, port, timeout, ssl_params)
|
261
253
|
# Note: this is using Redis::Connection::TCPSocket
|
262
254
|
tcp_sock = TCPSocket.connect(host, port, timeout)
|
263
255
|
|
264
256
|
ctx = OpenSSL::SSL::SSLContext.new
|
265
|
-
|
257
|
+
|
258
|
+
# The provided parameters are merged into OpenSSL::SSL::SSLContext::DEFAULT_PARAMS
|
259
|
+
ctx.set_params(ssl_params || {})
|
266
260
|
|
267
261
|
ssl_sock = new(tcp_sock, ctx)
|
268
262
|
ssl_sock.hostname = host
|
@@ -276,20 +270,23 @@ class Redis
|
|
276
270
|
# Instead, you have to retry.
|
277
271
|
ssl_sock.connect_nonblock
|
278
272
|
rescue Errno::EAGAIN, Errno::EWOULDBLOCK, IO::WaitReadable
|
279
|
-
if
|
273
|
+
if ssl_sock.wait_readable(timeout)
|
280
274
|
retry
|
281
275
|
else
|
282
276
|
raise TimeoutError
|
283
277
|
end
|
284
278
|
rescue IO::WaitWritable
|
285
|
-
if
|
279
|
+
if ssl_sock.wait_writable(timeout)
|
286
280
|
retry
|
287
281
|
else
|
288
282
|
raise TimeoutError
|
289
283
|
end
|
290
284
|
end
|
291
285
|
|
292
|
-
unless ctx.verify_mode == OpenSSL::SSL::VERIFY_NONE || (
|
286
|
+
unless ctx.verify_mode == OpenSSL::SSL::VERIFY_NONE || (
|
287
|
+
ctx.respond_to?(:verify_hostname) &&
|
288
|
+
!ctx.verify_hostname
|
289
|
+
)
|
293
290
|
ssl_sock.post_connection_check(host)
|
294
291
|
end
|
295
292
|
|
@@ -301,15 +298,16 @@ class Redis
|
|
301
298
|
class Ruby
|
302
299
|
include Redis::Connection::CommandHelper
|
303
300
|
|
304
|
-
MINUS = "-"
|
305
|
-
PLUS = "+"
|
306
|
-
COLON = ":"
|
307
|
-
DOLLAR = "$"
|
308
|
-
ASTERISK = "*"
|
301
|
+
MINUS = "-"
|
302
|
+
PLUS = "+"
|
303
|
+
COLON = ":"
|
304
|
+
DOLLAR = "$"
|
305
|
+
ASTERISK = "*"
|
309
306
|
|
310
307
|
def self.connect(config)
|
311
308
|
if config[:scheme] == "unix"
|
312
309
|
raise ArgumentError, "SSL incompatible with unix sockets" if config[:ssl]
|
310
|
+
|
313
311
|
sock = UNIXSocket.connect(config[:path], config[:connect_timeout])
|
314
312
|
elsif config[:scheme] == "rediss" || config[:ssl]
|
315
313
|
sock = SSLSocket.connect(config[:host], config[:port], config[:connect_timeout], config[:ssl_params])
|
@@ -325,7 +323,7 @@ class Redis
|
|
325
323
|
instance
|
326
324
|
end
|
327
325
|
|
328
|
-
if [
|
326
|
+
if %i[SOL_SOCKET SO_KEEPALIVE SOL_TCP TCP_KEEPIDLE TCP_KEEPINTVL TCP_KEEPCNT].all? { |c| Socket.const_defined? c }
|
329
327
|
def set_tcp_keepalive(keepalive)
|
330
328
|
return unless keepalive.is_a?(Hash)
|
331
329
|
|
@@ -337,14 +335,13 @@ class Redis
|
|
337
335
|
|
338
336
|
def get_tcp_keepalive
|
339
337
|
{
|
340
|
-
:
|
341
|
-
:
|
342
|
-
:
|
338
|
+
time: @sock.getsockopt(Socket::SOL_TCP, Socket::TCP_KEEPIDLE).int,
|
339
|
+
intvl: @sock.getsockopt(Socket::SOL_TCP, Socket::TCP_KEEPINTVL).int,
|
340
|
+
probes: @sock.getsockopt(Socket::SOL_TCP, Socket::TCP_KEEPCNT).int
|
343
341
|
}
|
344
342
|
end
|
345
343
|
else
|
346
|
-
def set_tcp_keepalive(keepalive)
|
347
|
-
end
|
344
|
+
def set_tcp_keepalive(keepalive); end
|
348
345
|
|
349
346
|
def get_tcp_keepalive
|
350
347
|
{
|
@@ -353,13 +350,12 @@ class Redis
|
|
353
350
|
end
|
354
351
|
|
355
352
|
# disables Nagle's Algorithm, prevents multiple round trips with MULTI
|
356
|
-
if [
|
357
|
-
def set_tcp_nodelay
|
358
|
-
@sock.setsockopt(Socket::IPPROTO_TCP, Socket::TCP_NODELAY, 1)
|
359
|
-
end
|
360
|
-
else
|
353
|
+
if %i[IPPROTO_TCP TCP_NODELAY].all? { |c| Socket.const_defined? c }
|
361
354
|
def set_tcp_nodelay
|
355
|
+
@sock.setsockopt(Socket::IPPROTO_TCP, Socket::TCP_NODELAY, 1)
|
362
356
|
end
|
357
|
+
else
|
358
|
+
def set_tcp_nodelay; end
|
363
359
|
end
|
364
360
|
|
365
361
|
def initialize(sock)
|
@@ -367,7 +363,7 @@ class Redis
|
|
367
363
|
end
|
368
364
|
|
369
365
|
def connected?
|
370
|
-
|
366
|
+
!!@sock
|
371
367
|
end
|
372
368
|
|
373
369
|
def disconnect
|
@@ -378,9 +374,7 @@ class Redis
|
|
378
374
|
end
|
379
375
|
|
380
376
|
def timeout=(timeout)
|
381
|
-
if @sock.respond_to?(:timeout=)
|
382
|
-
@sock.timeout = timeout
|
383
|
-
end
|
377
|
+
@sock.timeout = timeout if @sock.respond_to?(:timeout=)
|
384
378
|
end
|
385
379
|
|
386
380
|
def write_timeout=(timeout)
|
@@ -395,7 +389,6 @@ class Redis
|
|
395
389
|
line = @sock.gets
|
396
390
|
reply_type = line.slice!(0, 1)
|
397
391
|
format_reply(reply_type, line)
|
398
|
-
|
399
392
|
rescue Errno::EAGAIN
|
400
393
|
raise TimeoutError
|
401
394
|
end
|
@@ -407,7 +400,7 @@ class Redis
|
|
407
400
|
when COLON then format_integer_reply(line)
|
408
401
|
when DOLLAR then format_bulk_reply(line)
|
409
402
|
when ASTERISK then format_multi_bulk_reply(line)
|
410
|
-
else raise ProtocolError
|
403
|
+
else raise ProtocolError, reply_type
|
411
404
|
end
|
412
405
|
end
|
413
406
|
|
@@ -426,6 +419,7 @@ class Redis
|
|
426
419
|
def format_bulk_reply(line)
|
427
420
|
bulklen = line.to_i
|
428
421
|
return if bulklen == -1
|
422
|
+
|
429
423
|
reply = encode(@sock.read(bulklen))
|
430
424
|
@sock.read(2) # Discard CRLF.
|
431
425
|
reply
|