redis-client 0.14.0 → 0.15.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: ac50722e5156b95e037fb5fd933cb804a3d52d0d767faa2bbddaccc7cbd38d07
4
- data.tar.gz: 0a1645c3788dd195bf3567e14c0ba508b892357991359297c002c84bd2796e2e
3
+ metadata.gz: aa81349f7f830db128f2637bc4c782543b9c51a16075b54fdc4700810a95ae83
4
+ data.tar.gz: d34e19d6229292bb6b15e61aebeda57729780040e098f0fae1beb534f9717ff9
5
5
  SHA512:
6
- metadata.gz: 0c93ddbae9ffdd770589eb6003eee594dc902a8d2698c99c38878b923ac80a91aa6a43892d015d9a4ba52ef19300c95970a8947e25f1844c4c01d4b88ff9d9ae
7
- data.tar.gz: a06dd7eaa65ccf8544b4f04f7beffe2580d139783770170fcf76348c5760f78c5baa0d77e734a31111c720df6458bb8112a64960316618b831fdb6059b625963
6
+ metadata.gz: 01ed887cfc90ffc36ff82669a459d1c9b13b72c9d81f66ca770e7642c16cba90d30cfd31768029b81a1519778f6af2905b145ab606f070008ac471192ce812fa
7
+ data.tar.gz: 64320bfdcdaa3fd4402ae41350dc0451127f895e3f224d916140ea3fc5813dfe9344f04dc18c20d82e9c3a0878d19ade9b3716274278a21fbe3fd1c5da855797
data/CHANGELOG.md CHANGED
@@ -1,7 +1,19 @@
1
1
  # Unreleased
2
2
 
3
+ # 0.15.0
4
+
5
+ - Discard sockets rather than explictly close them when a fork is detected. #126.
6
+ - Allow to configure sentinel client via url. #117.
7
+ - Fix sentinel to preverse the auth/password when refreshing the sentinel list. #107.
8
+
9
+ # 0.14.1
10
+
11
+ - Include the timeout value in TimeoutError messages.
12
+ - Fix connection keep-alive on FreeBSD. #102.
13
+
3
14
  # 0.14.0
4
15
 
16
+ - Implement Sentinels list automatic refresh.
5
17
  - hiredis binding now implement GC compaction and write barriers.
6
18
  - hiredis binding now properly release the GVL around `connect(2)`.
7
19
  - hiredis the client memory is now re-used on reconnection when possible to reduce allocation churn.
data/Gemfile.lock CHANGED
@@ -1,16 +1,16 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- redis-client (0.14.0)
4
+ redis-client (0.15.0)
5
5
  connection_pool
6
6
 
7
7
  GEM
8
8
  remote: https://rubygems.org/
9
9
  specs:
10
10
  ast (2.4.2)
11
- benchmark-ips (2.11.0)
11
+ benchmark-ips (2.12.0)
12
12
  byebug (11.1.3)
13
- connection_pool (2.3.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.3)
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.23)
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
@@ -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 resonsible 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
@@ -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:
@@ -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
@@ -24,6 +24,10 @@ class RedisClient
24
24
  @io.to_io.close
25
25
  end
26
26
 
27
+ def reopen(*args)
28
+ @io.to_io.reopen(*args)
29
+ end
30
+
27
31
  def closed?
28
32
  @io.to_io.closed?
29
33
  end
@@ -70,9 +74,9 @@ class RedisClient
70
74
  return total
71
75
  end
72
76
  when :wait_readable
73
- @io.to_io.wait_readable(@read_timeout) or raise ReadTimeoutError
77
+ @io.to_io.wait_readable(@read_timeout) or raise(ReadTimeoutError, "Waited #{@read_timeout} seconds")
74
78
  when :wait_writable
75
- @io.to_io.wait_writable(@write_timeout) or raise WriteTimeoutError
79
+ @io.to_io.wait_writable(@write_timeout) or raise(WriteTimeoutError, "Waited #{@write_timeout} seconds")
76
80
  when nil
