redis-client 0.14.1 → 0.16.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: 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+