redis 4.8.1 → 5.4.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 (44) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +82 -0
  3. data/README.md +125 -162
  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 +13 -6
  10. data/lib/redis/commands/hyper_log_log.rb +1 -1
  11. data/lib/redis/commands/keys.rb +27 -23
  12. data/lib/redis/commands/lists.rb +74 -25
  13. data/lib/redis/commands/pubsub.rb +34 -25
  14. data/lib/redis/commands/server.rb +15 -15
  15. data/lib/redis/commands/sets.rb +35 -40
  16. data/lib/redis/commands/sorted_sets.rb +128 -18
  17. data/lib/redis/commands/streams.rb +48 -21
  18. data/lib/redis/commands/strings.rb +18 -17
  19. data/lib/redis/commands/transactions.rb +7 -31
  20. data/lib/redis/commands.rb +11 -12
  21. data/lib/redis/distributed.rb +136 -72
  22. data/lib/redis/errors.rb +15 -50
  23. data/lib/redis/hash_ring.rb +26 -26
  24. data/lib/redis/pipeline.rb +47 -222
  25. data/lib/redis/subscribe.rb +50 -14
  26. data/lib/redis/version.rb +1 -1
  27. data/lib/redis.rb +77 -184
  28. metadata +10 -57
  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?