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.
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?