redis-client 0.14.1 → 0.16.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: e9858303eff06d56f8e144eb7033995fc92839c41fe39d0bda6f1d9c2fed91e9
4
- data.tar.gz: a4f4ca2073a32e28932c58e537963a2f0b2321e36ca472c6edc9edab1b29122b
3
+ metadata.gz: 35ff65a4af6127cb60acff6a3d3b2d2e5e65ddeffc0e8e808b7dec90792697a6
4
+ data.tar.gz: 854f82d7548c5462b53ae29a637dc208cdc70b3399b28d0c61f38e02f073e37e
5
5
  SHA512:
6
- metadata.gz: 3c20b17e4587980ed89e0877b20cb9aee7d16365401127cfdc3b4f451f7706bf338988e97ac9d1e62d33c9af5b45ebfb3c35310205fbd309995ace46c3b85fce
7
- data.tar.gz: 21ed1731b716f1bd6e525b638b7715683979860faa0d14f2fc6f210c4d185a95d2329650b56cf731734d22d7008cdadccc2055a8d5ff7a332b043836e0d39b75
6
+ metadata.gz: 2c9e8e564662e86f4e1a24ca9528a5d909a096218eaf62bd7b58838402db390e7bec65d79a43aa89a2b98d22f9645ca7e412ae2db4010dc4131cbf2133d2666f
7
+ data.tar.gz: c6400693a10e28e377bf52792ee54f41afb2b2c97ea49578a90eba767b6e3b6a7f37eaeaccf41a38a8222827f461f44aa0b1f75881084dc30a521e407508e277
data/CHANGELOG.md CHANGED
@@ -1,5 +1,16 @@
1
1
  # Unreleased
2
2
 
3
+ # 0.16.0
4
+
5
+ - Add `RedisClient#disable_reconnection`.
6
+ - Reverted the special discard of connection. A regular `close(2)` should be enough.
7
+
8
+ # 0.15.0
9
+
10
+ - Discard sockets rather than explictly close them when a fork is detected. #126.
11
+ - Allow to configure sentinel client via url. #117.
12
+ - Fix sentinel to preverse the auth/password when refreshing the sentinel list. #107.
13
+
3
14
  # 0.14.1
4
15
 
5
16
  - Include the timeout value in TimeoutError messages.
data/Gemfile.lock CHANGED
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- redis-client (0.14.1)
4
+ redis-client (0.16.0)
5
5
  connection_pool
6
6
 
7
7
  GEM
@@ -10,7 +10,7 @@ GEM
10
10
  ast (2.4.2)
11
11
  benchmark-ips (2.12.0)
12
12
  byebug (11.1.3)
13
- connection_pool (2.4.0)
13
+ connection_pool (2.4.1)
14
14
  hiredis (0.6.3)
15
15
  hiredis (0.6.3-java)
16
16
  minitest (5.15.0)
@@ -19,7 +19,7 @@ GEM
19
19
  ast (~> 2.4.1)
20
20
  rainbow (3.1.1)
21
21
  rake (13.0.6)
22
- rake-compiler (1.2.1)
22
+ rake-compiler (1.2.5)
23
23
  rake
24
24
  redis (4.6.0)
25
25
  regexp_parser (2.5.0)
@@ -38,7 +38,7 @@ GEM
38
38
  rubocop-minitest (0.19.1)
39
39
  rubocop (>= 0.90, < 2.0)
40
40
  ruby-progressbar (1.11.0)
41
- stackprof (0.2.24)
41
+ stackprof (0.2.25)
42
42
  toxiproxy (2.0.2)
43
43
  unicode-display_width (2.2.0)
44
44
 
data/README.md CHANGED
@@ -42,7 +42,7 @@ redis.with do |r|
42
42
  end
43
43
  ```
44
44
 
45
- If you are working in a single threaded environment, or wish to use your own connection pooling mechanism,
45
+ If you are working in a single-threaded environment, or wish to use your own connection pooling mechanism,
46
46
  you can obtain a raw client with `#new_client`
47
47
 
