redis 3.0.0 → 4.5.0
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 +7 -0
- data/CHANGELOG.md +315 -0
- data/README.md +301 -58
- data/lib/redis/client.rb +383 -88
- data/lib/redis/cluster/command.rb +81 -0
- data/lib/redis/cluster/command_loader.rb +33 -0
- data/lib/redis/cluster/key_slot_converter.rb +72 -0
- data/lib/redis/cluster/node.rb +108 -0
- data/lib/redis/cluster/node_key.rb +31 -0
- data/lib/redis/cluster/node_loader.rb +37 -0
- data/lib/redis/cluster/option.rb +93 -0
- data/lib/redis/cluster/slot.rb +86 -0
- data/lib/redis/cluster/slot_loader.rb +49 -0
- data/lib/redis/cluster.rb +291 -0
- data/lib/redis/connection/command_helper.rb +7 -10
- data/lib/redis/connection/hiredis.rb +12 -8
- data/lib/redis/connection/registry.rb +2 -1
- data/lib/redis/connection/ruby.rb +266 -74
- data/lib/redis/connection/synchrony.rb +41 -14
- data/lib/redis/connection.rb +4 -2
- data/lib/redis/distributed.rb +258 -76
- data/lib/redis/errors.rb +48 -0
- data/lib/redis/hash_ring.rb +31 -73
- data/lib/redis/pipeline.rb +74 -18
- data/lib/redis/subscribe.rb +24 -13
- data/lib/redis/version.rb +3 -1
- data/lib/redis.rb +2068 -464
- metadata +63 -160
- data/.gitignore +0 -10
- data/.order +0 -169
- data/.travis/Gemfile +0 -11
- data/.travis.yml +0 -50
- data/.yardopts +0 -3
- data/Rakefile +0 -392
- data/benchmarking/logging.rb +0 -62
- data/benchmarking/pipeline.rb +0 -51
- data/benchmarking/speed.rb +0 -21
- data/benchmarking/suite.rb +0 -24
- data/benchmarking/worker.rb +0 -71
- data/examples/basic.rb +0 -15
- data/examples/dist_redis.rb +0 -43
- data/examples/incr-decr.rb +0 -17
- data/examples/list.rb +0 -26
- data/examples/pubsub.rb +0 -31
- data/examples/sets.rb +0 -36
- data/examples/unicorn/config.ru +0 -3
- data/examples/unicorn/unicorn.rb +0 -20
- data/redis.gemspec +0 -41
- data/test/blocking_commands_test.rb +0 -42
- data/test/command_map_test.rb +0 -30
- data/test/commands_on_hashes_test.rb +0 -21
- data/test/commands_on_lists_test.rb +0 -20
- data/test/commands_on_sets_test.rb +0 -77
- data/test/commands_on_sorted_sets_test.rb +0 -109
- data/test/commands_on_strings_test.rb +0 -83
- data/test/commands_on_value_types_test.rb +0 -99
- data/test/connection_handling_test.rb +0 -189
- data/test/db/.gitignore +0 -1
- data/test/distributed_blocking_commands_test.rb +0 -46
- data/test/distributed_commands_on_hashes_test.rb +0 -10
- data/test/distributed_commands_on_lists_test.rb +0 -22
- data/test/distributed_commands_on_sets_test.rb +0 -83
- data/test/distributed_commands_on_sorted_sets_test.rb +0 -18
- data/test/distributed_commands_on_strings_test.rb +0 -48
- data/test/distributed_commands_on_value_types_test.rb +0 -87
- data/test/distributed_commands_requiring_clustering_test.rb +0 -148
- data/test/distributed_connection_handling_test.rb +0 -23
- data/test/distributed_internals_test.rb +0 -15
- data/test/distributed_key_tags_test.rb +0 -52
- data/test/distributed_persistence_control_commands_test.rb +0 -26
- data/test/distributed_publish_subscribe_test.rb +0 -92
- data/test/distributed_remote_server_control_commands_test.rb +0 -53
- data/test/distributed_scripting_test.rb +0 -102
- data/test/distributed_sorting_test.rb +0 -20
- data/test/distributed_test.rb +0 -58
- data/test/distributed_transactions_test.rb +0 -32
- data/test/encoding_test.rb +0 -18
- data/test/error_replies_test.rb +0 -59
- data/test/helper.rb +0 -188
- data/test/helper_test.rb +0 -22
- data/test/internals_test.rb +0 -214
- data/test/lint/blocking_commands.rb +0 -124
- data/test/lint/hashes.rb +0 -162
- data/test/lint/lists.rb +0 -143
- data/test/lint/sets.rb +0 -96
- data/test/lint/sorted_sets.rb +0 -201
- data/test/lint/strings.rb +0 -157
- data/test/lint/value_types.rb +0 -106
- data/test/persistence_control_commands_test.rb +0 -26
- data/test/pipelining_commands_test.rb +0 -195
- data/test/publish_subscribe_test.rb +0 -153
- data/test/remote_server_control_commands_test.rb +0 -104
- data/test/scripting_test.rb +0 -78
- data/test/sorting_test.rb +0 -45
- data/test/support/connection/hiredis.rb +0 -1
- data/test/support/connection/ruby.rb +0 -1
- data/test/support/connection/synchrony.rb +0 -17
- data/test/support/redis_mock.rb +0 -92
- data/test/support/wire/synchrony.rb +0 -24
- data/test/support/wire/thread.rb +0 -5
- data/test/synchrony_driver.rb +0 -57
- data/test/test.conf +0 -9
- data/test/thread_safety_test.rb +0 -32
- data/test/transactions_test.rb +0 -244
- data/test/unknown_commands_test.rb +0 -14
- data/test/url_param_test.rb +0 -64
@@ -1,63 +1,117 @@
|
|
1
|
-
|
2
|
-
|
3
|
-
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative "registry"
|
4
|
+
require_relative "command_helper"
|
5
|
+
require_relative "../errors"
|
4
6
|
require "socket"
|
7
|
+
require "timeout"
|
8
|
+
|
9
|
+
begin
|
10
|
+
require "openssl"
|
11
|
+
rescue LoadError
|
12
|
+
# Not all systems have OpenSSL support
|
13
|
+
end
|
5
14
|
|
6
15
|
class Redis
|
7
16
|
module Connection
|
8
17
|
module SocketMixin
|
9
|
-
|
10
|
-
CRLF = "\r\n".freeze
|
18
|
+
CRLF = "\r\n"
|
11
19
|
|
12
20
|
def initialize(*args)
|
13
21
|
super(*args)
|
14
22
|
|
15
|
-
@timeout = nil
|
16
|
-
@buffer = ""
|
23
|
+
@timeout = @write_timeout = nil
|
24
|
+
@buffer = "".dup
|
17
25
|
end
|
18
26
|
|
19
27
|
def timeout=(timeout)
|
20
|
-
if timeout && timeout > 0
|
21
|
-
@timeout = timeout
|
22
|
-
else
|
23
|
-
@timeout = nil
|
24
|
-
end
|
28
|
+
@timeout = (timeout if timeout && timeout > 0)
|
25
29
|
end
|
26
30
|
|
27
|
-
def
|
28
|
-
|
31
|
+
def write_timeout=(timeout)
|
32
|
+
@write_timeout = (timeout if timeout && timeout > 0)
|
33
|
+
end
|
34
|
+
|
35
|
+
string_capacity_support = begin
|
36
|
+
String.new(capacity: 0)
|
37
|
+
true # Ruby 2.4+
|
38
|
+
rescue ArgumentError
|
39
|
+
false # Ruby 2.3
|
40
|
+
end
|
29
41
|
|
30
|
-
|
31
|
-
|
42
|
+
if string_capacity_support
|
43
|
+
def read(nbytes)
|
44
|
+
result = @buffer.slice!(0, nbytes)
|
45
|
+
|
46
|
+
buffer = String.new(capacity: nbytes, encoding: Encoding::ASCII_8BIT)
|
47
|
+
result << _read_from_socket(nbytes - result.bytesize, buffer) while result.bytesize < nbytes
|
48
|
+
|
49
|
+
result
|
32
50
|
end
|
51
|
+
else
|
52
|
+
def read(nbytes)
|
53
|
+
result = @buffer.slice!(0, nbytes)
|
54
|
+
|
55
|
+
result << _read_from_socket(nbytes - result.bytesize, "".b) while result.bytesize < nbytes
|
33
56
|
|
34
|
-
|
57
|
+
result
|
58
|
+
end
|
35
59
|
end
|
36
60
|
|
37
61
|
def gets
|
38
|
-
crlf = nil
|
39
|
-
|
40
|
-
while (crlf = @buffer.index(CRLF)) == nil
|
41
|
-
@buffer << _read_from_socket(1024)
|
62
|
+
while (crlf = @buffer.index(CRLF)).nil?
|
63
|
+
@buffer << _read_from_socket(16_384)
|
42
64
|
end
|
43
65
|
|
44
66
|
@buffer.slice!(0, crlf + CRLF.bytesize)
|
45
67
|
end
|
46
68
|
|
47
|
-
def _read_from_socket(nbytes)
|
48
|
-
|
49
|
-
read_nonblock(nbytes)
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
69
|
+
def _read_from_socket(nbytes, buffer = nil)
|
70
|
+
loop do
|
71
|
+
case chunk = read_nonblock(nbytes, buffer, exception: false)
|
72
|
+
when :wait_readable
|
73
|
+
unless wait_readable(@timeout)
|
74
|
+
raise Redis::TimeoutError
|
75
|
+
end
|
76
|
+
when :wait_writable
|
77
|
+
unless wait_writable(@timeout)
|
78
|
+
raise Redis::TimeoutError
|
79
|
+
end
|
80
|
+
when nil
|
81
|
+
raise Errno::ECONNRESET
|
82
|
+
when String
|
83
|
+
return chunk
|
56
84
|
end
|
57
85
|
end
|
86
|
+
end
|
87
|
+
|
88
|
+
def write(buffer)
|
89
|
+
return super(buffer) unless @write_timeout
|
90
|
+
|
91
|
+
bytes_to_write = buffer.bytesize
|
92
|
+
total_bytes_written = 0
|
93
|
+
loop do
|
94
|
+
case bytes_written = write_nonblock(buffer, exception: false)
|
95
|
+
when :wait_readable
|
96
|
+
unless wait_readable(@write_timeout)
|
97
|
+
raise Redis::TimeoutError
|
98
|
+
end
|
99
|
+
when :wait_writable
|
100
|
+
unless wait_writable(@write_timeout)
|
101
|
+
raise Redis::TimeoutError
|
102
|
+
end
|
103
|
+
when nil
|
104
|
+
raise Errno::ECONNRESET
|
105
|
+
when Integer
|
106
|
+
total_bytes_written += bytes_written
|
107
|
+
|
108
|
+
if total_bytes_written >= bytes_to_write
|
109
|
+
return total_bytes_written
|
110
|
+
end
|
58
111
|
|
59
|
-
|
60
|
-
|
112
|
+
buffer = buffer.byteslice(bytes_written..-1)
|
113
|
+
end
|
114
|
+
end
|
61
115
|
end
|
62
116
|
end
|
63
117
|
|
@@ -66,7 +120,6 @@ class Redis
|
|
66
120
|
require "timeout"
|
67
121
|
|
68
122
|
class TCPSocket < ::TCPSocket
|
69
|
-
|
70
123
|
include SocketMixin
|
71
124
|
|
72
125
|
def self.connect(host, port, timeout)
|
@@ -79,42 +132,46 @@ class Redis
|
|
79
132
|
end
|
80
133
|
end
|
81
134
|
|
82
|
-
|
135
|
+
if defined?(::UNIXSocket)
|
83
136
|
|
84
|
-
|
85
|
-
|
86
|
-
# readable. This behavior shows in 1.6.6 in both 1.8 and 1.9 mode.
|
87
|
-
# Therefore, fall back on the default Unix socket implementation,
|
88
|
-
# without timeouts.
|
137
|
+
class UNIXSocket < ::UNIXSocket
|
138
|
+
include SocketMixin
|
89
139
|
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
|
140
|
+
def self.connect(path, timeout)
|
141
|
+
Timeout.timeout(timeout) do
|
142
|
+
sock = new(path)
|
143
|
+
sock
|
144
|
+
end
|
145
|
+
rescue Timeout::Error
|
146
|
+
raise TimeoutError
|
147
|
+
end
|
148
|
+
|
149
|
+
# JRuby raises Errno::EAGAIN on #read_nonblock even when it
|
150
|
+
# says it is readable (1.6.6, in both 1.8 and 1.9 mode).
|
151
|
+
# Use the blocking #readpartial method instead.
|
152
|
+
|
153
|
+
def _read_from_socket(nbytes)
|
154
|
+
readpartial(nbytes)
|
155
|
+
rescue EOFError
|
156
|
+
raise Errno::ECONNRESET
|
94
157
|
end
|
95
|
-
rescue Timeout::Error
|
96
|
-
raise TimeoutError
|
97
158
|
end
|
159
|
+
|
98
160
|
end
|
99
161
|
|
100
162
|
else
|
101
163
|
|
102
164
|
class TCPSocket < ::Socket
|
103
|
-
|
104
165
|
include SocketMixin
|
105
166
|
|
106
|
-
def self.
|
107
|
-
|
108
|
-
|
109
|
-
sock = new(::Socket.const_get(addr[0][0]), Socket::SOCK_STREAM, 0)
|
110
|
-
sockaddr = ::Socket.pack_sockaddr_in(port, addr[0][3])
|
167
|
+
def self.connect_addrinfo(addrinfo, port, timeout)
|
168
|
+
sock = new(::Socket.const_get(addrinfo[0]), Socket::SOCK_STREAM, 0)
|
169
|
+
sockaddr = ::Socket.pack_sockaddr_in(port, addrinfo[3])
|
111
170
|
|
112
171
|
begin
|
113
172
|
sock.connect_nonblock(sockaddr)
|
114
173
|
rescue Errno::EINPROGRESS
|
115
|
-
|
116
|
-
raise TimeoutError
|
117
|
-
end
|
174
|
+
raise TimeoutError unless sock.wait_writable(timeout)
|
118
175
|
|
119
176
|
begin
|
120
177
|
sock.connect_nonblock(sockaddr)
|
@@ -124,12 +181,43 @@ class Redis
|
|
124
181
|
|
125
182
|
sock
|
126
183
|
end
|
184
|
+
|
185
|
+
def self.connect(host, port, timeout)
|
186
|
+
# Don't pass AI_ADDRCONFIG as flag to getaddrinfo(3)
|
187
|
+
#
|
188
|
+
# From the man page for getaddrinfo(3):
|
189
|
+
#
|
190
|
+
# If hints.ai_flags includes the AI_ADDRCONFIG flag, then IPv4
|
191
|
+
# addresses are returned in the list pointed to by res only if the
|
192
|
+
# local system has at least one IPv4 address configured, and IPv6
|
193
|
+
# addresses are returned only if the local system has at least one
|
194
|
+
# IPv6 address configured. The loopback address is not considered
|
195
|
+
# for this case as valid as a configured address.
|
196
|
+
#
|
197
|
+
# We do want the IPv6 loopback address to be returned if applicable,
|
198
|
+
# even if it is the only configured IPv6 address on the machine.
|
199
|
+
# Also see: https://github.com/redis/redis-rb/pull/394.
|
200
|
+
addrinfo = ::Socket.getaddrinfo(host, nil, Socket::AF_UNSPEC, Socket::SOCK_STREAM)
|
201
|
+
|
202
|
+
# From the man page for getaddrinfo(3):
|
203
|
+
#
|
204
|
+
# Normally, the application should try using the addresses in the
|
205
|
+
# order in which they are returned. The sorting function used
|
206
|
+
# within getaddrinfo() is defined in RFC 3484 [...].
|
207
|
+
#
|
208
|
+
addrinfo.each_with_index do |ai, i|
|
209
|
+
begin
|
210
|
+
return connect_addrinfo(ai, port, timeout)
|
211
|
+
rescue SystemCallError
|
212
|
+
# Raise if this was our last attempt.
|
213
|
+
raise if addrinfo.length == i + 1
|
214
|
+
end
|
215
|
+
end
|
216
|
+
end
|
127
217
|
end
|
128
218
|
|
129
219
|
class UNIXSocket < ::Socket
|
130
|
-
|
131
|
-
# This class doesn't include the mixin to keep its behavior in sync
|
132
|
-
# with the JRuby implementation.
|
220
|
+
include SocketMixin
|
133
221
|
|
134
222
|
def self.connect(path, timeout)
|
135
223
|
sock = new(::Socket::AF_UNIX, Socket::SOCK_STREAM, 0)
|
@@ -138,9 +226,7 @@ class Redis
|
|
138
226
|
begin
|
139
227
|
sock.connect_nonblock(sockaddr)
|
140
228
|
rescue Errno::EINPROGRESS
|
141
|
-
|
142
|
-
raise TimeoutError
|
143
|
-
end
|
229
|
+
raise TimeoutError unless sock.wait_writable(timeout)
|
144
230
|
|
145
231
|
begin
|
146
232
|
sock.connect_nonblock(sockaddr)
|
@@ -154,33 +240,137 @@ class Redis
|
|
154
240
|
|
155
241
|
end
|
156
242
|
|
243
|
+
if defined?(OpenSSL)
|
244
|
+
class SSLSocket < ::OpenSSL::SSL::SSLSocket
|
245
|
+
include SocketMixin
|
246
|
+
|
247
|
+
unless method_defined?(:wait_readable)
|
248
|
+
def wait_readable(timeout = nil)
|
249
|
+
to_io.wait_readable(timeout)
|
250
|
+
end
|
251
|
+
end
|
252
|
+
|
253
|
+
unless method_defined?(:wait_writable)
|
254
|
+
def wait_writable(timeout = nil)
|
255
|
+
to_io.wait_writable(timeout)
|
256
|
+
end
|
257
|
+
end
|
258
|
+
|
259
|
+
def self.connect(host, port, timeout, ssl_params)
|
260
|
+
# Note: this is using Redis::Connection::TCPSocket
|
261
|
+
tcp_sock = TCPSocket.connect(host, port, timeout)
|
262
|
+
|
263
|
+
ctx = OpenSSL::SSL::SSLContext.new
|
264
|
+
|
265
|
+
# The provided parameters are merged into OpenSSL::SSL::SSLContext::DEFAULT_PARAMS
|
266
|
+
ctx.set_params(ssl_params || {})
|
267
|
+
|
268
|
+
ssl_sock = new(tcp_sock, ctx)
|
269
|
+
ssl_sock.hostname = host
|
270
|
+
|
271
|
+
begin
|
272
|
+
# Initiate the socket connection in the background. If it doesn't fail
|
273
|
+
# immediately it will raise an IO::WaitWritable (Errno::EINPROGRESS)
|
274
|
+
# indicating the connection is in progress.
|
275
|
+
# Unlike waiting for a tcp socket to connect, you can't time out ssl socket
|
276
|
+
# connections during the connect phase properly, because IO.select only partially works.
|
277
|
+
# Instead, you have to retry.
|
278
|
+
ssl_sock.connect_nonblock
|
279
|
+
rescue Errno::EAGAIN, Errno::EWOULDBLOCK, IO::WaitReadable
|
280
|
+
if ssl_sock.wait_readable(timeout)
|
281
|
+
retry
|
282
|
+
else
|
283
|
+
raise TimeoutError
|
284
|
+
end
|
285
|
+
rescue IO::WaitWritable
|
286
|
+
if ssl_sock.wait_writable(timeout)
|
287
|
+
retry
|
288
|
+
else
|
289
|
+
raise TimeoutError
|
290
|
+
end
|
291
|
+
end
|
292
|
+
|
293
|
+
unless ctx.verify_mode == OpenSSL::SSL::VERIFY_NONE || (
|
294
|
+
ctx.respond_to?(:verify_hostname) &&
|
295
|
+
!ctx.verify_hostname
|
296
|
+
)
|
297
|
+
ssl_sock.post_connection_check(host)
|
298
|
+
end
|
299
|
+
|
300
|
+
ssl_sock
|
301
|
+
end
|
302
|
+
end
|
303
|
+
end
|
304
|
+
|
157
305
|
class Ruby
|
158
306
|
include Redis::Connection::CommandHelper
|
159
307
|
|
160
|
-
MINUS = "-"
|
161
|
-
PLUS = "+"
|
162
|
-
COLON = ":"
|
163
|
-
DOLLAR = "$"
|
164
|
-
ASTERISK = "*"
|
308
|
+
MINUS = "-"
|
309
|
+
PLUS = "+"
|
310
|
+
COLON = ":"
|
311
|
+
DOLLAR = "$"
|
312
|
+
ASTERISK = "*"
|
165
313
|
|
166
314
|
def self.connect(config)
|
167
315
|
if config[:scheme] == "unix"
|
168
|
-
|
316
|
+
raise ArgumentError, "SSL incompatible with unix sockets" if config[:ssl]
|
317
|
+
|
318
|
+
sock = UNIXSocket.connect(config[:path], config[:connect_timeout])
|
319
|
+
elsif config[:scheme] == "rediss" || config[:ssl]
|
320
|
+
sock = SSLSocket.connect(config[:host], config[:port], config[:connect_timeout], config[:ssl_params])
|
169
321
|
else
|
170
|
-
sock = TCPSocket.connect(config[:host], config[:port], config[:
|
322
|
+
sock = TCPSocket.connect(config[:host], config[:port], config[:connect_timeout])
|
171
323
|
end
|
172
324
|
|
173
325
|
instance = new(sock)
|
174
|
-
instance.timeout = config[:
|
326
|
+
instance.timeout = config[:read_timeout]
|
327
|
+
instance.write_timeout = config[:write_timeout]
|
328
|
+
instance.set_tcp_keepalive config[:tcp_keepalive]
|
329
|
+
instance.set_tcp_nodelay if sock.is_a? TCPSocket
|
175
330
|
instance
|
176
331
|
end
|
177
332
|
|
333
|
+
if %i[SOL_SOCKET SO_KEEPALIVE SOL_TCP TCP_KEEPIDLE TCP_KEEPINTVL TCP_KEEPCNT].all? { |c| Socket.const_defined? c }
|
334
|
+
def set_tcp_keepalive(keepalive)
|
335
|
+
return unless keepalive.is_a?(Hash)
|
336
|
+
|
337
|
+
@sock.setsockopt(Socket::SOL_SOCKET, Socket::SO_KEEPALIVE, true)
|
338
|
+
@sock.setsockopt(Socket::SOL_TCP, Socket::TCP_KEEPIDLE, keepalive[:time])
|
339
|
+
@sock.setsockopt(Socket::SOL_TCP, Socket::TCP_KEEPINTVL, keepalive[:intvl])
|
340
|
+
@sock.setsockopt(Socket::SOL_TCP, Socket::TCP_KEEPCNT, keepalive[:probes])
|
341
|
+
end
|
342
|
+
|
343
|
+
def get_tcp_keepalive
|
344
|
+
{
|
345
|
+
time: @sock.getsockopt(Socket::SOL_TCP, Socket::TCP_KEEPIDLE).int,
|
346
|
+
intvl: @sock.getsockopt(Socket::SOL_TCP, Socket::TCP_KEEPINTVL).int,
|
347
|
+
probes: @sock.getsockopt(Socket::SOL_TCP, Socket::TCP_KEEPCNT).int
|
348
|
+
}
|
349
|
+
end
|
350
|
+
else
|
351
|
+
def set_tcp_keepalive(keepalive); end
|
352
|
+
|
353
|
+
def get_tcp_keepalive
|
354
|
+
{
|
355
|
+
}
|
356
|
+
end
|
357
|
+
end
|
358
|
+
|
359
|
+
# disables Nagle's Algorithm, prevents multiple round trips with MULTI
|
360
|
+
if %i[IPPROTO_TCP TCP_NODELAY].all? { |c| Socket.const_defined? c }
|
361
|
+
def set_tcp_nodelay
|
362
|
+
@sock.setsockopt(Socket::IPPROTO_TCP, Socket::TCP_NODELAY, 1)
|
363
|
+
end
|
364
|
+
else
|
365
|
+
def set_tcp_nodelay; end
|
366
|
+
end
|
367
|
+
|
178
368
|
def initialize(sock)
|
179
369
|
@sock = sock
|
180
370
|
end
|
181
371
|
|
182
372
|
def connected?
|
183
|
-
|
373
|
+
!!@sock
|
184
374
|
end
|
185
375
|
|
186
376
|
def disconnect
|
@@ -191,9 +381,11 @@ class Redis
|
|
191
381
|
end
|
192
382
|
|
193
383
|
def timeout=(timeout)
|
194
|
-
if @sock.respond_to?(:timeout=)
|
195
|
-
|
196
|
-
|
384
|
+
@sock.timeout = timeout if @sock.respond_to?(:timeout=)
|
385
|
+
end
|
386
|
+
|
387
|
+
def write_timeout=(timeout)
|
388
|
+
@sock.write_timeout = timeout
|
197
389
|
end
|
198
390
|
|
199
391
|
def write(command)
|
@@ -204,7 +396,6 @@ class Redis
|
|
204
396
|
line = @sock.gets
|
205
397
|
reply_type = line.slice!(0, 1)
|
206
398
|
format_reply(reply_type, line)
|
207
|
-
|
208
399
|
rescue Errno::EAGAIN
|
209
400
|
raise TimeoutError
|
210
401
|
end
|
@@ -216,7 +407,7 @@ class Redis
|
|
216
407
|
when COLON then format_integer_reply(line)
|
217
408
|
when DOLLAR then format_bulk_reply(line)
|
218
409
|
when ASTERISK then format_multi_bulk_reply(line)
|
219
|
-
else raise ProtocolError
|
410
|
+
else raise ProtocolError, reply_type
|
220
411
|
end
|
221
412
|
end
|
222
413
|
|
@@ -235,6 +426,7 @@ class Redis
|
|
235
426
|
def format_bulk_reply(line)
|
236
427
|
bulklen = line.to_i
|
237
428
|
return if bulklen == -1
|
429
|
+
|
238
430
|
reply = encode(@sock.read(bulklen))
|
239
431
|
@sock.read(2) # Discard CRLF.
|
240
432
|
reply
|
@@ -1,14 +1,23 @@
|
|
1
|
-
|
2
|
-
|
3
|
-
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative "command_helper"
|
4
|
+
require_relative "registry"
|
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
|
10
17
|
include EventMachine::Deferrable
|
11
18
|
|
19
|
+
attr_accessor :timeout
|
20
|
+
|
12
21
|
def post_init
|
13
22
|
@req = nil
|
14
23
|
@connected = false
|
@@ -27,18 +36,24 @@ class Redis
|
|
27
36
|
def receive_data(data)
|
28
37
|
@reader.feed(data)
|
29
38
|
|
30
|
-
|
31
|
-
|
32
|
-
reply =
|
33
|
-
|
39
|
+
loop do
|
40
|
+
begin
|
41
|
+
reply = @reader.gets
|
42
|
+
rescue RuntimeError => err
|
43
|
+
@req.fail [:error, ProtocolError.new(err.message)]
|
44
|
+
break
|
34
45
|
end
|
35
|
-
|
36
|
-
|
46
|
+
|
47
|
+
break if reply == false
|
48
|
+
|
49
|
+
reply = CommandError.new(reply.message) if reply.is_a?(RuntimeError)
|
50
|
+
@req.succeed [:reply, reply]
|
37
51
|
end
|
38
52
|
end
|
39
53
|
|
40
54
|
def read
|
41
55
|
@req = EventMachine::DefaultDeferrable.new
|
56
|
+
@req.timeout(@timeout, :timeout) if @timeout > 0
|
42
57
|
EventMachine::Synchrony.sync @req
|
43
58
|
end
|
44
59
|
|
@@ -62,10 +77,20 @@ class Redis
|
|
62
77
|
|
63
78
|
def self.connect(config)
|
64
79
|
if config[:scheme] == "unix"
|
65
|
-
|
80
|
+
begin
|
81
|
+
conn = EventMachine.connect_unix_domain(config[:path], RedisClient)
|
82
|
+
rescue RuntimeError => e
|
83
|
+
if e.message == "no connection"
|
84
|
+
raise Errno::ECONNREFUSED
|
85
|
+
else
|
86
|
+
raise e
|
87
|
+
end
|
88
|
+
end
|
89
|
+
elsif config[:scheme] == "rediss" || config[:ssl]
|
90
|
+
raise NotImplementedError, "SSL not supported by synchrony driver"
|
66
91
|
else
|
67
92
|
conn = EventMachine.connect(config[:host], config[:port], RedisClient) do |c|
|
68
|
-
c.pending_connect_timeout = [config[:
|
93
|
+
c.pending_connect_timeout = [config[:connect_timeout], 0.1].max
|
69
94
|
end
|
70
95
|
end
|
71
96
|
|
@@ -76,7 +101,7 @@ class Redis
|
|
76
101
|
raise Errno::ECONNREFUSED if Fiber.yield == :refused
|
77
102
|
|
78
103
|
instance = new(conn)
|
79
|
-
instance.timeout = config[:
|
104
|
+
instance.timeout = config[:read_timeout]
|
80
105
|
instance
|
81
106
|
end
|
82
107
|
|
@@ -85,11 +110,11 @@ class Redis
|
|
85
110
|
end
|
86
111
|
|
87
112
|
def connected?
|
88
|
-
@connection
|
113
|
+
@connection&.connected?
|
89
114
|
end
|
90
115
|
|
91
116
|
def timeout=(timeout)
|
92
|
-
@timeout = timeout
|
117
|
+
@connection.timeout = timeout
|
93
118
|
end
|
94
119
|
|
95
120
|
def disconnect
|
@@ -108,6 +133,8 @@ class Redis
|
|
108
133
|
payload
|
109
134
|
elsif type == :error
|
110
135
|
raise payload
|
136
|
+
elsif type == :timeout
|
137
|
+
raise TimeoutError
|
111
138
|
else
|
112
139
|
raise "Unknown type #{type.inspect}"
|
113
140
|
end
|
data/lib/redis/connection.rb
CHANGED
@@ -1,4 +1,6 @@
|
|
1
|
-
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative "connection/registry"
|
2
4
|
|
3
5
|
# If a connection driver was required before this file, the array
|
4
6
|
# Redis::Connection.drivers will contain one or more classes. The last driver
|
@@ -6,4 +8,4 @@ require "redis/connection/registry"
|
|
6
8
|
# the plain Ruby driver as our default. Another driver can be required at a
|
7
9
|
# later point in time, causing it to be the last element of the #drivers array
|
8
10
|
# and therefore be chosen by default.
|
9
|
-
|
11
|
+
require_relative "connection/ruby" if Redis::Connection.drivers.empty?
|