redis 3.0.0 → 4.5.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (106) hide show
  1. checksums.yaml +7 -0
  2. data/CHANGELOG.md +315 -0
  3. data/README.md +301 -58
  4. data/lib/redis/client.rb +383 -88
  5. data/lib/redis/cluster/command.rb +81 -0
  6. data/lib/redis/cluster/command_loader.rb +33 -0
  7. data/lib/redis/cluster/key_slot_converter.rb +72 -0
  8. data/lib/redis/cluster/node.rb +108 -0
  9. data/lib/redis/cluster/node_key.rb +31 -0
  10. data/lib/redis/cluster/node_loader.rb +37 -0
  11. data/lib/redis/cluster/option.rb +93 -0
  12. data/lib/redis/cluster/slot.rb +86 -0
  13. data/lib/redis/cluster/slot_loader.rb +49 -0
  14. data/lib/redis/cluster.rb +291 -0
  15. data/lib/redis/connection/command_helper.rb +7 -10
  16. data/lib/redis/connection/hiredis.rb +12 -8
  17. data/lib/redis/connection/registry.rb +2 -1
  18. data/lib/redis/connection/ruby.rb +266 -74
  19. data/lib/redis/connection/synchrony.rb +41 -14
  20. data/lib/redis/connection.rb +4 -2
  21. data/lib/redis/distributed.rb +258 -76
  22. data/lib/redis/errors.rb +48 -0
  23. data/lib/redis/hash_ring.rb +31 -73
  24. data/lib/redis/pipeline.rb +74 -18
  25. data/lib/redis/subscribe.rb +24 -13
  26. data/lib/redis/version.rb +3 -1
  27. data/lib/redis.rb +2068 -464
  28. metadata +63 -160
  29. data/.gitignore +0 -10
  30. data/.order +0 -169
  31. data/.travis/Gemfile +0 -11
  32. data/.travis.yml +0 -50
  33. data/.yardopts +0 -3
  34. data/Rakefile +0 -392
  35. data/benchmarking/logging.rb +0 -62
  36. data/benchmarking/pipeline.rb +0 -51
  37. data/benchmarking/speed.rb +0 -21
  38. data/benchmarking/suite.rb +0 -24
  39. data/benchmarking/worker.rb +0 -71
  40. data/examples/basic.rb +0 -15
  41. data/examples/dist_redis.rb +0 -43
  42. data/examples/incr-decr.rb +0 -17
  43. data/examples/list.rb +0 -26
  44. data/examples/pubsub.rb +0 -31
  45. data/examples/sets.rb +0 -36
  46. data/examples/unicorn/config.ru +0 -3
  47. data/examples/unicorn/unicorn.rb +0 -20
  48. data/redis.gemspec +0 -41
  49. data/test/blocking_commands_test.rb +0 -42
  50. data/test/command_map_test.rb +0 -30
  51. data/test/commands_on_hashes_test.rb +0 -21
  52. data/test/commands_on_lists_test.rb +0 -20
  53. data/test/commands_on_sets_test.rb +0 -77
  54. data/test/commands_on_sorted_sets_test.rb +0 -109
  55. data/test/commands_on_strings_test.rb +0 -83
  56. data/test/commands_on_value_types_test.rb +0 -99
  57. data/test/connection_handling_test.rb +0 -189
  58. data/test/db/.gitignore +0 -1
  59. data/test/distributed_blocking_commands_test.rb +0 -46
  60. data/test/distributed_commands_on_hashes_test.rb +0 -10
  61. data/test/distributed_commands_on_lists_test.rb +0 -22
  62. data/test/distributed_commands_on_sets_test.rb +0 -83
  63. data/test/distributed_commands_on_sorted_sets_test.rb +0 -18
  64. data/test/distributed_commands_on_strings_test.rb +0 -48
  65. data/test/distributed_commands_on_value_types_test.rb +0 -87
  66. data/test/distributed_commands_requiring_clustering_test.rb +0 -148
  67. data/test/distributed_connection_handling_test.rb +0 -23
  68. data/test/distributed_internals_test.rb +0 -15
  69. data/test/distributed_key_tags_test.rb +0 -52
  70. data/test/distributed_persistence_control_commands_test.rb +0 -26
  71. data/test/distributed_publish_subscribe_test.rb +0 -92
  72. data/test/distributed_remote_server_control_commands_test.rb +0 -53
  73. data/test/distributed_scripting_test.rb +0 -102
  74. data/test/distributed_sorting_test.rb +0 -20
  75. data/test/distributed_test.rb +0 -58
  76. data/test/distributed_transactions_test.rb +0 -32
  77. data/test/encoding_test.rb +0 -18
  78. data/test/error_replies_test.rb +0 -59
  79. data/test/helper.rb +0 -188
  80. data/test/helper_test.rb +0 -22
  81. data/test/internals_test.rb +0 -214
  82. data/test/lint/blocking_commands.rb +0 -124
  83. data/test/lint/hashes.rb +0 -162
  84. data/test/lint/lists.rb +0 -143
  85. data/test/lint/sets.rb +0 -96
  86. data/test/lint/sorted_sets.rb +0 -201
  87. data/test/lint/strings.rb +0 -157
  88. data/test/lint/value_types.rb +0 -106
  89. data/test/persistence_control_commands_test.rb +0 -26
  90. data/test/pipelining_commands_test.rb +0 -195
  91. data/test/publish_subscribe_test.rb +0 -153
  92. data/test/remote_server_control_commands_test.rb +0 -104
  93. data/test/scripting_test.rb +0 -78
  94. data/test/sorting_test.rb +0 -45
  95. data/test/support/connection/hiredis.rb +0 -1
  96. data/test/support/connection/ruby.rb +0 -1
  97. data/test/support/connection/synchrony.rb +0 -17
  98. data/test/support/redis_mock.rb +0 -92
  99. data/test/support/wire/synchrony.rb +0 -24
  100. data/test/support/wire/thread.rb +0 -5
  101. data/test/synchrony_driver.rb +0 -57
  102. data/test/test.conf +0 -9
  103. data/test/thread_safety_test.rb +0 -32
  104. data/test/transactions_test.rb +0 -244
  105. data/test/unknown_commands_test.rb +0 -14
  106. data/test/url_param_test.rb +0 -64
