redis-client 0.25.3 → 0.26.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: c9d414e733ddfec002eb356535edebdca59f5dc95bb028ab9012ef7490708ef8
4
- data.tar.gz: 829dd355a87f34c5dd94da685839be29cb23755c9eb420ee4fcf4faef557c741
3
+ metadata.gz: 532a6809e653318f469cf8aa5d3ed07498dd15b0557cea4449acde840f1c45ab
4
+ data.tar.gz: 8fef3135440099474344a957096db4ec1cc0d058e7e7ed80227282b37752ffd1
5
5
  SHA512:
6
- metadata.gz: bbf68e7d8643165a1a7658a65774e0046e5c0e86338c91850f5c50292e3ba5bd2a8d9d4b124b95c722e5fb99844329bb8d1b42f4f5044c7f2e0db0a58f1f2add
7
- data.tar.gz: 6416e379d6858466d93fae1861f446632cebe8bbdcda483add50c480f99856c1419d24a076fd12f76c921a9b5338850dc88b3bb30ab3edb683ba40935fd688bd
6
+ metadata.gz: b9a13cf5d2bed374fec9ad8d4cf92e2c3dda1b4aea9ea03180144b83ac7115daf4e5252c5c78b3dfbc4cf69eba7c0a79d5cc021a608d544d982c7165e2d77f18
7
+ data.tar.gz: 21c1b9828143c1edaba52f5359f15ac887ef8070fe3948ec893a63ad3800ba4612379985673beaa39dc6143fa0c39f18f7e1dab20beded463b748c752d8627aa
data/CHANGELOG.md CHANGED
@@ -1,5 +1,22 @@
1
1
  # Unreleased
2
2
 
3
+ # 0.26.1
4
+
5
+ - Fix a few corner cases where `RedisClient::Error#final?` was innacurate.
6
+ - hiredis-client: Properly reconnect to the new leader after a sentinel failover.
7
+
8
+ # 0.26.0
9
+
10
+ - Add `RedisClient::Error#final?` and `#retriable?` to allow middleware to filter out non-final errors.
11
+ - Fix precedence of `db: nil` initialization parameter.
12
+
13
+ ```ruby
14
+ Redis.new(url: "redis://localhost:6379/3", db: nil).db
15
+ ```
16
+
17
+ Before: `0`
18
+ After: `3`
19
+
3
20
  # 0.25.3
4
21
 
5
22
  - Fix `hiredis-client` compilation with `clang 21`.
@@ -205,7 +222,7 @@
205
222
  - Added `_v` versions of `call` methods to make it easier to pass commands as arrays without splating.
206
223
  - Fix calling `blocking_call` with a block in a pipeline.
207
224
  - `blocking_call` now raise `ReadTimeoutError` if the command didn't complete in time.
208
- - Fix `blocking_call` to not respect `retry_attempts` on timeout.
225
+ - Fix `blocking_call` to not respect `reconnect_attempts` on timeout.
209
226
  - Stop parsing RESP3 sets as Ruby Set instances.
210
227
  - Fix `SystemStackError` when parsing very large hashes. Fix: #30
211
228
  - `hiredis` now more properly release the GVL when doing IOs.
data/README.md CHANGED
@@ -459,6 +459,29 @@ RedisClient.register(MyGlobalRedisInstrumentation)
459
459
 
460
460
  redis_config = RedisClient.config(custom: { tags: { "environment": Rails.env }})
