redis 4.1.0 → 4.5.1
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 +5 -5
- data/CHANGELOG.md +107 -0
- data/README.md +81 -17
- data/lib/redis/client.rb +137 -82
- data/lib/redis/cluster/command_loader.rb +6 -7
- data/lib/redis/cluster/node.rb +5 -1
- data/lib/redis/cluster/node_key.rb +3 -7
- data/lib/redis/cluster/option.rb +30 -14
- data/lib/redis/cluster/slot.rb +30 -13
- data/lib/redis/cluster/slot_loader.rb +4 -4
- data/lib/redis/cluster.rb +22 -17
- data/lib/redis/connection/command_helper.rb +5 -2
- data/lib/redis/connection/hiredis.rb +4 -3
- data/lib/redis/connection/registry.rb +2 -1
- data/lib/redis/connection/ruby.rb +121 -105
- data/lib/redis/connection/synchrony.rb +9 -4
- data/lib/redis/connection.rb +2 -0
- data/lib/redis/distributed.rb +170 -68
- data/lib/redis/errors.rb +2 -0
- data/lib/redis/hash_ring.rb +15 -14
- data/lib/redis/pipeline.rb +46 -8
- data/lib/redis/subscribe.rb +11 -12
- data/lib/redis/version.rb +3 -1
- data/lib/redis.rb +808 -476
- metadata +15 -25
data/lib/redis/cluster/slot.rb
CHANGED
@@ -1,7 +1,5 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
require 'set'
|
4
|
-
|
5
3
|
class Redis
|
6
4
|
class Cluster
|
7
5
|
# Keep slot and node key map for Redis Cluster Client
|
@@ -28,11 +26,20 @@ class Redis
|
|
28
26
|
return nil unless exists?(slot)
|
29
27
|
return find_node_key_of_master(slot) if replica_disabled?
|
30
28
|
|
31
|
-
@map[slot][:slaves].
|
29
|
+
@map[slot][:slaves].sample
|
32
30
|
end
|
33
31
|
|
34
32
|
def put(slot, node_key)
|
35
|
-
|
33
|
+
# Since we're sharing a hash for build_slot_node_key_map, duplicate it
|
34
|
+
# if it already exists instead of preserving as-is.
|
35
|
+
@map[slot] = @map[slot] ? @map[slot].dup : { master: nil, slaves: [] }
|
36
|
+
|
37
|
+
if master?(node_key)
|
38
|
+
@map[slot][:master] = node_key
|
39
|
+
elsif !@map[slot][:slaves].include?(node_key)
|
40
|
+
@map[slot][:slaves] << node_key
|
41
|
+
end
|
42
|
+
|
36
43
|
nil
|
37
44
|
end
|
38
45
|
|
@@ -50,19 +57,29 @@ class Redis
|
|
50
57
|
@node_flags[node_key] == ROLE_SLAVE
|
51
58
|
end
|
52
59
|
|
60
|
+
# available_slots is mapping of node_key to list of slot ranges
|
53
61
|
def build_slot_node_key_map(available_slots)
|
54
|
-
|
55
|
-
|
62
|
+
by_ranges = {}
|
63
|
+
available_slots.each do |node_key, slots_arr|
|
64
|
+
by_ranges[slots_arr] ||= { master: nil, slaves: [] }
|
65
|
+
|
66
|
+
if master?(node_key)
|
67
|
+
by_ranges[slots_arr][:master] = node_key
|
68
|
+
elsif !by_ranges[slots_arr][:slaves].include?(node_key)
|
69
|
+
by_ranges[slots_arr][:slaves] << node_key
|
70
|
+
end
|
56
71
|
end
|
57
|
-
end
|
58
72
|
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
73
|
+
by_slot = {}
|
74
|
+
by_ranges.each do |slots_arr, nodes|
|
75
|
+
slots_arr.each do |slots|
|
76
|
+
slots.each do |slot|
|
77
|
+
by_slot[slot] = nodes
|
78
|
+
end
|
79
|
+
end
|
65
80
|
end
|
81
|
+
|
82
|
+
by_slot
|
66
83
|
end
|
67
84
|
end
|
68
85
|
end
|
@@ -13,7 +13,7 @@ class Redis
|
|
13
13
|
info = {}
|
14
14
|
|
15
15
|
nodes.each do |node|
|
16
|
-
info =
|
16
|
+
info = fetch_slot_info(node)
|
17
17
|
info.empty? ? next : break
|
18
18
|
end
|
19
19
|
|
@@ -23,9 +23,10 @@ class Redis
|
|
23
23
|
end
|
24
24
|
|
25
25
|
def fetch_slot_info(node)
|
26
|
+
hash_with_default_arr = Hash.new { |h, k| h[k] = [] }
|
26
27
|
node.call(%i[cluster slots])
|
27
|
-
.
|
28
|
-
.
|
28
|
+
.flat_map { |arr| parse_slot_info(arr, default_ip: node.host) }
|
29
|
+
.each_with_object(hash_with_default_arr) { |arr, h| h[arr[0]] << arr[1] }
|
29
30
|
rescue CannotConnectError, ConnectionError, CommandError
|
30
31
|
{} # can retry on another node
|
31
32
|
end
|
@@ -34,7 +35,6 @@ class Redis
|
|
34
35
|
first_slot, last_slot = arr[0..1]
|
35
36
|
slot_range = (first_slot..last_slot).freeze
|
36
37
|
arr[2..-1].map { |addr| [stringify_node_key(addr, default_ip), slot_range] }
|
37
|
-
.flatten
|
38
38
|
end
|
39
39
|
|
40
40
|
def stringify_node_key(arr, default_ip)
|
data/lib/redis/cluster.rb
CHANGED
@@ -78,10 +78,13 @@ class Redis
|
|
78
78
|
end
|
79
79
|
|
80
80
|
def call_pipeline(pipeline)
|
81
|
-
node_keys
|
82
|
-
|
83
|
-
|
84
|
-
|
81
|
+
node_keys = pipeline.commands.map { |cmd| find_node_key(cmd, primary_only: true) }.compact.uniq
|
82
|
+
if node_keys.size > 1
|
83
|
+
raise(CrossSlotPipeliningError,
|
84
|
+
pipeline.commands.map { |cmd| @command.extract_first_key(cmd) }.reject(&:empty?).uniq)
|
85
|
+
end
|
86
|
+
|
87
|
+
try_send(find_node(node_keys.first), :call_pipeline, pipeline)
|
85
88
|
end
|
86
89
|
|
87
90
|
def call_with_timeout(command, timeout, &block)
|
@@ -112,12 +115,11 @@ class Redis
|
|
112
115
|
node = Node.new(option.per_node_key)
|
113
116
|
available_slots = SlotLoader.load(node)
|
114
117
|
node_flags = NodeLoader.load_flags(node)
|
115
|
-
|
116
|
-
option.update_node(available_node_urls)
|
118
|
+
option.update_node(available_slots.keys.map { |k| NodeKey.optionize(k) })
|
117
119
|
[Node.new(option.per_node_key, node_flags, option.use_replica?),
|
118
120
|
Slot.new(available_slots, node_flags, option.use_replica?)]
|
119
121
|
ensure
|
120
|
-
node
|
122
|
+
node&.each(&:disconnect)
|
121
123
|
end
|
122
124
|
|
123
125
|
def fetch_command_details(nodes)
|
@@ -128,7 +130,7 @@ class Redis
|
|
128
130
|
def send_command(command, &block)
|
129
131
|
cmd = command.first.to_s.downcase
|
130
132
|
case cmd
|
131
|
-
when 'auth', 'bgrewriteaof', 'bgsave', 'quit', 'save'
|
133
|
+
when 'acl', 'auth', 'bgrewriteaof', 'bgsave', 'quit', 'save'
|
132
134
|
@node.call_all(command, &block).first
|
133
135
|
when 'flushall', 'flushdb'
|
134
136
|
@node.call_master(command, &block).first
|
@@ -216,9 +218,14 @@ class Redis
|
|
216
218
|
node.public_send(method_name, *args, &block)
|
217
219
|
rescue CommandError => err
|
218
220
|
if err.message.start_with?('MOVED')
|
219
|
-
|
221
|
+
raise if retry_count <= 0
|
222
|
+
|
223
|
+
node = assign_redirection_node(err.message)
|
224
|
+
retry_count -= 1
|
225
|
+
retry
|
220
226
|
elsif err.message.start_with?('ASK')
|
221
227
|
raise if retry_count <= 0
|
228
|
+
|
222
229
|
node = assign_asking_node(err.message)
|
223
230
|
node.call(%i[asking])
|
224
231
|
retry_count -= 1
|
@@ -226,6 +233,9 @@ class Redis
|
|
226
233
|
else
|
227
234
|
raise
|
228
235
|
end
|
236
|
+
rescue CannotConnectError
|
237
|
+
update_cluster_info!
|
238
|
+
raise
|
229
239
|
end
|
230
240
|
|
231
241
|
def assign_redirection_node(err_msg)
|
@@ -245,14 +255,14 @@ class Redis
|
|
245
255
|
find_node(node_key)
|
246
256
|
end
|
247
257
|
|
248
|
-
def find_node_key(command)
|
258
|
+
def find_node_key(command, primary_only: false)
|
249
259
|
key = @command.extract_first_key(command)
|
250
260
|
return if key.empty?
|
251
261
|
|
252
262
|
slot = KeySlotConverter.convert(key)
|
253
263
|
return unless @slot.exists?(slot)
|
254
264
|
|
255
|
-
if @command.should_send_to_master?(command)
|
265
|
+
if @command.should_send_to_master?(command) || primary_only
|
256
266
|
@slot.find_node_key_of_master(slot)
|
257
267
|
else
|
258
268
|
@slot.find_node_key_of_slave(slot)
|
@@ -261,6 +271,7 @@ class Redis
|
|
261
271
|
|
262
272
|
def find_node(node_key)
|
263
273
|
return @node.sample if node_key.nil?
|
274
|
+
|
264
275
|
@node.find_by(node_key)
|
265
276
|
rescue Node::ReloadNeeded
|
266
277
|
update_cluster_info!(node_key)
|
@@ -276,11 +287,5 @@ class Redis
|
|
276
287
|
@node.map(&:disconnect)
|
277
288
|
@node, @slot = fetch_cluster_info!(@option)
|
278
289
|
end
|
279
|
-
|
280
|
-
def extract_keys_in_pipeline(pipeline)
|
281
|
-
node_keys = pipeline.commands.map { |cmd| find_node_key(cmd) }.compact.uniq
|
282
|
-
command_keys = pipeline.commands.map { |cmd| @command.extract_first_key(cmd) }.reject(&:empty?)
|
283
|
-
[node_keys, command_keys]
|
284
|
-
end
|
285
290
|
end
|
286
291
|
end
|
@@ -1,7 +1,8 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
class Redis
|
2
4
|
module Connection
|
3
5
|
module CommandHelper
|
4
|
-
|
5
6
|
COMMAND_DELIMITER = "\r\n"
|
6
7
|
|
7
8
|
def build_command(args)
|
@@ -11,11 +12,13 @@ class Redis
|
|
11
12
|
if i.is_a? Array
|
12
13
|
i.each do |j|
|
13
14
|
j = j.to_s
|
15
|
+
j = j.encoding == Encoding::BINARY ? j : j.b
|
14
16
|
command << "$#{j.bytesize}"
|
15
17
|
command << j
|
16
18
|
end
|
17
19
|
else
|
18
20
|
i = i.to_s
|
21
|
+
i = i.encoding == Encoding::BINARY ? i : i.b
|
19
22
|
command << "$#{i.bytesize}"
|
20
23
|
command << i
|
21
24
|
end
|
@@ -28,7 +31,7 @@ class Redis
|
|
28
31
|
command.join(COMMAND_DELIMITER)
|
29
32
|
end
|
30
33
|
|
31
|
-
|
34
|
+
protected
|
32
35
|
|
33
36
|
def encode(string)
|
34
37
|
string.force_encoding(Encoding.default_external)
|
@@ -1,3 +1,5 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
require_relative "registry"
|
2
4
|
require_relative "../errors"
|
3
5
|
require "hiredis/connection"
|
@@ -6,7 +8,6 @@ require "timeout"
|
|
6
8
|
class Redis
|
7
9
|
module Connection
|
8
10
|
class Hiredis
|
9
|
-
|
10
11
|
def self.connect(config)
|
11
12
|
connection = ::Hiredis::Connection.new
|
12
13
|
connect_timeout = (config.fetch(:connect_timeout, 0) * 1_000_000).to_i
|
@@ -31,7 +32,7 @@ class Redis
|
|
31
32
|
end
|
32
33
|
|
33
34
|
def connected?
|
34
|
-
@connection
|
35
|
+
@connection&.connected?
|
35
36
|
end
|
36
37
|
|
37
38
|
def timeout=(timeout)
|
@@ -57,7 +58,7 @@ class Redis
|
|
57
58
|
rescue Errno::EAGAIN
|
58
59
|
raise TimeoutError
|
59
60
|
rescue RuntimeError => err
|
60
|
-
raise ProtocolError
|
61
|
+
raise ProtocolError, err.message
|
61
62
|
end
|
62
63
|
end
|
63
64
|
end
|
@@ -1,6 +1,7 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
class Redis
|
2
4
|
module Connection
|
3
|
-
|
4
5
|
# Store a list of loaded connection drivers in the Connection module.
|
5
6
|
# Redis::Client uses the last required driver by default, and will be aware
|
6
7
|
# of the loaded connection drivers if the user chooses to override the
|
@@ -1,3 +1,5 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
require_relative "registry"
|
2
4
|
require_relative "command_helper"
|
3
5
|
require_relative "../errors"
|
@@ -13,108 +15,85 @@ end
|
|
13
15
|
class Redis
|
14
16
|
module Connection
|
15
17
|
module SocketMixin
|
16
|
-
|
17
|
-
CRLF = "\r\n".freeze
|
18
|
+
CRLF = "\r\n"
|
18
19
|
|
19
20
|
def initialize(*args)
|
20
21
|
super(*args)
|
21
22
|
|
22
23
|
@timeout = @write_timeout = nil
|
23
|
-
@buffer = "".
|
24
|
+
@buffer = "".b
|
24
25
|
end
|
25
26
|
|
26
27
|
def timeout=(timeout)
|
27
|
-
if timeout && timeout > 0
|
28
|
-
@timeout = timeout
|
29
|
-
else
|
30
|
-
@timeout = nil
|
31
|
-
end
|
28
|
+
@timeout = (timeout if timeout && timeout > 0)
|
32
29
|
end
|
33
30
|
|
34
31
|
def write_timeout=(timeout)
|
35
|
-
if timeout && timeout > 0
|
36
|
-
@write_timeout = timeout
|
37
|
-
else
|
38
|
-
@write_timeout = nil
|
39
|
-
end
|
32
|
+
@write_timeout = (timeout if timeout && timeout > 0)
|
40
33
|
end
|
41
34
|
|
42
35
|
def read(nbytes)
|
43
36
|
result = @buffer.slice!(0, nbytes)
|
44
37
|
|
45
|
-
|
46
|
-
|
47
|
-
end
|
38
|
+
buffer = String.new(capacity: nbytes, encoding: Encoding::ASCII_8BIT)
|
39
|
+
result << _read_from_socket(nbytes - result.bytesize, buffer) while result.bytesize < nbytes
|
48
40
|
|
49
41
|
result
|
50
42
|
end
|
51
43
|
|
52
44
|
def gets
|
53
|
-
crlf = nil
|
54
|
-
|
55
|
-
while (crlf = @buffer.index(CRLF)) == nil
|
56
|
-
@buffer << _read_from_socket(1024)
|
45
|
+
while (crlf = @buffer.index(CRLF)).nil?
|
46
|
+
@buffer << _read_from_socket(16_384)
|
57
47
|
end
|
58
48
|
|
59
49
|
@buffer.slice!(0, crlf + CRLF.bytesize)
|
60
50
|
end
|
61
51
|
|
62
|
-
def _read_from_socket(nbytes)
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
raise Redis::TimeoutError
|
78
|
-
end
|
79
|
-
end
|
80
|
-
|
81
|
-
rescue EOFError
|
82
|
-
raise Errno::ECONNRESET
|
83
|
-
end
|
84
|
-
|
85
|
-
def _write_to_socket(data)
|
86
|
-
begin
|
87
|
-
write_nonblock(data)
|
88
|
-
|
89
|
-
rescue IO::WaitWritable
|
90
|
-
if IO.select(nil, [self], nil, @write_timeout)
|
91
|
-
retry
|
92
|
-
else
|
93
|
-
raise Redis::TimeoutError
|
94
|
-
end
|
95
|
-
rescue IO::WaitReadable
|
96
|
-
if IO.select([self], nil, nil, @write_timeout)
|
97
|
-
retry
|
98
|
-
else
|
99
|
-
raise Redis::TimeoutError
|
52
|
+
def _read_from_socket(nbytes, buffer = nil)
|
53
|
+
loop do
|
54
|
+
case chunk = read_nonblock(nbytes, buffer, exception: false)
|
55
|
+
when :wait_readable
|
56
|
+
unless wait_readable(@timeout)
|
57
|
+
raise Redis::TimeoutError
|
58
|
+
end
|
59
|
+
when :wait_writable
|
60
|
+
unless wait_writable(@timeout)
|
61
|
+
raise Redis::TimeoutError
|
62
|
+
end
|
63
|
+
when nil
|
64
|
+
raise Errno::ECONNRESET
|
65
|
+
when String
|
66
|
+
return chunk
|
100
67
|
end
|
101
68
|
end
|
102
|
-
|
103
|
-
rescue EOFError
|
104
|
-
raise Errno::ECONNRESET
|
105
69
|
end
|
106
70
|
|
107
|
-
def write(
|
108
|
-
return super(
|
71
|
+
def write(buffer)
|
72
|
+
return super(buffer) unless @write_timeout
|
109
73
|
|
110
|
-
|
111
|
-
|
74
|
+
bytes_to_write = buffer.bytesize
|
75
|
+
total_bytes_written = 0
|
112
76
|
loop do
|
113
|
-
|
77
|
+
case bytes_written = write_nonblock(buffer, exception: false)
|
78
|
+
when :wait_readable
|
79
|
+
unless wait_readable(@write_timeout)
|
80
|
+
raise Redis::TimeoutError
|
81
|
+
end
|
82
|
+
when :wait_writable
|
83
|
+
unless wait_writable(@write_timeout)
|
84
|
+
raise Redis::TimeoutError
|
85
|
+
end
|
86
|
+
when nil
|
87
|
+
raise Errno::ECONNRESET
|
88
|
+
when Integer
|
89
|
+
total_bytes_written += bytes_written
|
90
|
+
|
91
|
+
if total_bytes_written >= bytes_to_write
|
92
|
+
return total_bytes_written
|
93
|
+
end
|
114
94
|
|
115
|
-
|
116
|
-
|
117
|
-
data = data.byteslice(count..-1)
|
95
|
+
buffer = buffer.byteslice(bytes_written..-1)
|
96
|
+
end
|
118
97
|
end
|
119
98
|
end
|
120
99
|
end
|
@@ -124,7 +103,6 @@ class Redis
|
|
124
103
|
require "timeout"
|
125
104
|
|
126
105
|
class TCPSocket < ::TCPSocket
|
127
|
-
|
128
106
|
include SocketMixin
|
129
107
|
|
130
108
|
def self.connect(host, port, timeout)
|
@@ -140,7 +118,6 @@ class Redis
|
|
140
118
|
if defined?(::UNIXSocket)
|
141
119
|
|
142
120
|
class UNIXSocket < ::UNIXSocket
|
143
|
-
|
144
121
|
include SocketMixin
|
145
122
|
|
146
123
|
def self.connect(path, timeout)
|
@@ -152,13 +129,12 @@ class Redis
|
|
152
129
|
raise TimeoutError
|
153
130
|
end
|
154
131
|
|
155
|
-
# JRuby raises Errno::EAGAIN on #read_nonblock even when
|
132
|
+
# JRuby raises Errno::EAGAIN on #read_nonblock even when it
|
156
133
|
# says it is readable (1.6.6, in both 1.8 and 1.9 mode).
|
157
134
|
# Use the blocking #readpartial method instead.
|
158
135
|
|
159
136
|
def _read_from_socket(nbytes)
|
160
137
|
readpartial(nbytes)
|
161
|
-
|
162
138
|
rescue EOFError
|
163
139
|
raise Errno::ECONNRESET
|
164
140
|
end
|
@@ -169,19 +145,16 @@ class Redis
|
|
169
145
|
else
|
170
146
|
|
171
147
|
class TCPSocket < ::Socket
|
172
|
-
|
173
148
|
include SocketMixin
|
174
149
|
|
175
|
-
def self.connect_addrinfo(
|
176
|
-
sock = new(::Socket.const_get(
|
177
|
-
sockaddr = ::Socket.pack_sockaddr_in(port,
|
150
|
+
def self.connect_addrinfo(addrinfo, port, timeout)
|
151
|
+
sock = new(::Socket.const_get(addrinfo[0]), Socket::SOCK_STREAM, 0)
|
152
|
+
sockaddr = ::Socket.pack_sockaddr_in(port, addrinfo[3])
|
178
153
|
|
179
154
|
begin
|
180
155
|
sock.connect_nonblock(sockaddr)
|
181
156
|
rescue Errno::EINPROGRESS
|
182
|
-
|
183
|
-
raise TimeoutError
|
184
|
-
end
|
157
|
+
raise TimeoutError unless sock.wait_writable(timeout)
|
185
158
|
|
186
159
|
begin
|
187
160
|
sock.connect_nonblock(sockaddr)
|
@@ -220,14 +193,13 @@ class Redis
|
|
220
193
|
return connect_addrinfo(ai, port, timeout)
|
221
194
|
rescue SystemCallError
|
222
195
|
# Raise if this was our last attempt.
|
223
|
-
raise if addrinfo.length == i+1
|
196
|
+
raise if addrinfo.length == i + 1
|
224
197
|
end
|
225
198
|
end
|
226
199
|
end
|
227
200
|
end
|
228
201
|
|
229
202
|
class UNIXSocket < ::Socket
|
230
|
-
|
231
203
|
include SocketMixin
|
232
204
|
|
233
205
|
def self.connect(path, timeout)
|
@@ -237,9 +209,7 @@ class Redis
|
|
237
209
|
begin
|
238
210
|
sock.connect_nonblock(sockaddr)
|
239
211
|
rescue Errno::EINPROGRESS
|
240
|
-
|
241
|
-
raise TimeoutError
|
242
|
-
end
|
212
|
+
raise TimeoutError unless sock.wait_writable(timeout)
|
243
213
|
|
244
214
|
begin
|
245
215
|
sock.connect_nonblock(sockaddr)
|
@@ -257,18 +227,56 @@ class Redis
|
|
257
227
|
class SSLSocket < ::OpenSSL::SSL::SSLSocket
|
258
228
|
include SocketMixin
|
259
229
|
|
230
|
+
unless method_defined?(:wait_readable)
|
231
|
+
def wait_readable(timeout = nil)
|
232
|
+
to_io.wait_readable(timeout)
|
233
|
+
end
|
234
|
+
end
|
235
|
+
|
236
|
+
unless method_defined?(:wait_writable)
|
237
|
+
def wait_writable(timeout = nil)
|
238
|
+
to_io.wait_writable(timeout)
|
239
|
+
end
|
240
|
+
end
|
241
|
+
|
260
242
|
def self.connect(host, port, timeout, ssl_params)
|
261
243
|
# Note: this is using Redis::Connection::TCPSocket
|
262
244
|
tcp_sock = TCPSocket.connect(host, port, timeout)
|
263
245
|
|
264
246
|
ctx = OpenSSL::SSL::SSLContext.new
|
265
|
-
|
247
|
+
|
248
|
+
# The provided parameters are merged into OpenSSL::SSL::SSLContext::DEFAULT_PARAMS
|
249
|
+
ctx.set_params(ssl_params || {})
|
266
250
|
|
267
251
|
ssl_sock = new(tcp_sock, ctx)
|
268
252
|
ssl_sock.hostname = host
|
269
|
-
ssl_sock.connect
|
270
253
|
|
271
|
-
|
254
|
+
begin
|
255
|
+
# Initiate the socket connection in the background. If it doesn't fail
|
256
|
+
# immediately it will raise an IO::WaitWritable (Errno::EINPROGRESS)
|
257
|
+
# indicating the connection is in progress.
|
258
|
+
# Unlike waiting for a tcp socket to connect, you can't time out ssl socket
|
259
|
+
# connections during the connect phase properly, because IO.select only partially works.
|
260
|
+
# Instead, you have to retry.
|
261
|
+
ssl_sock.connect_nonblock
|
262
|
+
rescue Errno::EAGAIN, Errno::EWOULDBLOCK, IO::WaitReadable
|
263
|
+
if ssl_sock.wait_readable(timeout)
|
264
|
+
retry
|
265
|
+
else
|
266
|
+
raise TimeoutError
|
267
|
+
end
|
268
|
+
rescue IO::WaitWritable
|
269
|
+
if ssl_sock.wait_writable(timeout)
|
270
|
+
retry
|
271
|
+
else
|
272
|
+
raise TimeoutError
|
273
|
+
end
|
274
|
+
end
|
275
|
+
|
276
|
+
unless ctx.verify_mode == OpenSSL::SSL::VERIFY_NONE || (
|
277
|
+
ctx.respond_to?(:verify_hostname) &&
|
278
|
+
!ctx.verify_hostname
|
279
|
+
)
|
272
280
|
ssl_sock.post_connection_check(host)
|
273
281
|
end
|
274
282
|
|
@@ -280,15 +288,16 @@ class Redis
|
|
280
288
|
class Ruby
|
281
289
|
include Redis::Connection::CommandHelper
|
282
290
|
|
283
|
-
MINUS = "-"
|
284
|
-
PLUS = "+"
|
285
|
-
COLON = ":"
|
286
|
-
DOLLAR = "$"
|
287
|
-
ASTERISK = "*"
|
291
|
+
MINUS = "-"
|
292
|
+
PLUS = "+"
|
293
|
+
COLON = ":"
|
294
|
+
DOLLAR = "$"
|
295
|
+
ASTERISK = "*"
|
288
296
|
|
289
297
|
def self.connect(config)
|
290
298
|
if config[:scheme] == "unix"
|
291
299
|
raise ArgumentError, "SSL incompatible with unix sockets" if config[:ssl]
|
300
|
+
|
292
301
|
sock = UNIXSocket.connect(config[:path], config[:connect_timeout])
|
293
302
|
elsif config[:scheme] == "rediss" || config[:ssl]
|
294
303
|
sock = SSLSocket.connect(config[:host], config[:port], config[:connect_timeout], config[:ssl_params])
|
@@ -300,10 +309,11 @@ class Redis
|
|
300
309
|
instance.timeout = config[:read_timeout]
|
301
310
|
instance.write_timeout = config[:write_timeout]
|
302
311
|
instance.set_tcp_keepalive config[:tcp_keepalive]
|
312
|
+
instance.set_tcp_nodelay if sock.is_a? TCPSocket
|
303
313
|
instance
|
304
314
|
end
|
305
315
|
|
306
|
-
if [
|
316
|
+
if %i[SOL_SOCKET SO_KEEPALIVE SOL_TCP TCP_KEEPIDLE TCP_KEEPINTVL TCP_KEEPCNT].all? { |c| Socket.const_defined? c }
|
307
317
|
def set_tcp_keepalive(keepalive)
|
308
318
|
return unless keepalive.is_a?(Hash)
|
309
319
|
|
@@ -315,14 +325,13 @@ class Redis
|
|
315
325
|
|
316
326
|
def get_tcp_keepalive
|
317
327
|
{
|
318
|
-
:
|
319
|
-
:
|
320
|
-
:
|
328
|
+
time: @sock.getsockopt(Socket::SOL_TCP, Socket::TCP_KEEPIDLE).int,
|
329
|
+
intvl: @sock.getsockopt(Socket::SOL_TCP, Socket::TCP_KEEPINTVL).int,
|
330
|
+
probes: @sock.getsockopt(Socket::SOL_TCP, Socket::TCP_KEEPCNT).int
|
321
331
|
}
|
322
332
|
end
|
323
333
|
else
|
324
|
-
def set_tcp_keepalive(keepalive)
|
325
|
-
end
|
334
|
+
def set_tcp_keepalive(keepalive); end
|
326
335
|
|
327
336
|
def get_tcp_keepalive
|
328
337
|
{
|
@@ -330,12 +339,21 @@ class Redis
|
|
330
339
|
end
|
331
340
|
end
|
332
341
|
|
342
|
+
# disables Nagle's Algorithm, prevents multiple round trips with MULTI
|
343
|
+
if %i[IPPROTO_TCP TCP_NODELAY].all? { |c| Socket.const_defined? c }
|
344
|
+
def set_tcp_nodelay
|
345
|
+
@sock.setsockopt(Socket::IPPROTO_TCP, Socket::TCP_NODELAY, 1)
|
346
|
+
end
|
347
|
+
else
|
348
|
+
def set_tcp_nodelay; end
|
349
|
+
end
|
350
|
+
|
333
351
|
def initialize(sock)
|
334
352
|
@sock = sock
|
335
353
|
end
|
336
354
|
|
337
355
|
def connected?
|
338
|
-
|
356
|
+
!!@sock
|
339
357
|
end
|
340
358
|
|
341
359
|
def disconnect
|
@@ -346,9 +364,7 @@ class Redis
|
|
346
364
|
end
|
347
365
|
|
348
366
|
def timeout=(timeout)
|
349
|
-
if @sock.respond_to?(:timeout=)
|
350
|
-
@sock.timeout = timeout
|
351
|
-
end
|
367
|
+
@sock.timeout = timeout if @sock.respond_to?(:timeout=)
|
352
368
|
end
|
353
369
|
|
354
370
|
def write_timeout=(timeout)
|
@@ -363,7 +379,6 @@ class Redis
|
|
363
379
|
line = @sock.gets
|
364
380
|
reply_type = line.slice!(0, 1)
|
365
381
|
format_reply(reply_type, line)
|
366
|
-
|
367
382
|
rescue Errno::EAGAIN
|
368
383
|
raise TimeoutError
|
369
384
|
end
|
@@ -375,7 +390,7 @@ class Redis
|
|
375
390
|
when COLON then format_integer_reply(line)
|
376
391
|
when DOLLAR then format_bulk_reply(line)
|
377
392
|
when ASTERISK then format_multi_bulk_reply(line)
|
378
|
-
else raise ProtocolError
|
393
|
+
else raise ProtocolError, reply_type
|
379
394
|
end
|
380
395
|
end
|
381
396
|
|
@@ -394,6 +409,7 @@ class Redis
|
|
394
409
|
def format_bulk_reply(line)
|
395
410
|
bulklen = line.to_i
|
396
411
|
return if bulklen == -1
|
412
|
+
|
397
413
|
reply = encode(@sock.read(bulklen))
|
398
414
|
@sock.read(2) # Discard CRLF.
|
399
415
|
reply
|