redis-client 0.13.0 → 0.14.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/CHANGELOG.md +6 -0
- data/Gemfile.lock +1 -1
- data/Rakefile +4 -0
- data/lib/redis_client/config.rb +3 -1
- data/lib/redis_client/connection_mixin.rb +15 -2
- data/lib/redis_client/ruby_connection.rb +50 -38
- data/lib/redis_client/sentinel_config.rb +33 -13
- data/lib/redis_client/version.rb +1 -1
- data/lib/redis_client.rb +20 -15
- metadata +2 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: ac50722e5156b95e037fb5fd933cb804a3d52d0d767faa2bbddaccc7cbd38d07
|
4
|
+
data.tar.gz: 0a1645c3788dd195bf3567e14c0ba508b892357991359297c002c84bd2796e2e
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 0c93ddbae9ffdd770589eb6003eee594dc902a8d2698c99c38878b923ac80a91aa6a43892d015d9a4ba52ef19300c95970a8947e25f1844c4c01d4b88ff9d9ae
|
7
|
+
data.tar.gz: a06dd7eaa65ccf8544b4f04f7beffe2580d139783770170fcf76348c5760f78c5baa0d77e734a31111c720df6458bb8112a64960316618b831fdb6059b625963
|
data/CHANGELOG.md
CHANGED
@@ -1,5 +1,11 @@
|
|
1
1
|
# Unreleased
|
2
2
|
|
3
|
+
# 0.14.0
|
4
|
+
|
5
|
+
- hiredis binding now implement GC compaction and write barriers.
|
6
|
+
- hiredis binding now properly release the GVL around `connect(2)`.
|
7
|
+
- hiredis the client memory is now re-used on reconnection when possible to reduce allocation churn.
|
8
|
+
|
3
9
|
# 0.13.0
|
4
10
|
|
5
11
|
- Enable TCP keepalive on redis sockets. It sends a keep alive probe every 15 seconds for 2 minutes. #94.
|
data/Gemfile.lock
CHANGED
data/Rakefile
CHANGED
@@ -26,6 +26,7 @@ namespace :test do
|
|
26
26
|
t.libs << "test"
|
27
27
|
t.libs << "lib"
|
28
28
|
t.test_files = FileList["test/**/*_test.rb"].exclude("test/sentinel/*_test.rb")
|
29
|
+
t.options = '-v' if ENV['CI'] || ENV['VERBOSE']
|
29
30
|
end
|
30
31
|
|
31
32
|
Rake::TestTask.new(:sentinel) do |t|
|
@@ -33,13 +34,16 @@ namespace :test do
|
|
33
34
|
t.libs << "test"
|
34
35
|
t.libs << "lib"
|
35
36
|
t.test_files = FileList["test/sentinel/*_test.rb"]
|
37
|
+
t.options = '-v' if ENV['CI'] || ENV['VERBOSE']
|
36
38
|
end
|
37
39
|
|
38
40
|
Rake::TestTask.new(:hiredis) do |t|
|
39
41
|
t.libs << "test/hiredis"
|
40
42
|
t.libs << "test"
|
43
|
+
t.libs << "hiredis-client/lib"
|
41
44
|
t.libs << "lib"
|
42
45
|
t.test_files = FileList["test/**/*_test.rb"].exclude("test/sentinel/*_test.rb")
|
46
|
+
t.options = '-v' if ENV['CI'] || ENV['VERBOSE']
|
43
47
|
end
|
44
48
|
end
|
45
49
|
|
data/lib/redis_client/config.rb
CHANGED
@@ -6,9 +6,22 @@ class RedisClient
|
|
6
6
|
@pending_reads = 0
|
7
7
|
end
|
8
8
|
|
9
|
+
def reconnect
|
10
|
+
close
|
11
|
+
connect
|
12
|
+
end
|
13
|
+
|
14
|
+
def close
|
15
|
+
@pending_reads = 0
|
16
|
+
nil
|
17
|
+
end
|
18
|
+
|
9
19
|
def revalidate
|
10
|
-
if @pending_reads
|
11
|
-
|
20
|
+
if @pending_reads > 0
|
21
|
+
close
|
22
|
+
false
|
23
|
+
else
|
24
|
+
connected?
|
12
25
|
end
|
13
26
|
end
|
14
27
|
|
@@ -42,44 +42,11 @@ class RedisClient
|
|
42
42
|
|
43
43
|
def initialize(config, connect_timeout:, read_timeout:, write_timeout:)
|
44
44
|
super()
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
else
|
51
|
-
Socket.tcp(config.host, config.port, connect_timeout: connect_timeout)
|
52
|
-
end
|
53
|
-
# disables Nagle's Algorithm, prevents multiple round trips with MULTI
|
54
|
-
sock.setsockopt(Socket::IPPROTO_TCP, Socket::TCP_NODELAY, 1)
|
55
|
-
enable_socket_keep_alive(sock)
|
56
|
-
sock
|
57
|
-
end
|
58
|
-
|
59
|
-
if config.ssl
|
60
|
-
socket = OpenSSL::SSL::SSLSocket.new(socket, config.ssl_context)
|
61
|
-
socket.hostname = config.host
|
62
|
-
loop do
|
63
|
-
case status = socket.connect_nonblock(exception: false)
|
64
|
-
when :wait_readable
|
65
|
-
socket.to_io.wait_readable(connect_timeout) or raise CannotConnectError
|
66
|
-
when :wait_writable
|
67
|
-
socket.to_io.wait_writable(connect_timeout) or raise CannotConnectError
|
68
|
-
when socket
|
69
|
-
break
|
70
|
-
else
|
71
|
-
raise "Unexpected `connect_nonblock` return: #{status.inspect}"
|
72
|
-
end
|
73
|
-
end
|
74
|
-
end
|
75
|
-
|
76
|
-
@io = BufferedIO.new(
|
77
|
-
socket,
|
78
|
-
read_timeout: read_timeout,
|
79
|
-
write_timeout: write_timeout,
|
80
|
-
)
|
81
|
-
rescue SystemCallError, OpenSSL::SSL::SSLError, SocketError => error
|
82
|
-
raise CannotConnectError, error.message, error.backtrace
|
45
|
+
@config = config
|
46
|
+
@connect_timeout = connect_timeout
|
47
|
+
@read_timeout = read_timeout
|
48
|
+
@write_timeout = write_timeout
|
49
|
+
connect
|
83
50
|
end
|
84
51
|
|
85
52
|
def connected?
|
@@ -88,13 +55,16 @@ class RedisClient
|
|
88
55
|
|
89
56
|
def close
|
90
57
|
@io.close
|
58
|
+
super
|
91
59
|
end
|
92
60
|
|
93
61
|
def read_timeout=(timeout)
|
62
|
+
@read_timeout = timeout
|
94
63
|
@io.read_timeout = timeout if @io
|
95
64
|
end
|
96
65
|
|
97
66
|
def write_timeout=(timeout)
|
67
|
+
@write_timeout = timeout
|
98
68
|
@io.write_timeout = timeout if @io
|
99
69
|
end
|
100
70
|
|
@@ -133,6 +103,48 @@ class RedisClient
|
|
133
103
|
|
134
104
|
private
|
135
105
|
|
106
|
+
def connect
|
107
|
+
socket = if @config.path
|
108
|
+
UNIXSocket.new(@config.path)
|
109
|
+
else
|
110
|
+
sock = if SUPPORTS_RESOLV_TIMEOUT
|
111
|
+
Socket.tcp(@config.host, @config.port, connect_timeout: @connect_timeout, resolv_timeout: @connect_timeout)
|
112
|
+
else
|
113
|
+
Socket.tcp(@config.host, @config.port, connect_timeout: @connect_timeout)
|
114
|
+
end
|
115
|
+
# disables Nagle's Algorithm, prevents multiple round trips with MULTI
|
116
|
+
sock.setsockopt(Socket::IPPROTO_TCP, Socket::TCP_NODELAY, 1)
|
117
|
+
enable_socket_keep_alive(sock)
|
118
|
+
sock
|
119
|
+
end
|
120
|
+
|
121
|
+
if @config.ssl
|
122
|
+
socket = OpenSSL::SSL::SSLSocket.new(socket, @config.ssl_context)
|
123
|
+
socket.hostname = @config.host
|
124
|
+
loop do
|
125
|
+
case status = socket.connect_nonblock(exception: false)
|
126
|
+
when :wait_readable
|
127
|
+
socket.to_io.wait_readable(@connect_timeout) or raise CannotConnectError
|
128
|
+
when :wait_writable
|
129
|
+
socket.to_io.wait_writable(@connect_timeout) or raise CannotConnectError
|
130
|
+
when socket
|
131
|
+
break
|
132
|
+
else
|
133
|
+
raise "Unexpected `connect_nonblock` return: #{status.inspect}"
|
134
|
+
end
|
135
|
+
end
|
136
|
+
end
|
137
|
+
|
138
|
+
@io = BufferedIO.new(
|
139
|
+
socket,
|
140
|
+
read_timeout: @read_timeout,
|
141
|
+
write_timeout: @write_timeout,
|
142
|
+
)
|
143
|
+
true
|
144
|
+
rescue SystemCallError, OpenSSL::SSL::SSLError, SocketError => error
|
145
|
+
raise CannotConnectError, error.message, error.backtrace
|
146
|
+
end
|
147
|
+
|
136
148
|
KEEP_ALIVE_INTERVAL = 15 # Same as hiredis defaults
|
137
149
|
KEEP_ALIVE_TTL = 120 # Longer than hiredis defaults
|
138
150
|
KEEP_ALIVE_PROBES = (KEEP_ALIVE_TTL / KEEP_ALIVE_INTERVAL) - 1
|
@@ -13,9 +13,9 @@ class RedisClient
|
|
13
13
|
end
|
14
14
|
|
15
15
|
@to_list_of_hash = @to_hash = nil
|
16
|
-
extra_config = {}
|
16
|
+
@extra_config = {}
|
17
17
|
if client_config[:protocol] == 2
|
18
|
-
extra_config[:protocol] = client_config[:protocol]
|
18
|
+
@extra_config[:protocol] = client_config[:protocol]
|
19
19
|
@to_list_of_hash = lambda do |may_be_a_list|
|
20
20
|
if may_be_a_list.is_a?(Array)
|
21
21
|
may_be_a_list.map { |l| l.each_slice(2).to_h }
|
@@ -26,14 +26,7 @@ class RedisClient
|
|
26
26
|
end
|
27
27
|
|
28
28
|
@name = name
|
29
|
-
@sentinel_configs = sentinels
|
30
|
-
case s
|
31
|
-
when String
|
32
|
-
Config.new(**extra_config, url: s)
|
33
|
-
else
|
34
|
-
Config.new(**extra_config, **s)
|
35
|
-
end
|
36
|
-
end
|
29
|
+
@sentinel_configs = sentinels_to_configs(sentinels)
|
37
30
|
@sentinels = {}.compare_by_identity
|
38
31
|
@role = role
|
39
32
|
@mutex = Mutex.new
|
@@ -93,6 +86,17 @@ class RedisClient
|
|
93
86
|
|
94
87
|
private
|
95
88
|
|
89
|
+
def sentinels_to_configs(sentinels)
|
90
|
+
sentinels.map do |sentinel|
|
91
|
+
case sentinel
|
92
|
+
when String
|
93
|
+
Config.new(**@extra_config, url: sentinel)
|
94
|
+
else
|
95
|
+
Config.new(**@extra_config, **sentinel)
|
96
|
+
end
|
97
|
+
end
|
98
|
+
end
|
99
|
+
|
96
100
|
def config
|
97
101
|
@mutex.synchronize do
|
98
102
|
@config ||= if @role == :master
|
@@ -106,9 +110,11 @@ class RedisClient
|
|
106
110
|
def resolve_master
|
107
111
|
each_sentinel do |sentinel_client|
|
108
112
|
host, port = sentinel_client.call("SENTINEL", "get-master-addr-by-name", @name)
|
109
|
-
|
110
|
-
|
111
|
-
|
113
|
+
next unless host && port
|
114
|
+
|
115
|
+
refresh_sentinels(sentinel_client)
|
116
|
+
|
117
|
+
return Config.new(host: host, port: Integer(port), **@client_config)
|
112
118
|
end
|
113
119
|
rescue ConnectionError
|
114
120
|
raise ConnectionError, "No sentinels available"
|
@@ -159,5 +165,19 @@ class RedisClient
|
|
159
165
|
|
160
166
|
raise last_error if last_error
|
161
167
|
end
|
168
|
+
|
169
|
+
def refresh_sentinels(sentinel_client)
|
170
|
+
sentinel_response = sentinel_client.call("SENTINEL", "sentinels", @name, &@to_list_of_hash)
|
171
|
+
sentinels = sentinel_response.map do |sentinel|
|
172
|
+
{ host: sentinel.fetch("ip"), port: Integer(sentinel.fetch("port")) }
|
173
|
+
end
|
174
|
+
new_sentinels = sentinels.select do |sentinel|
|
175
|
+
@sentinel_configs.none? do |sentinel_config|
|
176
|
+
sentinel_config.host == sentinel.fetch(:host) && sentinel_config.port == sentinel.fetch(:port)
|
177
|
+
end
|
178
|
+
end
|
179
|
+
|
180
|
+
@sentinel_configs.concat sentinels_to_configs(new_sentinels)
|
181
|
+
end
|
162
182
|
end
|
163
183
|
end
|
data/lib/redis_client/version.rb
CHANGED
data/lib/redis_client.rb
CHANGED
@@ -346,7 +346,6 @@ class RedisClient
|
|
346
346
|
|
347
347
|
def close
|
348
348
|
@raw_connection&.close
|
349
|
-
@raw_connection = nil
|
350
349
|
self
|
351
350
|
end
|
352
351
|
|
@@ -430,7 +429,7 @@ class RedisClient
|
|
430
429
|
|
431
430
|
def close
|
432
431
|
@raw_connection&.close
|
433
|
-
@raw_connection = nil
|
432
|
+
@raw_connection = nil # PubSub can't just reconnect
|
434
433
|
self
|
435
434
|
end
|
436
435
|
|
@@ -654,20 +653,28 @@ class RedisClient
|
|
654
653
|
end
|
655
654
|
|
656
655
|
def raw_connection
|
657
|
-
@raw_connection
|
658
|
-
|
656
|
+
if @raw_connection.nil? || !@raw_connection.revalidate
|
657
|
+
connect
|
658
|
+
end
|
659
|
+
@raw_connection
|
659
660
|
end
|
660
661
|
|
661
662
|
def connect
|
662
663
|
@pid = PIDCache.pid
|
663
664
|
|
664
|
-
|
665
|
-
|
666
|
-
|
667
|
-
|
668
|
-
|
669
|
-
|
670
|
-
|
665
|
+
if @raw_connection
|
666
|
+
@middlewares.connect(config) do
|
667
|
+
@raw_connection.reconnect
|
668
|
+
end
|
669
|
+
else
|
670
|
+
@raw_connection = @middlewares.connect(config) do
|
671
|
+
config.driver.new(
|
672
|
+
config,
|
673
|
+
connect_timeout: connect_timeout,
|
674
|
+
read_timeout: read_timeout,
|
675
|
+
write_timeout: write_timeout,
|
676
|
+
)
|
677
|
+
end
|
671
678
|
end
|
672
679
|
|
673
680
|
prelude = config.connection_prelude.dup
|
@@ -680,18 +687,16 @@ class RedisClient
|
|
680
687
|
if config.sentinel?
|
681
688
|
prelude << ["ROLE"]
|
682
689
|
role, = @middlewares.call_pipelined(prelude, config) do
|
683
|
-
|
690
|
+
@raw_connection.call_pipelined(prelude, nil).last
|
684
691
|
end
|
685
692
|
config.check_role!(role)
|
686
693
|
else
|
687
694
|
unless prelude.empty?
|
688
695
|
@middlewares.call_pipelined(prelude, config) do
|
689
|
-
|
696
|
+
@raw_connection.call_pipelined(prelude, nil)
|
690
697
|
end
|
691
698
|
end
|
692
699
|
end
|
693
|
-
|
694
|
-
connection
|
695
700
|
rescue FailoverError, CannotConnectError
|
696
701
|
raise
|
697
702
|
rescue ConnectionError => error
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: redis-client
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.14.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Jean Boussier
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2023-
|
11
|
+
date: 2023-03-10 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: connection_pool
|