77
81
  raise Errno::ECONNRESET
78
82
  else
@@ -137,10 +141,10 @@ class RedisClient
137
141
  return if !strict || remaining <= 0
138
142
  when :wait_readable
139
143
  unless @io.to_io.wait_readable(@read_timeout)
140
- raise ReadTimeoutError unless @blocking_reads
144
+ raise ReadTimeoutError, "Waited #{@read_timeout} seconds" unless @blocking_reads
141
145
  end
142
146
  when :wait_writable
143
- @io.to_io.wait_writable(@write_timeout) or raise WriteTimeoutError
147
+ @io.to_io.wait_writable(@write_timeout) or raise(WriteTimeoutError, "Waited #{@write_timeout} seconds")
144
148
  when nil
145
149
  raise EOFError
146
150
  else
@@ -58,6 +58,13 @@ class RedisClient
58
58
  super
59
59
  end
60
60
 
61
+ def discard
62
+ unless @io.closed?
63
+ @io.reopen(File::NULL)
64
+ end
65
+ close
66
+ end
67
+
61
68
  def read_timeout=(timeout)
62
69
  @read_timeout = timeout
63
70
  @io.read_timeout = timeout if @io
@@ -101,6 +108,12 @@ class RedisClient
101
108
  raise ConnectionError, error.message
102
109
  end
103
110
 
111
+ def measure_round_trip_delay
112
+ start = Process.clock_gettime(Process::CLOCK_MONOTONIC, :float_millisecond)
113
+ call(["PING"], @read_timeout)
114
+ Process.clock_gettime(Process::CLOCK_MONOTONIC, :float_millisecond) - start
115
+ end
116
+
104
117
  private
105
118
 
106
119
  def connect
@@ -152,7 +165,7 @@ class RedisClient
152
165
  private_constant :KEEP_ALIVE_TTL
153
166
  private_constant :KEEP_ALIVE_PROBES
154
167
 
155
- if %i[SOL_SOCKET TCP_KEEPIDLE TCP_KEEPINTVL TCP_KEEPCNT].all? { |c| Socket.const_defined? c } # Linux
168
+ if %i[SOL_TCP SOL_SOCKET TCP_KEEPIDLE TCP_KEEPINTVL TCP_KEEPCNT].all? { |c| Socket.const_defined? c } # Linux
156
169
  def enable_socket_keep_alive(socket)
157
170
  socket.setsockopt(Socket::SOL_SOCKET, Socket::SO_KEEPALIVE, true)
158
171
  socket.setsockopt(Socket::SOL_TCP, Socket::TCP_KEEPIDLE, KEEP_ALIVE_INTERVAL)
@@ -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)
10
+ attr_reader :name
11
+
12
+ def initialize(sentinels:, role: :master, name: nil, url: nil, **client_config)
11
13
  unless %i(master replica slave).include?(role)
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,8 +42,6 @@ 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
46
  @role = role
32
47
  @mutex = Mutex.new
@@ -35,6 +50,7 @@ class RedisClient
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.0"
4
+ VERSION = "0.15.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,11 @@ class RedisClient
349
358
  self
350
359
  end
351
360
 
361
+ def discard
362
+ @raw_connection&.discard
363
+ self
364
+ end
365
+
352
366
  def pipelined
353
367
  pipeline = Pipeline.new(@command_builder)
354
368
  yield pipeline
@@ -609,7 +623,7 @@ class RedisClient
609
623
  end
610
624
 
611
625
  def ensure_connected(retryable: true)
612
- close if !config.inherit_socket && @pid != PIDCache.pid
626
+ discard if !config.inherit_socket && @pid != PIDCache.pid
613
627
 
614
628
  if @disable_reconnection
615
629
  if block_given?
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.0
4
+ version: 0.15.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-10 00:00:00.000000000 Z
11
+ date: 2023-08-01 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.3.7
80
81
  signing_key:
81
82
  specification_version: 4
82
83
  summary: Simple low-level client for Redis 6+