redis 4.8.1 → 5.1.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (44) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +65 -0
  3. data/README.md +101 -161
  4. data/lib/redis/client.rb +82 -616
  5. data/lib/redis/commands/bitmaps.rb +14 -4
  6. data/lib/redis/commands/cluster.rb +1 -18
  7. data/lib/redis/commands/connection.rb +5 -10
  8. data/lib/redis/commands/geo.rb +3 -3
  9. data/lib/redis/commands/hashes.rb +9 -6
  10. data/lib/redis/commands/hyper_log_log.rb +1 -1
  11. data/lib/redis/commands/keys.rb +21 -23
  12. data/lib/redis/commands/lists.rb +74 -25
  13. data/lib/redis/commands/pubsub.rb +28 -25
  14. data/lib/redis/commands/server.rb +15 -15
  15. data/lib/redis/commands/sets.rb +31 -40
  16. data/lib/redis/commands/sorted_sets.rb +84 -12
  17. data/lib/redis/commands/streams.rb +39 -19
  18. data/lib/redis/commands/strings.rb +18 -17
  19. data/lib/redis/commands/transactions.rb +7 -31
  20. data/lib/redis/commands.rb +4 -7
  21. data/lib/redis/distributed.rb +128 -68
  22. data/lib/redis/errors.rb +15 -50
  23. data/lib/redis/hash_ring.rb +26 -26
  24. data/lib/redis/pipeline.rb +43 -222
  25. data/lib/redis/subscribe.rb +50 -14
  26. data/lib/redis/version.rb +1 -1
  27. data/lib/redis.rb +76 -184
  28. metadata +10 -54
  29. data/lib/redis/cluster/command.rb +0 -79
  30. data/lib/redis/cluster/command_loader.rb +0 -33
  31. data/lib/redis/cluster/key_slot_converter.rb +0 -72
  32. data/lib/redis/cluster/node.rb +0 -120
  33. data/lib/redis/cluster/node_key.rb +0 -31
  34. data/lib/redis/cluster/node_loader.rb +0 -34
  35. data/lib/redis/cluster/option.rb +0 -100
  36. data/lib/redis/cluster/slot.rb +0 -86
  37. data/lib/redis/cluster/slot_loader.rb +0 -46
  38. data/lib/redis/cluster.rb +0 -315
  39. data/lib/redis/connection/command_helper.rb +0 -41
  40. data/lib/redis/connection/hiredis.rb +0 -68
  41. data/lib/redis/connection/registry.rb +0 -13
  42. data/lib/redis/connection/ruby.rb +0 -437
  43. data/lib/redis/connection/synchrony.rb +0 -148
  44. data/lib/redis/connection.rb +0 -11
