redis 4.1.1 → 4.2.5
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 +5 -5
- data/CHANGELOG.md +69 -0
- data/README.md +24 -5
- data/lib/redis/client.rb +71 -73
- 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/cluster.rb +13 -4
- 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 +118 -103
- data/lib/redis/connection/synchrony.rb +9 -4
- data/lib/redis/connection.rb +2 -0
- data/lib/redis/distributed.rb +117 -62
- 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
- data/lib/redis.rb +394 -354
- metadata +15 -25
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/cluster.rb
CHANGED
@@ -80,6 +80,7 @@ class Redis
|
|
80
80
|
def call_pipeline(pipeline)
|
81
81
|
node_keys, command_keys = extract_keys_in_pipeline(pipeline)
|
82
82
|
raise CrossSlotPipeliningError, command_keys if node_keys.size > 1
|
83
|
+
|
83
84
|
node = find_node(node_keys.first)
|
84
85
|
try_send(node, :call_pipeline, pipeline)
|
85
86
|
end
|
@@ -112,12 +113,11 @@ class Redis
|
|
112
113
|
node = Node.new(option.per_node_key)
|
113
114
|
available_slots = SlotLoader.load(node)
|
114
115
|
node_flags = NodeLoader.load_flags(node)
|
115
|
-
|
116
|
-
option.update_node(available_node_urls)
|
116
|
+
option.update_node(available_slots.keys.map { |k| NodeKey.optionize(k) })
|
117
117
|
[Node.new(option.per_node_key, node_flags, option.use_replica?),
|
118
118
|
Slot.new(available_slots, node_flags, option.use_replica?)]
|
119
119
|
ensure
|
120
|
-
node
|
120
|
+
node&.each(&:disconnect)
|
121
121
|
end
|
122
122
|
|
123
123
|
def fetch_command_details(nodes)
|
@@ -216,9 +216,14 @@ class Redis
|
|
216
216
|
node.public_send(method_name, *args, &block)
|
217
217
|
rescue CommandError => err
|
218
218
|
if err.message.start_with?('MOVED')
|
219
|
-
|
219
|
+
raise if retry_count <= 0
|
220
|
+
|
221
|
+
node = assign_redirection_node(err.message)
|
222
|
+
retry_count -= 1
|
223
|
+
retry
|
220
224
|
elsif err.message.start_with?('ASK')
|
221
225
|
raise if retry_count <= 0
|
226
|
+
|
222
227
|
node = assign_asking_node(err.message)
|
223
228
|
node.call(%i[asking])
|
224
229
|
retry_count -= 1
|
@@ -226,6 +231,9 @@ class Redis
|
|
226
231
|
else
|
227
232
|
raise
|
228
233
|
end
|
234
|
+
rescue CannotConnectError
|
235
|
+
update_cluster_info!
|
236
|
+
raise
|
229
237
|
end
|
230
238
|
|
231
239
|
def assign_redirection_node(err_msg)
|
@@ -261,6 +269,7 @@ class Redis
|
|
261
269
|
|
262
270
|
def find_node(node_key)
|
263
271
|
return @node.sample if node_key.nil?
|
272
|
+
|
264
273
|
@node.find_by(node_key)
|
265
274
|
rescue Node::ReloadNeeded
|
266
275
|
update_cluster_info!(node_key)
|
@@ -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,97 +25,74 @@ 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
|
78
|
-
end
|
79
|
-
end
|
80
|
-
|
81
|
-
rescue EOFError
|
82
|
-
raise Errno::ECONNRESET
|
83
|
-
end
|
84
|
-
|
85
|
-
def _write_to_socket(data)
|
86
|
-
begin
|
87
|
-
write_nonblock(data)
|
88
|
-
|
89
|
-
rescue IO::WaitWritable
|
90
|
-
if IO.select(nil, [self], nil, @write_timeout)
|
91
|
-
retry
|
92
|
-
else
|
93
|
-
raise Redis::TimeoutError
|
94
|
-
end
|
95
|
-
rescue IO::WaitReadable
|
96
|
-
if IO.select([self], nil, nil, @write_timeout)
|
97
|
-
retry
|
98
|
-
else
|
99
|
-
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
|
100
66
|
end
|
101
67
|
end
|
102
|
-
|
103
|
-
rescue EOFError
|
104
|
-
raise Errno::ECONNRESET
|
105
68
|
end
|
106
69
|
|
107
|
-
def write(
|
108
|
-
return super(
|
70
|
+
def write(buffer)
|
71
|
+
return super(buffer) unless @write_timeout
|
109
72
|
|
110
|
-
|
111
|
-
|
73
|
+
bytes_to_write = buffer.bytesize
|
74
|
+
total_bytes_written = 0
|
112
75
|
loop do
|
113
|
-
|
76
|
+
case bytes_written = write_nonblock(buffer, exception: false)
|
77
|
+
when :wait_readable
|
78
|
+
unless wait_readable(@write_timeout)
|
79
|
+
raise Redis::TimeoutError
|
80
|
+
end
|
81
|
+
when :wait_writable
|
82
|
+
unless wait_writable(@write_timeout)
|
83
|
+
raise Redis::TimeoutError
|
84
|
+
end
|
85
|
+
when nil
|
86
|
+
raise Errno::ECONNRESET
|
87
|
+
when Integer
|
88
|
+
total_bytes_written += bytes_written
|
89
|
+
|
90
|
+
if total_bytes_written >= bytes_to_write
|
91
|
+
return total_bytes_written
|
92
|
+
end
|
114
93
|
|
115
|
-
|
116
|
-
|
117
|
-
data = data.byteslice(count..-1)
|
94
|
+
buffer = buffer.byteslice(bytes_written..-1)
|
95
|
+
end
|
118
96
|
end
|
119
97
|
end
|
120
98
|
end
|
@@ -124,7 +102,6 @@ class Redis
|
|
124
102
|
require "timeout"
|
125
103
|
|
126
104
|
class TCPSocket < ::TCPSocket
|
127
|
-
|
128
105
|
include SocketMixin
|
129
106
|
|
130
107
|
def self.connect(host, port, timeout)
|
@@ -140,7 +117,6 @@ class Redis
|
|
140
117
|
if defined?(::UNIXSocket)
|
141
118
|
|
142
119
|
class UNIXSocket < ::UNIXSocket
|
143
|
-
|
144
120
|
include SocketMixin
|
145
121
|
|
146
122
|
def self.connect(path, timeout)
|
@@ -152,13 +128,12 @@ class Redis
|
|
152
128
|
raise TimeoutError
|
153
129
|
end
|
154
130
|
|
155
|
-
# JRuby raises Errno::EAGAIN on #read_nonblock even when
|
131
|
+
# JRuby raises Errno::EAGAIN on #read_nonblock even when it
|
156
132
|
# says it is readable (1.6.6, in both 1.8 and 1.9 mode).
|
157
133
|
# Use the blocking #readpartial method instead.
|
158
134
|
|
159
135
|
def _read_from_socket(nbytes)
|
160
136
|
readpartial(nbytes)
|
161
|
-
|
162
137
|
rescue EOFError
|
163
138
|
raise Errno::ECONNRESET
|
164
139
|
end
|
@@ -169,19 +144,16 @@ class Redis
|
|
169
144
|
else
|
170
145
|
|
171
146
|
class TCPSocket < ::Socket
|
172
|
-
|
173
147
|
include SocketMixin
|
174
148
|
|
175
|
-
def self.connect_addrinfo(
|
176
|
-
sock = new(::Socket.const_get(
|
177
|
-
sockaddr = ::Socket.pack_sockaddr_in(port,
|
149
|
+
def self.connect_addrinfo(addrinfo, port, timeout)
|
150
|
+
sock = new(::Socket.const_get(addrinfo[0]), Socket::SOCK_STREAM, 0)
|
151
|
+
sockaddr = ::Socket.pack_sockaddr_in(port, addrinfo[3])
|
178
152
|
|
179
153
|
begin
|
180
154
|
sock.connect_nonblock(sockaddr)
|
181
155
|
rescue Errno::EINPROGRESS
|
182
|
-
|
183
|
-
raise TimeoutError
|
184
|
-
end
|
156
|
+
raise TimeoutError unless sock.wait_writable(timeout)
|
185
157
|
|
186
158
|
begin
|
187
159
|
sock.connect_nonblock(sockaddr)
|
@@ -220,14 +192,13 @@ class Redis
|
|
220
192
|
return connect_addrinfo(ai, port, timeout)
|
221
193
|
rescue SystemCallError
|
222
194
|
# Raise if this was our last attempt.
|
223
|
-
raise if addrinfo.length == i+1
|
195
|
+
raise if addrinfo.length == i + 1
|
224
196
|
end
|
225
197
|
end
|
226
198
|
end
|
227
199
|
end
|
228
200
|
|
229
201
|
class UNIXSocket < ::Socket
|
230
|
-
|
231
202
|
include SocketMixin
|
232
203
|
|
233
204
|
def self.connect(path, timeout)
|
@@ -237,9 +208,7 @@ class Redis
|
|
237
208
|
begin
|
238
209
|
sock.connect_nonblock(sockaddr)
|
239
210
|
rescue Errno::EINPROGRESS
|
240
|
-
|
241
|
-
raise TimeoutError
|
242
|
-
end
|
211
|
+
raise TimeoutError unless sock.wait_writable(timeout)
|
243
212
|
|
244
213
|
begin
|
245
214
|
sock.connect_nonblock(sockaddr)
|
@@ -257,18 +226,56 @@ class Redis
|
|
257
226
|
class SSLSocket < ::OpenSSL::SSL::SSLSocket
|
258
227
|
include SocketMixin
|
259
228
|
|
229
|
+
unless method_defined?(:wait_readable)
|
230
|
+
def wait_readable(timeout = nil)
|
231
|
+
to_io.wait_readable(timeout)
|
232
|
+
end
|
233
|
+
end
|
234
|
+
|
235
|
+
unless method_defined?(:wait_writable)
|
236
|
+
def wait_writable(timeout = nil)
|
237
|
+
to_io.wait_writable(timeout)
|
238
|
+
end
|
239
|
+
end
|
240
|
+
|
260
241
|
def self.connect(host, port, timeout, ssl_params)
|
261
242
|
# Note: this is using Redis::Connection::TCPSocket
|
262
243
|
tcp_sock = TCPSocket.connect(host, port, timeout)
|
263
244
|
|
264
245
|
ctx = OpenSSL::SSL::SSLContext.new
|
265
|
-
|
246
|
+
|
247
|
+
# The provided parameters are merged into OpenSSL::SSL::SSLContext::DEFAULT_PARAMS
|
248
|
+
ctx.set_params(ssl_params || {})
|
266
249
|
|
267
250
|
ssl_sock = new(tcp_sock, ctx)
|
268
251
|
ssl_sock.hostname = host
|
269
|
-
ssl_sock.connect
|
270
252
|
|
271
|
-
|
253
|
+
begin
|
254
|
+
# Initiate the socket connection in the background. If it doesn't fail
|
255
|
+
# immediately it will raise an IO::WaitWritable (Errno::EINPROGRESS)
|
256
|
+
# indicating the connection is in progress.
|
257
|
+
# Unlike waiting for a tcp socket to connect, you can't time out ssl socket
|
258
|
+
# connections during the connect phase properly, because IO.select only partially works.
|
259
|
+
# Instead, you have to retry.
|
260
|
+
ssl_sock.connect_nonblock
|
261
|
+
rescue Errno::EAGAIN, Errno::EWOULDBLOCK, IO::WaitReadable
|
262
|
+
if ssl_sock.wait_readable(timeout)
|
263
|
+
retry
|
264
|
+
else
|
265
|
+
raise TimeoutError
|
266
|
+
end
|
267
|
+
rescue IO::WaitWritable
|
268
|
+
if ssl_sock.wait_writable(timeout)
|
269
|
+
retry
|
270
|
+
else
|
271
|
+
raise TimeoutError
|
272
|
+
end
|
273
|
+
end
|
274
|
+
|
275
|
+
unless ctx.verify_mode == OpenSSL::SSL::VERIFY_NONE || (
|
276
|
+
ctx.respond_to?(:verify_hostname) &&
|
277
|
+
!ctx.verify_hostname
|
278
|
+
)
|
272
279
|
ssl_sock.post_connection_check(host)
|
273
280
|
end
|
274
281
|
|
@@ -280,15 +287,16 @@ class Redis
|
|
280
287
|
class Ruby
|
281
288
|
include Redis::Connection::CommandHelper
|
282
289
|
|
283
|
-
MINUS = "-"
|
284
|
-
PLUS = "+"
|
285
|
-
COLON = ":"
|
286
|
-
DOLLAR = "$"
|
287
|
-
ASTERISK = "*"
|
290
|
+
MINUS = "-"
|
291
|
+
PLUS = "+"
|
292
|
+
COLON = ":"
|
293
|
+
DOLLAR = "$"
|
294
|
+
ASTERISK = "*"
|
288
295
|
|
289
296
|
def self.connect(config)
|
290
297
|
if config[:scheme] == "unix"
|
291
298
|
raise ArgumentError, "SSL incompatible with unix sockets" if config[:ssl]
|
299
|
+
|
292
300
|
sock = UNIXSocket.connect(config[:path], config[:connect_timeout])
|
293
301
|
elsif config[:scheme] == "rediss" || config[:ssl]
|
294
302
|
sock = SSLSocket.connect(config[:host], config[:port], config[:connect_timeout], config[:ssl_params])
|
@@ -300,10 +308,11 @@ class Redis
|
|
300
308
|
instance.timeout = config[:read_timeout]
|
301
309
|
instance.write_timeout = config[:write_timeout]
|
302
310
|
instance.set_tcp_keepalive config[:tcp_keepalive]
|
311
|
+
instance.set_tcp_nodelay if sock.is_a? TCPSocket
|
303
312
|
instance
|
304
313
|
end
|
305
314
|
|
306
|
-
if [
|
315
|
+
if %i[SOL_SOCKET SO_KEEPALIVE SOL_TCP TCP_KEEPIDLE TCP_KEEPINTVL TCP_KEEPCNT].all? { |c| Socket.const_defined? c }
|
307
316
|
def set_tcp_keepalive(keepalive)
|
308
317
|
return unless keepalive.is_a?(Hash)
|
309
318
|
|
@@ -315,14 +324,13 @@ class Redis
|
|
315
324
|
|
316
325
|
def get_tcp_keepalive
|
317
326
|
{
|
318
|
-
:
|
319
|
-
:
|
320
|
-
:
|
327
|
+
time: @sock.getsockopt(Socket::SOL_TCP, Socket::TCP_KEEPIDLE).int,
|
328
|
+
intvl: @sock.getsockopt(Socket::SOL_TCP, Socket::TCP_KEEPINTVL).int,
|
329
|
+
probes: @sock.getsockopt(Socket::SOL_TCP, Socket::TCP_KEEPCNT).int
|
321
330
|
}
|
322
331
|
end
|
323
332
|
else
|
324
|
-
def set_tcp_keepalive(keepalive)
|
325
|
-
end
|
333
|
+
def set_tcp_keepalive(keepalive); end
|
326
334
|
|
327
335
|
def get_tcp_keepalive
|
328
336
|
{
|
@@ -330,12 +338,21 @@ class Redis
|
|
330
338
|
end
|
331
339
|
end
|
332
340
|
|
341
|
+
# disables Nagle's Algorithm, prevents multiple round trips with MULTI
|
342
|
+
if %i[IPPROTO_TCP TCP_NODELAY].all? { |c| Socket.const_defined? c }
|
343
|
+
def set_tcp_nodelay
|
344
|
+
@sock.setsockopt(Socket::IPPROTO_TCP, Socket::TCP_NODELAY, 1)
|
345
|
+
end
|
346
|
+
else
|
347
|
+
def set_tcp_nodelay; end
|
348
|
+
end
|
349
|
+
|
333
350
|
def initialize(sock)
|
334
351
|
@sock = sock
|
335
352
|
end
|
336
353
|
|
337
354
|
def connected?
|
338
|
-
|
355
|
+
!!@sock
|
339
356
|
end
|
340
357
|
|
341
358
|
def disconnect
|
@@ -346,9 +363,7 @@ class Redis
|
|
346
363
|
end
|
347
364
|
|
348
365
|
def timeout=(timeout)
|
349
|
-
if @sock.respond_to?(:timeout=)
|
350
|
-
@sock.timeout = timeout
|
351
|
-
end
|
366
|
+
@sock.timeout = timeout if @sock.respond_to?(:timeout=)
|
352
367
|
end
|
353
368
|
|
354
369
|
def write_timeout=(timeout)
|
@@ -363,7 +378,6 @@ class Redis
|
|
363
378
|
line = @sock.gets
|
364
379
|
reply_type = line.slice!(0, 1)
|
365
380
|
format_reply(reply_type, line)
|
366
|
-
|
367
381
|
rescue Errno::EAGAIN
|
368
382
|
raise TimeoutError
|
369
383
|
end
|
@@ -375,7 +389,7 @@ class Redis
|
|
375
389
|
when COLON then format_integer_reply(line)
|
376
390
|
when DOLLAR then format_bulk_reply(line)
|
377
391
|
when ASTERISK then format_multi_bulk_reply(line)
|
378
|
-
else raise ProtocolError
|
392
|
+
else raise ProtocolError, reply_type
|
379
393
|
end
|
380
394
|
end
|
381
395
|
|
@@ -394,6 +408,7 @@ class Redis
|
|
394
408
|
def format_bulk_reply(line)
|
395
409
|
bulklen = line.to_i
|
396
410
|
return if bulklen == -1
|
411
|
+
|
397
412
|
reply = encode(@sock.read(bulklen))
|
398
413
|
@sock.read(2) # Discard CRLF.
|
399
414
|
reply
|
@@ -1,9 +1,16 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
require_relative "command_helper"
|
2
4
|
require_relative "registry"
|
3
5
|
require_relative "../errors"
|
4
6
|
require "em-synchrony"
|
5
7
|
require "hiredis/reader"
|
6
8
|
|
9
|
+
Kernel.warn(
|
10
|
+
"The redis synchrony driver is deprecated and will be removed in redis-rb 5.0. " \
|
11
|
+
"We're looking for people to maintain it as a separate gem, see https://github.com/redis/redis-rb/issues/915"
|
12
|
+
)
|
13
|
+
|
7
14
|
class Redis
|
8
15
|
module Connection
|
9
16
|
class RedisClient < EventMachine::Connection
|
@@ -46,9 +53,7 @@ class Redis
|
|
46
53
|
|
47
54
|
def read
|
48
55
|
@req = EventMachine::DefaultDeferrable.new
|
49
|
-
if @timeout > 0
|
50
|
-
@req.timeout(@timeout, :timeout)
|
51
|
-
end
|
56
|
+
@req.timeout(@timeout, :timeout) if @timeout > 0
|
52
57
|
EventMachine::Synchrony.sync @req
|
53
58
|
end
|
54
59
|
|
@@ -105,7 +110,7 @@ class Redis
|
|
105
110
|
end
|
106
111
|
|
107
112
|
def connected?
|
108
|
-
@connection
|
113
|
+
@connection&.connected?
|
109
114
|
end
|
110
115
|
|
111
116
|
def timeout=(timeout)
|