48
48
  ```ruby
@@ -67,11 +67,11 @@ redis.call("GET", "mykey")
67
67
  - `host`: The server hostname or IP address. Defaults to `"localhost"`.
68
68
  - `port`: The server port. Defaults to `6379`.
69
69
  - `path`: The path to a UNIX socket, if set `url`, `host` and `port` are ignored.
70
- - `ssl`: Wether to connect using SSL or not.
70
+ - `ssl`: Whether to connect using SSL or not.
71
71
  - `ssl_params`: A configuration Hash passed to [`OpenSSL::SSL::SSLContext#set_params`](https://www.rubydoc.info/stdlib/openssl/OpenSSL%2FSSL%2FSSLContext:set_params), notable options include:
72
72
  - `cert`: The path to the client certificate (e.g. `client.crt`).
73
73
  - `key`: The path to the client key (e.g. `client.key`).
74
- - `ca_file`: The certificate authority to use, useful for self signed certificates (e.g. `ca.crt`),
74
+ - `ca_file`: The certificate authority to use, useful for self-signed certificates (e.g. `ca.crt`),
75
75
  - `db`: The database to select after connecting, defaults to `0`.
76
76
  - `id` ID for the client connection, assigns name to current connection by sending `CLIENT SETNAME`.
77
77
  - `username` Username to authenticate against server, defaults to `"default"`.