@@ -1,437 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require "redis/connection/registry"
4
- require "redis/connection/command_helper"
5
- require "redis/errors"
6
-
7
- require "socket"
8
- require "timeout"
9
-
10
- begin
11
- require "openssl"
12
- rescue LoadError
13
- # Not all systems have OpenSSL support
14
- end
15
-
16
- class Redis
17
- module Connection
18
- module SocketMixin
19
- CRLF = "\r\n"
20
-
21
- def initialize(*args)
22
- super(*args)
23
-
24
- @timeout = @write_timeout = nil
25
- @buffer = "".b
26
- end
27
-
28
- def timeout=(timeout)
29
- @timeout = (timeout if timeout && timeout > 0)
30
- end
31
-
32
- def write_timeout=(timeout)
33
- @write_timeout = (timeout if timeout && timeout > 0)
34
- end
35
-
36
- def read(nbytes)
37
- result = @buffer.slice!(0, nbytes)
38
-
39
- buffer = String.new(capacity: nbytes, encoding: Encoding::ASCII_8BIT)
40
- result << _read_from_socket(nbytes - result.bytesize, buffer) while result.bytesize < nbytes
41
-
42
- result
43
- end
44
-
45
- def gets
46
- while (crlf = @buffer.index(CRLF)).nil?
47
- @buffer << _read_from_socket(16_384)
48
- end
49
-
50
- @buffer.slice!(0, crlf + CRLF.bytesize)
51
- end
52
-
53
- def _read_from_socket(nbytes, buffer = nil)
54
- loop do
55
- case chunk = read_nonblock(nbytes, buffer, exception: false)
56
- when :wait_readable
57
- unless wait_readable(@timeout)
58
- raise Redis::TimeoutError
59
- end
60
- when :wait_writable
61
- unless wait_writable(@timeout)
62
- raise Redis::TimeoutError
63
- end
64
- when nil
65
- raise Errno::ECONNRESET
66
- when String
67
- return chunk
68
- end
69
- end
70
- end
71
-
72
- def write(buffer)
73
- return super(buffer) unless @write_timeout
74
-
75
- bytes_to_write = buffer.bytesize
76
- total_bytes_written = 0
77
- loop do
78
- case bytes_written = write_nonblock(buffer, exception: false)
79
- when :wait_readable
80
- unless wait_readable(@write_timeout)
81
- raise Redis::TimeoutError
82
- end
83
- when :wait_writable
84
- unless wait_writable(@write_timeout)
85
- raise Redis::TimeoutError
86
- end
87
- when nil
88
- raise Errno::ECONNRESET
89
- when Integer
90
- total_bytes_written += bytes_written
91
-
92
- if total_bytes_written >= bytes_to_write
93
- return total_bytes_written
94
- end
95
-
96
- buffer = buffer.byteslice(bytes_written..-1)
97
- end
98
- end
99
- end
100
- end
101
-
102
- if defined?(RUBY_ENGINE) && RUBY_ENGINE == "jruby"
103
-
104
- require "timeout"
105
-
106
- class TCPSocket < ::TCPSocket
107
- include SocketMixin
108
-
109
- def self.connect(host, port, timeout)
110
- Timeout.timeout(timeout) do
111
- sock = new(host, port)
112
- sock
113
- end
114
- rescue Timeout::Error
115
- raise TimeoutError
116
- end
117
- end
118
-
119
- if defined?(::UNIXSocket)
120
-
121
- class UNIXSocket < ::UNIXSocket
122
- include SocketMixin
123
-
124
- def self.connect(path, timeout)
125
- Timeout.timeout(timeout) do
126
- sock = new(path)
127
- sock
128
- end
129
- rescue Timeout::Error
130
- raise TimeoutError
131
- end
132
-
133
- # JRuby raises Errno::EAGAIN on #read_nonblock even when it
134
- # says it is readable (1.6.6, in both 1.8 and 1.9 mode).
135
- # Use the blocking #readpartial method instead.
136
-
137
- def _read_from_socket(nbytes, _buffer = nil)
138
- # JRuby: Throw away the buffer as we won't need it
139
- # but still need to support the max arity of 2
140
- readpartial(nbytes)
141
- rescue EOFError
142
- raise Errno::ECONNRESET
143
- end
144
- end
145
-
146
- end
147
-
148
- else
149
-
150
- class TCPSocket < ::Socket
151
- include SocketMixin
152
-
153
- def self.connect_addrinfo(addrinfo, port, timeout)
154
- sock = new(::Socket.const_get(addrinfo[0]), Socket::SOCK_STREAM, 0)
155
- sockaddr = ::Socket.pack_sockaddr_in(port, addrinfo[3])
156
-
157
- begin
158
- sock.connect_nonblock(sockaddr)
159
- rescue Errno::EINPROGRESS
160
- raise TimeoutError unless sock.wait_writable(timeout)
161
-
162
- begin
163
- sock.connect_nonblock(sockaddr)
164
- rescue Errno::EISCONN
165
- end
166
- end
167
-
168
- sock
169
- end
170
-
171
- def self.connect(host, port, timeout)
172
- # Don't pass AI_ADDRCONFIG as flag to getaddrinfo(3)
173
- #
174
- # From the man page for getaddrinfo(3):
175
- #
176
- # If hints.ai_flags includes the AI_ADDRCONFIG flag, then IPv4
177
- # addresses are returned in the list pointed to by res only if the
178
- # local system has at least one IPv4 address configured, and IPv6
179
- # addresses are returned only if the local system has at least one
180
- # IPv6 address configured. The loopback address is not considered
181
- # for this case as valid as a configured address.
182
- #
183
- # We do want the IPv6 loopback address to be returned if applicable,
184
- # even if it is the only configured IPv6 address on the machine.
185
- # Also see: https://github.com/redis/redis-rb/pull/394.
186
- addrinfo = ::Socket.getaddrinfo(host, nil, Socket::AF_UNSPEC, Socket::SOCK_STREAM)
187
-
188
- # From the man page for getaddrinfo(3):
189
- #
190
- # Normally, the application should try using the addresses in the
191
- # order in which they are returned. The sorting function used
192
- # within getaddrinfo() is defined in RFC 3484 [...].
193
- #
194
- addrinfo.each_with_index do |ai, i|
195
- begin
196
- return connect_addrinfo(ai, port, timeout)
197
- rescue SystemCallError
198
- # Raise if this was our last attempt.
199
- raise if addrinfo.length == i + 1
200
- end
201
- end
202
- end
203
- end
204
-
205
- class UNIXSocket < ::Socket
206
- include SocketMixin
207
-
208
- def self.connect(path, timeout)
209
- sock = new(::Socket::AF_UNIX, Socket::SOCK_STREAM, 0)
210
- sockaddr = ::Socket.pack_sockaddr_un(path)
211
-
212
- begin
213
- sock.connect_nonblock(sockaddr)
214
- rescue Errno::EINPROGRESS
215
- raise TimeoutError unless sock.wait_writable(timeout)
216
-
217
- begin
218
- sock.connect_nonblock(sockaddr)
219
- rescue Errno::EISCONN
220
- end
221
- end
222
-
223
- sock
224
- end
225
- end
226
-
227
- end
228
-
229
- if defined?(OpenSSL)
230
- class SSLSocket < ::OpenSSL::SSL::SSLSocket
231
- include SocketMixin
232
-
233
- unless method_defined?(:wait_readable)
234
- def wait_readable(timeout = nil)
235
- to_io.wait_readable(timeout)
236
- end
237
- end
238
-
239
- unless method_defined?(:wait_writable)
240
- def wait_writable(timeout = nil)
241
- to_io.wait_writable(timeout)
242
- end
243
- end
244
-
245
- def self.connect(host, port, timeout, ssl_params)
246
- # NOTE: this is using Redis::Connection::TCPSocket
247
- tcp_sock = TCPSocket.connect(host, port, timeout)
248
-
249
- ctx = OpenSSL::SSL::SSLContext.new
250
-
251
- # The provided parameters are merged into OpenSSL::SSL::SSLContext::DEFAULT_PARAMS
252
- ctx.set_params(ssl_params || {})
253
-
254
- ssl_sock = new(tcp_sock, ctx)
255
- ssl_sock.hostname = host
256
-
257
- begin
258
- # Initiate the socket connection in the background. If it doesn't fail
259
- # immediately it will raise an IO::WaitWritable (Errno::EINPROGRESS)
260
- # indicating the connection is in progress.
261
- # Unlike waiting for a tcp socket to connect, you can't time out ssl socket
262
- # connections during the connect phase properly, because IO.select only partially works.
263
- # Instead, you have to retry.
264
- ssl_sock.connect_nonblock
265
- rescue Errno::EAGAIN, Errno::EWOULDBLOCK, IO::WaitReadable
266
- if ssl_sock.wait_readable(timeout)
267
- retry
268
- else
269
- raise TimeoutError
270
- end
271
- rescue IO::WaitWritable
272
- if ssl_sock.wait_writable(timeout)
273
- retry
274
- else
275
- raise TimeoutError
276
- end
277
- end
278
-
279
- unless ctx.verify_mode == OpenSSL::SSL::VERIFY_NONE || (
280
- ctx.respond_to?(:verify_hostname) &&
281
- !ctx.verify_hostname
282
- )
283
- ssl_sock.post_connection_check(host)
284
- end
285
-
286
- ssl_sock
287
- end
288
- end
289
- end
290
-
291
- class Ruby
292
- include Redis::Connection::CommandHelper
293
-
294
- MINUS = "-"
295
- PLUS = "+"
296
- COLON = ":"
297
- DOLLAR = "$"
298
- ASTERISK = "*"
299
-
300
- def self.connect(config)
301
- if config[:scheme] == "unix"
302
- raise ArgumentError, "SSL incompatible with unix sockets" if config[:ssl]
303
-
304
- sock = UNIXSocket.connect(config[:path], config[:connect_timeout])
305
- elsif config[:scheme] == "rediss" || config[:ssl]
306
- sock = SSLSocket.connect(config[:host], config[:port], config[:connect_timeout], config[:ssl_params])
307
- else
308
- sock = TCPSocket.connect(config[:host], config[:port], config[:connect_timeout])
309
- end
310
-
311
- instance = new(sock)
312
- instance.timeout = config[:read_timeout]
313
- instance.write_timeout = config[:write_timeout]
314
- instance.set_tcp_keepalive config[:tcp_keepalive]
315
- instance.set_tcp_nodelay if sock.is_a? TCPSocket
316
- instance
317
- end
318
-
319
- if %i[SOL_SOCKET SO_KEEPALIVE SOL_TCP TCP_KEEPIDLE TCP_KEEPINTVL TCP_KEEPCNT].all? { |c| Socket.const_defined? c }
320
- def set_tcp_keepalive(keepalive)
321
- return unless keepalive.is_a?(Hash)
322
-
323
- @sock.setsockopt(Socket::SOL_SOCKET, Socket::SO_KEEPALIVE, true)
324
- @sock.setsockopt(Socket::SOL_TCP, Socket::TCP_KEEPIDLE, keepalive[:time])
325
- @sock.setsockopt(Socket::SOL_TCP, Socket::TCP_KEEPINTVL, keepalive[:intvl])
326
- @sock.setsockopt(Socket::SOL_TCP, Socket::TCP_KEEPCNT, keepalive[:probes])
327
- end
328
-
329
- def get_tcp_keepalive
330
- {
331
- time: @sock.getsockopt(Socket::SOL_TCP, Socket::TCP_KEEPIDLE).int,
332
- intvl: @sock.getsockopt(Socket::SOL_TCP, Socket::TCP_KEEPINTVL).int,
333
- probes: @sock.getsockopt(Socket::SOL_TCP, Socket::TCP_KEEPCNT).int
334
- }
335
- end
336
- else
337
- def set_tcp_keepalive(keepalive); end
338
-
339
- def get_tcp_keepalive
340
- {
341
- }
342
- end
343
- end
344
-
345
- # disables Nagle's Algorithm, prevents multiple round trips with MULTI
346
- if %i[IPPROTO_TCP TCP_NODELAY].all? { |c| Socket.const_defined? c }
347
- def set_tcp_nodelay
348
- @sock.setsockopt(Socket::IPPROTO_TCP, Socket::TCP_NODELAY, 1)
349
- end
350
- else
351
- def set_tcp_nodelay; end
352
- end
353
-
354
- def initialize(sock)
355
- @sock = sock
356
- end
357
-
358
- def connected?
359
- !!@sock
360
- end
361
-
362
- def disconnect
363
- @sock.close
364
- rescue
365
- ensure
366
- @sock = nil
367
- end
368
-
369
- def timeout=(timeout)
370
- @sock.timeout = timeout if @sock.respond_to?(:timeout=)
371
- end
372
-
373
- def write_timeout=(timeout)
374
- @sock.write_timeout = timeout
375
- end
376
-
377
- def write(command)
378
- @sock.write(build_command(command))
379
- end
380
-
381
- def read
382
- line = @sock.gets
383
- reply_type = line.slice!(0, 1)
384
- format_reply(reply_type, line)
385
- rescue Errno::EAGAIN
386
- raise TimeoutError
387
- rescue OpenSSL::SSL::SSLError => ssl_error
388
- if ssl_error.message.match?(/SSL_read: unexpected eof while reading/i)
389
- raise EOFError, ssl_error.message
390
- else
391
- raise
392
- end
393
- end
394
-
395
- def format_reply(reply_type, line)
396
- case reply_type
397
- when MINUS then format_error_reply(line)
398
- when PLUS then format_status_reply(line)
399
- when COLON then format_integer_reply(line)
400
- when DOLLAR then format_bulk_reply(line)
401
- when ASTERISK then format_multi_bulk_reply(line)
402
- else raise ProtocolError, reply_type
403
- end
404
- end
405
-
406
- def format_error_reply(line)
407
- CommandError.new(line.strip)
408
- end
409
-
410
- def format_status_reply(line)
411
- line.strip
412
- end
413
-
414
- def format_integer_reply(line)
415
- line.to_i
416
- end
417
-
418
- def format_bulk_reply(line)
419
- bulklen = line.to_i
420
- return if bulklen == -1
421
-
422
- reply = encode(@sock.read(bulklen))
423
- @sock.read(2) # Discard CRLF.
424
- reply
425
- end
426
-
427
- def format_multi_bulk_reply(line)
428
- n = line.to_i
429
- return if n == -1
430
-
431
- Array.new(n) { read }
432
- end
433
- end
434
- end
435
- end
436
-
437
- Redis::Connection.drivers << Redis::Connection::Ruby
@@ -1,148 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require "redis/connection/registry"
4
- require "redis/connection/command_helper"
5
- require "redis/errors"
6
-
7
- require "em-synchrony"
8
- require "hiredis/reader"
9
-
10
- ::Redis.deprecate!(
11
- "The redis synchrony driver is deprecated and will be removed in redis-rb 5.0.0. " \
12
- "We're looking for people to maintain it as a separate gem, see https://github.com/redis/redis-rb/issues/915"
13
- )
14
-
15
- class Redis
16
- module Connection
17
- class RedisClient < EventMachine::Connection
18
- include EventMachine::Deferrable
19
-
20
- attr_accessor :timeout
21
-
22
- def post_init
23
- @req = nil
24
- @connected = false
25
- @reader = ::Hiredis::Reader.new
26
- end
27
-
28
- def connection_completed
29
- @connected = true
30
- succeed
31
- end
32
-
33
- def connected?
34
- @connected
35
- end
36
-
37
- def receive_data(data)
38
- @reader.feed(data)
39
-
40
- loop do
41
- begin
42
- reply = @reader.gets
43
- rescue RuntimeError => err
44
- @req.fail [:error, ProtocolError.new(err.message)]
45
- break
46
- end
47
-
48
- break if reply == false
49
-
50
- reply = CommandError.new(reply.message) if reply.is_a?(RuntimeError)
51
- @req.succeed [:reply, reply]
52
- end
53
- end
54
-
55
- def read
56
- @req = EventMachine::DefaultDeferrable.new
57
- @req.timeout(@timeout, :timeout) if @timeout > 0
58
- EventMachine::Synchrony.sync @req
59
- end
60
-
61
- def send(data)
62
- callback { send_data data }
63
- end
64
-
65
- def unbind
66
- @connected = false
67
- if @req
68
- @req.fail [:error, Errno::ECONNRESET]
69
- @req = nil
70
- else
71
- fail
72
- end
73
- end
74
- end
75
-
76
- class Synchrony
77
- include Redis::Connection::CommandHelper
78
-
79
- def self.connect(config)
80
- if config[:scheme] == "unix"
81
- begin
82
- conn = EventMachine.connect_unix_domain(config[:path], RedisClient)
83
- rescue RuntimeError => e
84
- if e.message == "no connection"
85
- raise Errno::ECONNREFUSED
86
- else
87
- raise e
88
- end
89
- end
90
- elsif config[:scheme] == "rediss" || config[:ssl]
91
- raise NotImplementedError, "SSL not supported by synchrony driver"
92
- else
93
- conn = EventMachine.connect(config[:host], config[:port], RedisClient) do |c|
94
- c.pending_connect_timeout = [config[:connect_timeout], 0.1].max
95
- end
96
- end
97
-
98
- fiber = Fiber.current
99
- conn.callback { fiber.resume }
100
- conn.errback { fiber.resume :refused }
101
-
102
- raise Errno::ECONNREFUSED if Fiber.yield == :refused
103
-
104
- instance = new(conn)
105
- instance.timeout = config[:read_timeout]
106
- instance
107
- end
108
-
109
- def initialize(connection)
110
- @connection = connection
111
- end
112
-
113
- def connected?
114
- @connection&.connected?
115
- end
116
-
117
- def timeout=(timeout)
118
- @connection.timeout = timeout
119
- end
120
-
121
- def disconnect
122
- @connection.close_connection
123
- @connection = nil
124
- end
125
-
126
- def write(command)
127
- @connection.send(build_command(command))
128
- end
129
-
130
- def read
131
- type, payload = @connection.read
132
-
133
- case type
134
- when :reply
135
- payload
136
- when :error
137
- raise payload
138
- when :timeout
139
- raise TimeoutError
140
- else
141
- raise "Unknown type #{type.inspect}"
142
- end
143
- end
144
- end
145
- end
146
- end
147
-
148
- Redis::Connection.drivers << Redis::Connection::Synchrony
@@ -1,11 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require "redis/connection/registry"
4
-
5
- # If a connection driver was required before this file, the array
6
- # Redis::Connection.drivers will contain one or more classes. The last driver
7
- # in this array will be used as default driver. If this array is empty, we load
8
- # the plain Ruby driver as our default. Another driver can be required at a
9
- # later point in time, causing it to be the last element of the #drivers array
10
- # and therefore be chosen by default.
11
- require_relative "connection/ruby" if Redis::Connection.drivers.empty?