redis-client 0.13.0 → 0.14.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.
- 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
|