@@ -1,63 +1,117 @@
1
- require "redis/connection/registry"
2
- require "redis/connection/command_helper"
3
- require "redis/errors"
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 read(nbytes)
28
- result = @buffer.slice!(0, nbytes)
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
- while result.bytesize < nbytes
31
- result << _read_from_socket(nbytes - result.bytesize)
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
- result
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
- begin
49
- read_nonblock(nbytes)
50
-
51
- rescue Errno::EWOULDBLOCK, Errno::EAGAIN
52
- if IO.select([self], nil, nil, @timeout)
53
- retry
54
- else
55
- raise Redis::TimeoutError
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
- rescue EOFError
60
- raise Errno::ECONNRESET
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
- class UNIXSocket < ::UNIXSocket
135
+ if defined?(::UNIXSocket)
83
136
 
84
- # This class doesn't include the mixin, because JRuby raises
85
- # Errno::EAGAIN on #read_nonblock even when IO.select says it is
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
- def self.connect(path, timeout)
91
- Timeout.timeout(timeout) do
92
- sock = new(path)
93
- sock
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.connect(host, port, timeout)
107
- # Limit lookup to IPv4, as Redis doesn't yet do IPv6...
108
- addr = ::Socket.getaddrinfo(host, nil, Socket::AF_INET)
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
- if IO.select(nil, [sock], nil, timeout) == nil
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
- if IO.select(nil, [sock], nil, timeout) == nil
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 = "-".freeze
161
- PLUS = "+".freeze
162
- COLON = ":".freeze
163
- DOLLAR = "$".freeze
164
- ASTERISK = "*".freeze
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
- sock = UNIXSocket.connect(config[:path], config[:timeout])
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[:timeout])
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[:timeout]
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
- !! @sock
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
- @sock.timeout = timeout
196
- end
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.new(reply_type)
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
- require "redis/connection/command_helper"
2
- require "redis/connection/registry"
3
- require "redis/errors"
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
- begin
31
- until (reply = @reader.gets) == false
32
- reply = CommandError.new(reply.message) if reply.is_a?(RuntimeError)
33
- @req.succeed [:reply, reply]
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
- rescue RuntimeError => err
36
- @req.fail [:error, ProtocolError.new(err.message)]
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
- conn = EventMachine.connect_unix_domain(config[:path], RedisClient)
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[:timeout], 0.1].max
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[:timeout]
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 && @connection.connected?
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
@@ -1,4 +1,6 @@
1
- require "redis/connection/registry"
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
- require "redis/connection/ruby" if Redis::Connection.drivers.empty?
11
+ require_relative "connection/ruby" if Redis::Connection.drivers.empty?