461
461
  ```
462
+
463
+ ### Instrumenting Errors
464
+
465
+ It is important to note that when `reconnect_attempts` is enabled, all network errors are reported to the middlewares,
466
+ even the ones that will be retried.
467
+
468
+ In many cases you may want to ignore retriable errors, or report them differently:
469
+
470
+ ```ruby
471
+ module MyGlobalRedisInstrumentation
472
+ def call(command, redis_config)
473
+ super
474
+ rescue RedisClient::Error => error
475
+ if error.final?
476
+ # Error won't be retried.
477
+ else
478
+ # Error will be retried.
479
+ end
480
+ raise
481
+ end
482
+ end
483
+ ```
484
+
462
485
  ### Timeouts
463
486
 
464
487
  The client allows you to configure connect, read, and write timeouts.
@@ -140,6 +140,10 @@ class RedisClient
140
140
  @client_implementation.new(self, **kwargs)
141
141
  end
142
142
 
143
+ def retriable?(attempt)
144
+ @reconnect_attempts && @reconnect_attempts[attempt]
145
+ end
146
+
143
147
  def retry_connecting?(attempt, _error)
144
148
  if @reconnect_attempts
145
149
  if (pause = @reconnect_attempts[attempt])
@@ -182,7 +186,7 @@ class RedisClient
182
186
 
183
187
  include Common
184
188
 
185
- attr_reader :host, :port, :path
189
+ attr_reader :host, :port, :path, :server_key
186
190
 
187
191
  def initialize(
188
192
  url: nil,
@@ -191,14 +195,15 @@ class RedisClient
191
195
  path: nil,
192
196
  username: nil,
193
197
  password: nil,
198
+ db: nil,
194
199
  **kwargs
195
200
  )
196
201
  if url
197
202
  url_config = URLConfig.new(url)
198
203
  kwargs = {
199
204
  ssl: url_config.ssl?,
200
- db: url_config.db,
201
205
  }.compact.merge(kwargs)
206
+ db ||= url_config.db
202
207
  host ||= url_config.host
203
208
  port ||= url_config.port
204
209
  path ||= url_config.path
@@ -206,7 +211,7 @@ class RedisClient
206
211
  password ||= url_config.password
207
212
  end
208
213
 
209
- super(username: username, password: password, **kwargs)
214
+ super(username: username, password: password, db: db, **kwargs)
210
215
 
211
216
  if @path = path
212
217
  @host = nil
@@ -215,6 +220,8 @@ class RedisClient
215
220
  @host = host || DEFAULT_HOST
216
221
  @port = Integer(port || DEFAULT_PORT)
217
222
  end
223
+
224
+ @server_key = [@path, @host, @port].freeze
218
225
  end
219
226
  end
220
227
  end
@@ -2,8 +2,14 @@
2
2
 
3
3
  class RedisClient
4
4
  module ConnectionMixin
5
- def initialize
5
+ attr_accessor :retry_attempt
6
+ attr_reader :config
7
+
8
+ def initialize(config)
6
9
  @pending_reads = 0
10
+ @retry_attempt = nil
11
+ @config = config
12
+ @server_key = nil
7
13
  end
8
14
 
9
15
  def reconnect
@@ -17,7 +23,7 @@ class RedisClient
17
23
  end
18
24
 
19
25
  def revalidate
20
- if @pending_reads > 0
26
+ if @pending_reads > 0 || @server_key != @config.server_key
21
27
  close
22
28
  false
23
29
  else
@@ -33,6 +39,7 @@ class RedisClient
33
39
  if result.is_a?(Error)
34
40
  result._set_command(command)
35
41
  result._set_config(config)
42
+ result._set_retry_attempt(@retry_attempt)
36
43
  raise result
37
44
  else
38
45
  result
@@ -61,6 +68,7 @@ class RedisClient
61
68
  elsif result.is_a?(Error)
62
69
  result._set_command(commands[index])
63
70
  result._set_config(config)
71
+ result._set_retry_attempt(@retry_attempt)
64
72
  first_exception ||= result
65
73
  end
66
74
 
@@ -82,5 +90,17 @@ class RedisClient
82
90
  # to account for the network delay.
83
91
  timeout + config.read_timeout
84
92
  end
93
+
94
+ def protocol_error(message)
95
+ error = ProtocolError.with_config(message, config)
96
+ error._set_retry_attempt(@retry_attempt)
97
+ error
98
+ end
99
+
100
+ def connection_error(message)
101
+ error = ConnectionError.with_config(message, config)
102
+ error._set_retry_attempt(@retry_attempt)
103
+ error
104
+ end
85
105
  end
86
106
  end
@@ -40,11 +40,8 @@ class RedisClient
40
40
 
41
41
  SUPPORTS_RESOLV_TIMEOUT = Socket.method(:tcp).parameters.any? { |p| p.last == :resolv_timeout }
42
42
 
43
- attr_reader :config
44
-
45
43
  def initialize(config, connect_timeout:, read_timeout:, write_timeout:)
46
- super()
47
- @config = config
44
+ super(config)
48
45
  @connect_timeout = connect_timeout
49
46
  @read_timeout = read_timeout
50
47
  @write_timeout = write_timeout
@@ -75,7 +72,11 @@ class RedisClient
75
72
  begin
76
73
  @io.write(buffer)
77
74
  rescue SystemCallError, IOError, OpenSSL::SSL::SSLError => error
78
- raise ConnectionError.with_config(error.message, config)
75
+ raise connection_error(error.message)
76
+ rescue Error => error
77
+ error._set_config(config)
78
+ error._set_retry_attempt(@retry_attempt)
79
+ raise error
79
80
  end
80
81
  end
81
82
 
@@ -87,7 +88,7 @@ class RedisClient
87
88
  begin
88
89
  @io.write(buffer)
89
90
  rescue SystemCallError, IOError, OpenSSL::SSL::SSLError => error
90
- raise ConnectionError.with_config(error.message, config)
91
+ raise connection_error(error.message)
91
92
  end
92
93
  end
93
94
 
@@ -97,10 +98,10 @@ class RedisClient
97
98
  else
98
99
  @io.with_timeout(timeout) { RESP3.load(@io) }
99
100
  end
100
- rescue RedisClient::RESP3::UnknownType => error
101
- raise RedisClient::ProtocolError.with_config(error.message, config)
101
+ rescue RedisClient::RESP3::Error => error
102
+ raise protocol_error(error.message)
102
103
  rescue SystemCallError, IOError, OpenSSL::SSL::SSLError => error
103
- raise ConnectionError.with_config(error.message, config)
104
+ raise connection_error(error.message)
104
105
  end
105
106
 
106
107
  def measure_round_trip_delay
@@ -112,6 +113,7 @@ class RedisClient
112
113
  private
113
114
 
114
115
  def connect
116
+ @server_key = @config.server_key
115
117
  socket = if @config.path
116
118
  UNIXSocket.new(@config.path)
117
119
  else
@@ -82,6 +82,10 @@ class RedisClient
82
82
  end
83
83
  end
84
84
 
85
+ def server_key
86
+ config.server_key
87
+ end
88
+
85
89
  def host
86
90
  config.host
87
91
  end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  class RedisClient
4
- VERSION = "0.25.3"
4
+ VERSION = "0.26.1"
5
5
  end
data/lib/redis_client.rb CHANGED
@@ -99,13 +99,49 @@ class RedisClient
99
99
  end
100
100
  end
101
101
 
102
+ module Retriable
103
+ def _set_retry_attempt(retry_attempt)
104
+ @retry_attempt = retry_attempt
105
+ end
106
+
107
+ def retry_attempt
108
+ @retry_attempt || 0
109
+ end
110
+
111
+ def retriable?
112
+ !!@retry_attempt
113
+ end
114
+
115
+ def final?
116
+ !@retry_attempt
117
+ end
118
+ end
119
+
120
+ module Final
121
+ def _set_retry_attempt(_retry_attempt)
122
+ end
123
+
124
+ def retry_attempt
125
+ 0
126
+ end
127
+
128
+ def retriable?
129
+ false
130
+ end
131
+
132
+ def final?
133
+ true
134
+ end
135
+ end
136
+
102
137
  class Error < StandardError
103
138
  include HasConfig
139
+ include Retriable
104
140
 
105
141
  def self.with_config(message, config = nil)
106
- new(message).tap do |error|
107
- error._set_config(config)
108
- end
142
+ error = new(message)
143
+ error._set_config(config)
144
+ error
109
145
  end
110
146
  end
111
147
 
@@ -142,6 +178,7 @@ class RedisClient
142
178
  class CommandError < Error
143
179
  include HasCommand
144
180
  include HasCode
181
+ include Final
145
182
 
146
183
  class << self
147
184
  def parse(error_message)
@@ -212,6 +249,7 @@ class RedisClient
212
249
  @middlewares = config.middlewares_stack.new(self)
213
250
  @raw_connection = nil
214
251
  @disable_reconnection = false
252
+ @retry_attempt = nil
215
253
  end
216
254
 
217
255
  def inspect
@@ -706,6 +744,7 @@ class RedisClient
706
744
  close if !config.inherit_socket && @pid != PIDCache.pid
707
745
 
708
746
  if @disable_reconnection
747
+ @raw_connection.retry_attempt = nil
709
748
  if block_given?
710
749
  yield @raw_connection
711
750
  else
@@ -716,6 +755,7 @@ class RedisClient
716
755
  connection = nil
717
756
  preferred_error = nil
718
757
  begin
758
+ @retry_attempt = config.retriable?(tries) ? tries : nil
719
759
  connection = raw_connection
720
760
  if block_given?
721
761
  yield connection
@@ -744,6 +784,7 @@ class RedisClient
744
784
  connection = ensure_connected
745
785
  begin
746
786
  @disable_reconnection = true
787
+ @raw_connection.retry_attempt = nil
747
788
  yield connection
748
789
  rescue ConnectionError, ProtocolError
749
790
  close
@@ -758,13 +799,14 @@ class RedisClient
758
799
  if @raw_connection.nil? || !@raw_connection.revalidate
759
800
  connect
760
801
  end
802
+ @raw_connection.retry_attempt = @retry_attempt
761
803
  @raw_connection
762
804
  end
763
805
 
764
806
  def connect
765
807
  @pid = PIDCache.pid
766
808
 
767
- if @raw_connection
809
+ if @raw_connection&.revalidate
768
810
  @middlewares.connect(config) do
769
811
  @raw_connection.reconnect
770
812
  end
@@ -778,6 +820,7 @@ class RedisClient
778
820
  )
779
821
  end
780
822
  end
823
+ @raw_connection.retry_attempt = @retry_attempt
781
824
 
782
825
  prelude = config.connection_prelude.dup
783
826
 
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: redis-client
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.25.3
4
+ version: 0.26.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Jean Boussier
@@ -70,7 +70,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
70
70
  - !ruby/object:Gem::Version
71
71
  version: '0'
72
72
  requirements: []
73
- rubygems_version: 3.7.1
73
+ rubygems_version: 3.6.9
74
74
  specification_version: 4
75
75
  summary: Simple low-level client for Redis 6+
76
76
  test_files: []