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 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