@@ -83,7 +83,7 @@ redis.call("GET", "mykey")
83
83
  - `reconnect_attempts`: Specify how many times the client should retry to send queries. Defaults to `0`. Makes sure to read the [reconnection section](#reconnection) before enabling it.
84
84
  - `circuit_breaker`: A Hash with circuit breaker configuration. Defaults to `nil`. See the [circuit breaker section](#circuit-breaker) for details.
85
85
  - `protocol:` The version of the RESP protocol to use. Default to `3`.
86
- - `custom`: A user owned value ignored by `redis-client` but available as `Config#custom`. This can be used to hold middleware configurations and other user specific metadatas.
86
+ - `custom`: A user-owned value ignored by `redis-client` but available as `Config#custom`. This can be used to hold middleware configurations and other user-specific metadata.
87
87
 
88
88
  ### Sentinel support
89
89
 
@@ -343,6 +343,10 @@ loop do
343
343
  end
344
344
  ```
345
345
 
346
+ *Note*: pubsub connections are stateful, as such they won't ever reconnect automatically.
347
+ The caller is responsible for reconnecting if the connection is lost and to resubscribe to
348
+ all channels.
349
+
346
350
  ## Production
347
351
 
348
352
  ### Instrumentation and Middlewares
@@ -376,7 +380,7 @@ redis_config = RedisClient.config(middlewares: [AnotherRedisInstrumentation])
376
380
  redis_config.new_client
377
381
  ```
378
382
 
379
- If middlewares need a client specific configuration, `Config#custom` can be used
383
+ If middlewares need a client-specific configuration, `Config#custom` can be used
380
384
 
381
385
  ```ruby
382
386
  module MyGlobalRedisInstrumentation
@@ -439,6 +443,8 @@ redis.call("GET", "counter") # Will be retried up to 3 times.
439
443
  redis.call_once("INCR", "counter") # Won't be retried.
440
444
  ```
441
445
 
446
+ **Note**: automatic reconnection doesn't apply to pubsub clients as their connection is stateful.
447
+
442
448
  ### Exponential backoff
443
449
 
444
450
  Alternatively, `reconnect_attempts` accepts a list of sleep durations for implementing exponential backoff:
@@ -448,9 +454,9 @@ redis_config = RedisClient.config(reconnect_attempts: [0, 0.05, 0.1])
448
454
  ```
449
455
 
450
456
  This configuration is generally used when the Redis server is expected to failover or recover relatively quickly and
451
- that it's not really possibe to continue without issuing the command.
457
+ that it's not really possible to continue without issuing the command.
452
458
 
453
- When the Redis server is used as an ephemeral cache, circuit breakers are generally prefered.
459
+ When the Redis server is used as an ephemeral cache, circuit breakers are generally preferred.
454
460
 
455
461
  ### Circuit Breaker
456
462
 
@@ -162,37 +162,21 @@ class RedisClient
162
162
  **kwargs
163
163
  )
164
164
  if url
165
- uri = URI(url)
166
- unless uri.scheme == "redis" || uri.scheme == "rediss"
167
- raise ArgumentError, "Invalid URL: #{url.inspect}"
168
- end
169
-
170
- kwargs[:ssl] = uri.scheme == "rediss" unless kwargs.key?(:ssl)
171
-
172
- kwargs[:username] ||= uri.user if uri.password && !uri.user.empty?
173
-
174
- kwargs[:password] ||= if uri.user && !uri.password
175
- URI.decode_www_form_component(uri.user)
176
- elsif uri.user && uri.password
177
- URI.decode_www_form_component(uri.password)
178
- end
179
-
180
- db_path = uri.path&.delete_prefix("/")
181
- kwargs[:db] ||= Integer(db_path) if db_path && !db_path.empty?
165
+ url_config = URLConfig.new(url)
166
+ kwargs = {
167
+ ssl: url_config.ssl?,
168
+ username: url_config.username,
169
+ password: url_config.password,
170
+ db: url_config.db,
171
+ }.compact.merge(kwargs)
172
+ host ||= url_config.host
173
+ port ||= url_config.port
182
174
  end
183
175
 
184
176
  super(**kwargs)
185
177
 
186
- @host = host
187
- unless @host
188
- uri_host = uri&.host
189
- uri_host = nil if uri_host&.empty?
190
- if uri_host
191
- @host = uri_host&.sub(/\A\[(.*)\]\z/, '\1')
192
- end
193
- end
194
- @host ||= DEFAULT_HOST
195
- @port = Integer(port || uri&.port || DEFAULT_PORT)
178
+ @host = host || DEFAULT_HOST
179
+ @port = Integer(port || DEFAULT_PORT)
196
180
  @path = path
197
181
  end
198
182
  end
@@ -101,6 +101,12 @@ class RedisClient
101
101
  raise ConnectionError, error.message
102
102
  end
103
103
 
104
+ def measure_round_trip_delay
105
+ start = Process.clock_gettime(Process::CLOCK_MONOTONIC, :float_millisecond)
106
+ call(["PING"], @read_timeout)
107
+ Process.clock_gettime(Process::CLOCK_MONOTONIC, :float_millisecond) - start
108
+ end
109
+
104
110
  private
105
111
 
106
112
  def connect
@@ -7,11 +7,28 @@ class RedisClient
7
7
  SENTINEL_DELAY = 0.25
8
8
  DEFAULT_RECONNECT_ATTEMPTS = 2
9
9
 
10
- def initialize(name:, sentinels:, role: :master, **client_config)
11
- unless %i(master replica slave).include?(role)
10
+ attr_reader :name
11
+
12
+ def initialize(sentinels:, role: :master, name: nil, url: nil, **client_config)
13
+ unless %i(master replica slave).include?(role.to_sym)
12
14
  raise ArgumentError, "Expected role to be either :master or :replica, got: #{role.inspect}"
13
15
  end
14
16
 
17
+ if url
18
+ url_config = URLConfig.new(url)
19
+ client_config = {
20
+ username: url_config.username,
21
+ password: url_config.password,
22
+ db: url_config.db,
23
+ }.compact.merge(client_config)
24
+ name ||= url_config.host
25
+ end
26
+
27
+ @name = name
28
+ unless @name
29
+ raise ArgumentError, "RedisClient::SentinelConfig requires either a name or an url with a host"
30
+ end
31
+
15
32
  @to_list_of_hash = @to_hash = nil
16
33
  @extra_config = {}
17
34
  if client_config[:protocol] == 2
@@ -25,16 +42,15 @@ class RedisClient
25
42
  end
26
43
  end
27
44
 
28
- @name = name
29
- @sentinel_configs = sentinels_to_configs(sentinels)
30
45
  @sentinels = {}.compare_by_identity
31
- @role = role
46
+ @role = role.to_sym
32
47
  @mutex = Mutex.new
33
48
  @config = nil
34
49
 
35
50
  client_config[:reconnect_attempts] ||= DEFAULT_RECONNECT_ATTEMPTS
36
51
  @client_config = client_config || {}
37
52
  super(**client_config)
53
+ @sentinel_configs = sentinels_to_configs(sentinels)
38
54
  end
39
55
 
40
56
  def sentinels
@@ -90,9 +106,9 @@ class RedisClient
90
106
  sentinels.map do |sentinel|
91
107
  case sentinel
92
108
  when String
93
- Config.new(**@extra_config, url: sentinel)
109
+ Config.new(**@client_config, **@extra_config, url: sentinel, db: nil)
94
110
  else
95
- Config.new(**@extra_config, **sentinel)
111
+ Config.new(**@client_config, **@extra_config, **sentinel, db: nil)
96
112
  end
97
113
  end
98
114
  end
@@ -0,0 +1,53 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "uri"
4
+
5
+ class RedisClient
6
+ class URLConfig
7
+ DEFAULT_SCHEMA = "redis"
8
+ SSL_SCHEMA = "rediss"
9
+
10
+ attr_reader :url, :uri
11
+
12
+ def initialize(url)
13
+ @url = url
14
+ @uri = URI(url)
15
+ unless uri.scheme == DEFAULT_SCHEMA || uri.scheme == SSL_SCHEMA
16
+ raise ArgumentError, "Invalid URL: #{url.inspect}"
17
+ end
18
+ end
19
+
20
+ def ssl?
21
+ @uri.scheme == SSL_SCHEMA
22
+ end
23
+
24
+ def db
25
+ db_path = uri.path&.delete_prefix("/")
26
+ Integer(db_path) if db_path && !db_path.empty?
27
+ end
28
+
29
+ def username
30
+ uri.user if uri.password && !uri.user.empty?
31
+ end
32
+
33
+ def password
34
+ if uri.user && !uri.password
35
+ URI.decode_www_form_component(uri.user)
36
+ elsif uri.user && uri.password
37
+ URI.decode_www_form_component(uri.password)
38
+ end
39
+ end
40
+
41
+ def host
42
+ return if uri.host.nil? || uri.host.empty?
43
+
44
+ uri.host.sub(/\A\[(.*)\]\z/, '\1')
45
+ end
46
+
47
+ def port
48
+ return unless uri.port
49
+
50
+ Integer(uri.port)
51
+ end
52
+ end
53
+ end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  class RedisClient
4
- VERSION = "0.14.1"
4
+ VERSION = "0.16.0"
5
5
  end
data/lib/redis_client.rb CHANGED
@@ -2,6 +2,7 @@
2
2
 
3
3
  require "redis_client/version"
4
4
  require "redis_client/command_builder"
5
+ require "redis_client/url_config"
5
6
  require "redis_client/config"
6
7
  require "redis_client/pid_cache"
7
8
  require "redis_client/sentinel_config"
@@ -204,6 +205,14 @@ class RedisClient
204
205
  sub
205
206
  end
206
207
 
208
+ def measure_round_trip_delay
209
+ ensure_connected do |connection|
210
+ @middlewares.call(["PING"], config) do
211
+ connection.measure_round_trip_delay
212
+ end
213
+ end
214
+ end
215
+
207
216
  def call(*command, **kwargs)
208
217
  command = @command_builder.generate(command, kwargs)
209
218
  result = ensure_connected do |connection|
@@ -349,6 +358,10 @@ class RedisClient
349
358
  self
350
359
  end
351
360
 
361
+ def disable_reconnection(&block)
362
+ ensure_connected(retryable: false, &block)
363
+ end
364
+
352
365
  def pipelined
353
366
  pipeline = Pipeline.new(@command_builder)
354
367
  yield pipeline
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.14.1
4
+ version: 0.16.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-03-30 00:00:00.000000000 Z
11
+ date: 2023-08-17 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: connection_pool
@@ -51,6 +51,7 @@ files:
51
51
  - lib/redis_client/ruby_connection/buffered_io.rb
52
52
  - lib/redis_client/ruby_connection/resp3.rb
53
53
  - lib/redis_client/sentinel_config.rb
54
+ - lib/redis_client/url_config.rb
54
55
  - lib/redis_client/version.rb
55
56
  - redis-client.gemspec
56
57
  homepage: https://github.com/redis-rb/redis-client
@@ -76,7 +77,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
76
77
  - !ruby/object:Gem::Version
77
78
  version: '0'
78
79
  requirements: []
79
- rubygems_version: 3.4.6
80
+ rubygems_version: 3.4.10
80
81
  signing_key:
81
82
  specification_version: 4
82
83
  summary: Simple low-level client for Redis 6+