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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: e12fdb44add8ecb747b797c75a5991c973cb78519bbbce1f15a68bb80d1e2bb9
4
- data.tar.gz: 0def4c2cc5a2f55ec9e9eb48a4ce5bfeadbef889c9695642126c011aebcd83b2
3
+ metadata.gz: ac50722e5156b95e037fb5fd933cb804a3d52d0d767faa2bbddaccc7cbd38d07
4
+ data.tar.gz: 0a1645c3788dd195bf3567e14c0ba508b892357991359297c002c84bd2796e2e
5
5
  SHA512:
6
- metadata.gz: 5b242cf54b503ae90c4642acdd8d59d8022639cb97de09a664afcbaec8730269fab79e298b0e4e2f74fe54f14ecc36e54adbb65b508d9aead715d0758921a2bd
7
- data.tar.gz: e8b33e77e568ad63150b40e5a17d5ef528346868d802be80b4953d68986a89b93eaa1d24dcd80368c7b73bcab4ee9428d6d0ad99f300eb18ef43502783649686
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
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- redis-client (0.13.0)
4
+ redis-client (0.14.0)
5
5
  connection_pool
6
6
 
7
7
  GEM
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
 
@@ -112,7 +112,9 @@ class RedisClient
112
112
  end
113
113
 
114
114
  def ssl_context
115
- @ssl_context ||= @driver.ssl_context(@ssl_params || {})
115
+ if ssl
116
+ @ssl_context ||= @driver.ssl_context(@ssl_params || {})
117
+ end
116
118
  end
117
119
 
118
120
  def server_url
@@ -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 == 0 && connected?
11
- self
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
- socket = if config.path
46
- UNIXSocket.new(config.path)
47
- else
48
- sock = if SUPPORTS_RESOLV_TIMEOUT
49
- Socket.tcp(config.host, config.port, connect_timeout: connect_timeout, resolv_timeout: connect_timeout)
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.map do |s|
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
- if host && port
110
- return Config.new(host: host, port: Integer(port), **@client_config)
111
- end
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
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  class RedisClient
4
- VERSION = "0.13.0"
4
+ VERSION = "0.14.0"
5
5
  end
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 = @raw_connection&.revalidate
658
- @raw_connection ||= connect
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
- connection = @middlewares.connect(config) do
665
- config.driver.new(
666
- config,
667
- connect_timeout: connect_timeout,
668
- read_timeout: read_timeout,
669
- write_timeout: write_timeout,
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
- connection.call_pipelined(prelude, nil).last
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
- connection.call_pipelined(prelude, nil)
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.13.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-02-28 00:00:00.000000000 Z
11
+ date: 2023-03-10 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: connection_pool