redis-client 0.9.0 → 0.11.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: 95dd96eda7210df59b85fafb8d2df5fa46cf0d3d2da10ad1ebf60137b8ec2652
4
- data.tar.gz: d417951e19d9a20b7f96a2d4e520d433cd52c38209d8c46499ddfc0d6d4be941
3
+ metadata.gz: 7f28d878a923f684e1ddade2a735339dc7ae55d3091b3d21b812fcf1b232b8fe
4
+ data.tar.gz: 5cc874fa373695185c4b5f040e8368be66550596838faadab8b3e57e45f8c0b0
5
5
  SHA512:
6
- metadata.gz: 0dd66a81c969a08abbb89140187e6a3a31aadcb258845082bf84bba792304ebc3d27852635fa4519805139eeecb7f405033b2b0c6acc4d39f7823873cd91dbdc
7
- data.tar.gz: 4a9f5b79ffc8d195a3fac2cd439750d43124bb78583eef840564262d25565b2e585d8d77eb3f9d2096ec938fa4db7fe7a775c5ba5f80c4e2bd91f2b121901a98
6
+ metadata.gz: ba5313f360e675d1e751f6c5138290facab7e3ea043d5328b14556050ceb44155947ad670077e22b1fb04b2aa5738a6d6b77454b5d31e0a4641c1e705f3ba545
7
+ data.tar.gz: 45463aae9b8d188cc5d1a2c0604962001bafcd266fb526fecb7ddee5081fbc28a9a5a27b8a09525b515d69266cf7740424a50408e62cf2361debe1ffb52b3d9e
data/CHANGELOG.md CHANGED
@@ -1,5 +1,16 @@
1
1
  # Unreleased
2
2
 
3
+ # 0.11.0
4
+
5
+ - hiredis: do not eagerly close the connection on read timeout, let the caller decide if a timeout is final.
6
+ - Add `Config#custom` to store configuration metadata. It can be used for per server middleware configuration.
7
+
8
+ # 0.10.0
9
+
10
+ - Added instance scoped middlewares. See: #53
11
+ - Allow subclasses of accepted types as command arguments. Fix: #51
12
+ - Improve hiredis driver error messages.
13
+
3
14
  # 0.9.0
4
15
 
5
16
  - Automatically reconnect if the process was forked.
data/Gemfile.lock CHANGED
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- redis-client (0.9.0)
4
+ redis-client (0.11.0)
5
5
  connection_pool
6
6
 
7
7
  GEM
@@ -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.21)
41
+ stackprof (0.2.22)
42
42
  toxiproxy (2.0.2)
43
43
  unicode-display_width (2.2.0)
44
44
 
data/README.md CHANGED
@@ -82,6 +82,7 @@ redis.call("GET", "mykey")
82
82
  - `write_timeout`: The write timeout, takes precedence over the general timeout when sending commands to the server.
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
  - `protocol:` The version of the RESP protocol to use. Default to `3`.
85
+ - `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.
85
86
 
86
87
  ### Sentinel support
87
88
 
@@ -343,12 +344,13 @@ end
343
344
 
344
345
  ## Production
345
346
 
346
- ### Instrumentation
347
+ ### Instrumentation and Middlewares
347
348
 
348
- `redis-client` offers a public instrumentation API monitoring tools.
349
+ `redis-client` offers a public middleware API to aid in monitoring and library extension. Middleware can be registered
350
+ either globally or on a given configuration instance.
349
351
 
350
352
  ```ruby
351
- module MyRedisInstrumentation
353
+ module MyGlobalRedisInstrumentation
352
354
  def connect(redis_config)
353
355
  MyMonitoringService.instrument("redis.connect") { super }
354
356
  end
@@ -361,11 +363,38 @@ module MyRedisInstrumentation
361
363
  MyMonitoringService.instrument("redis.pipeline") { super }
362
364
  end
363
365
  end
364
- RedisClient.register(MyRedisInstrumentation)
366
+ RedisClient.register(MyGlobalRedisInstrumentation)
365
367
  ```
366
368
 
367
- Note that this instrumentation is global.
369
+ Note that `RedisClient.register` is global and apply to all `RedisClient` instances.
368
370
 
371
+ To add middlewares to only a single client, you can provide them when creating the config:
372
+
373
+ ```ruby
374
+ redis_config = RedisClient.config(middlewares: [AnotherRedisInstrumentation])
375
+ redis_config.new_client
376
+ ```
377
+
378
+ If middlewares need a client specific configuration, `Config#custom` can be used
379
+
380
+ ```ruby
381
+ module MyGlobalRedisInstrumentation
382
+ def connect(redis_config)
383
+ MyMonitoringService.instrument("redis.connect", tags: redis_config.custom[:tags]) { super }
384
+ end
385
+
386
+ def call(command, redis_config)
387
+ MyMonitoringService.instrument("redis.query", tags: redis_config.custom[:tags]) { super }
388
+ end
389
+
390
+ def call_pipelined(commands, redis_config)
391
+ MyMonitoringService.instrument("redis.pipeline", tags: redis_config.custom[:tags]) { super }
392
+ end
393
+ end
394
+ RedisClient.register(MyGlobalRedisInstrumentation)
395
+
396
+ redis_config = RedisClient.config(custom: { tags: { "environment": Rails.env }})
397
+ ```
369
398
  ### Timeouts
370
399
 
371
400
  The client allows you to configure connect, read, and write timeouts.
data/Rakefile CHANGED
@@ -67,12 +67,15 @@ namespace :hiredis do
67
67
  end
68
68
  end
69
69
 
70
+ benchmark_suites = %w(single pipelined)
71
+ benchmark_modes = %i[ruby yjit hiredis]
70
72
  namespace :benchmark do
71
- task :record do
72
- system("rm -rf tmp/*.benchmark")
73
- %w(single pipelined).each do |suite|
74
- %i[ruby yjit hiredis].each do |mode|
75
- output_path = "benchmark/#{suite}_#{mode}.md"
73
+ benchmark_suites.each do |suite|
74
+ benchmark_modes.each do |mode|
75
+ name = "#{suite}_#{mode}"
76
+ task name do
77
+ output_path = "benchmark/#{name}.md"
78
+ sh "rm", "-f", output_path
76
79
  File.open(output_path, "w+") do |output|
77
80
  output.puts("ruby: `#{RUBY_DESCRIPTION}`\n\n")
78
81
  output.puts("redis-server: `#{`redis-server -v`.strip}`\n\n")
@@ -103,6 +106,8 @@ namespace :benchmark do
103
106
  end
104
107
  end
105
108
  end
109
+
110
+ task all: benchmark_suites.flat_map { |s| benchmark_modes.flat_map { |m| "#{s}_#{m}" } }
106
111
  end
107
112
 
108
113
  if hiredis_supported
@@ -13,7 +13,8 @@ class RedisClient
13
13
 
14
14
  module Common
15
15
  attr_reader :db, :password, :id, :ssl, :ssl_params, :command_builder, :inherit_socket,
16
- :connect_timeout, :read_timeout, :write_timeout, :driver, :connection_prelude, :protocol
16
+ :connect_timeout, :read_timeout, :write_timeout, :driver, :connection_prelude, :protocol,
17
+ :middlewares_stack, :custom
17
18
 
18
19
  alias_method :ssl?, :ssl
19
20
 
@@ -27,13 +28,15 @@ class RedisClient
27
28
  write_timeout: timeout,
28
29
  connect_timeout: timeout,
29
30
  ssl: nil,
31
+ custom: {},
30
32
  ssl_params: nil,
31
33
  driver: nil,
32
34
  protocol: 3,
33
35
  client_implementation: RedisClient,
34
36
  command_builder: CommandBuilder,
35
37
  inherit_socket: false,
36
- reconnect_attempts: false
38
+ reconnect_attempts: false,
39
+ middlewares: false
37
40
  )
38
41
  @username = username
39
42
  @password = password
@@ -48,6 +51,8 @@ class RedisClient
48
51
 
49
52
  @driver = driver ? RedisClient.driver(driver) : RedisClient.default_driver
50
53
 
54
+ @custom = custom
55
+
51
56
  @client_implementation = client_implementation
52
57
  @protocol = protocol
53
58
  unless protocol == 2 || protocol == 3
@@ -60,6 +65,15 @@ class RedisClient
60
65
  reconnect_attempts = Array.new(reconnect_attempts, 0).freeze if reconnect_attempts.is_a?(Integer)
61
66
  @reconnect_attempts = reconnect_attempts
62
67
  @connection_prelude = build_connection_prelude
68
+
69
+ middlewares_stack = Middlewares
70
+ if middlewares && !middlewares.empty?
71
+ middlewares_stack = Class.new(Middlewares)
72
+ middlewares.each do |mod|
73
+ middlewares_stack.include(mod)
74
+ end
75
+ end
76
+ @middlewares_stack = middlewares_stack
63
77
  end
64
78
 
65
79
  def username
@@ -1,8 +1,12 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  class RedisClient
4
- module Middlewares
5
- extend self
4
+ class BasicMiddleware
5
+ attr_reader :client
6
+
7
+ def initialize(client)
8
+ @client = client
9
+ end
6
10
 
7
11
  def connect(_config)
8
12
  yield
@@ -13,4 +17,7 @@ class RedisClient
13
17
  end
14
18
  alias_method :call_pipelined, :call
15
19
  end
20
+
21
+ class Middlewares < BasicMiddleware
22
+ end
16
23
  end
@@ -10,12 +10,12 @@ class RedisClient
10
10
 
11
11
  EOL = "\r\n".b.freeze
12
12
  EOL_SIZE = EOL.bytesize
13
- DUMP_TYPES = {
13
+ DUMP_TYPES = { # rubocop:disable Style/MutableConstant
14
14
  String => :dump_string,
15
15
  Symbol => :dump_symbol,
16
16
  Integer => :dump_numeric,
17
17
  Float => :dump_numeric,
18
- }.freeze
18
+ }
19
19
  PARSER_TYPES = {
20
20
  '#' => :parse_boolean,
21
21
  '$' => :parse_blob,
@@ -55,8 +55,12 @@ class RedisClient
55
55
  end
56
56
 
57
57
  def dump_any(object, buffer)
58
- method = DUMP_TYPES.fetch(object.class) do
59
- raise TypeError, "Unsupported command argument type: #{object.class}"
58
+ method = DUMP_TYPES.fetch(object.class) do |unexpected_class|
59
+ if superclass = DUMP_TYPES.keys.find { |t| t > unexpected_class }
60
+ DUMP_TYPES[unexpected_class] = DUMP_TYPES[superclass]
61
+ else
62
+ raise TypeError, "Unsupported command argument type: #{unexpected_class}"
63
+ end
60
64
  end
61
65
  send(method, object, buffer)
62
66
  end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  class RedisClient
4
- VERSION = "0.9.0"
4
+ VERSION = "0.11.0"
5
5
  end
data/lib/redis_client.rb CHANGED
@@ -145,7 +145,7 @@ class RedisClient
145
145
  end
146
146
 
147
147
  def register(middleware)
148
- Middlewares.extend(middleware)
148
+ Middlewares.include(middleware)
149
149
  end
150
150
  end
151
151
 
@@ -153,6 +153,7 @@ class RedisClient
153
153
 
154
154
  def initialize(config, **)
155
155
  super
156
+ @middlewares = config.middlewares_stack.new(self)
156
157
  @raw_connection = nil
157
158
  @disable_reconnection = false
158
159
  end
@@ -195,7 +196,7 @@ class RedisClient
195
196
  def call(*command, **kwargs)
196
197
  command = @command_builder.generate(command, kwargs)
197
198
  result = ensure_connected do |connection|
198
- Middlewares.call(command, config) do
199
+ @middlewares.call(command, config) do
199
200
  connection.call(command, nil)
200
201
  end
201
202
  end
@@ -210,7 +211,7 @@ class RedisClient
210
211
  def call_v(command)
211
212
  command = @command_builder.generate(command)
212
213
  result = ensure_connected do |connection|
213
- Middlewares.call(command, config) do
214
+ @middlewares.call(command, config) do
214
215
  connection.call(command, nil)
215
216
  end
216
217
  end
@@ -225,7 +226,7 @@ class RedisClient
225
226
  def call_once(*command, **kwargs)
226
227
  command = @command_builder.generate(command, kwargs)
227
228
  result = ensure_connected(retryable: false) do |connection|
228
- Middlewares.call(command, config) do
229
+ @middlewares.call(command, config) do
229
230
  connection.call(command, nil)
230
231
  end
231
232
  end
@@ -240,7 +241,7 @@ class RedisClient
240
241
  def call_once_v(command)
241
242
  command = @command_builder.generate(command)
242
243
  result = ensure_connected(retryable: false) do |connection|
243
- Middlewares.call(command, config) do
244
+ @middlewares.call(command, config) do
244
245
  connection.call(command, nil)
245
246
  end
246
247
  end
@@ -256,7 +257,7 @@ class RedisClient
256
257
  command = @command_builder.generate(command, kwargs)
257
258
  error = nil
258
259
  result = ensure_connected do |connection|
259
- Middlewares.call(command, config) do
260
+ @middlewares.call(command, config) do
260
261
  connection.call(command, timeout)
261
262
  end
262
263
  rescue ReadTimeoutError => error
@@ -276,7 +277,7 @@ class RedisClient
276
277
  command = @command_builder.generate(command)
277
278
  error = nil
278
279
  result = ensure_connected do |connection|
279
- Middlewares.call(command, config) do
280
+ @middlewares.call(command, config) do
280
281
  connection.call(command, timeout)
281
282
  end
282
283
  rescue ReadTimeoutError => error
@@ -347,7 +348,7 @@ class RedisClient
347
348
  else
348
349
  results = ensure_connected(retryable: pipeline._retryable?) do |connection|
349
350
  commands = pipeline._commands
350
- Middlewares.call_pipelined(commands, config) do
351
+ @middlewares.call_pipelined(commands, config) do
351
352
  connection.call_pipelined(commands, pipeline._timeouts)
352
353
  end
353
354
  end
@@ -367,7 +368,7 @@ class RedisClient
367
368
  begin
368
369
  if transaction = build_transaction(&block)
369
370
  commands = transaction._commands
370
- results = Middlewares.call_pipelined(commands, config) do
371
+ results = @middlewares.call_pipelined(commands, config) do
371
372
  connection.call_pipelined(commands, nil)
372
373
  end.last
373
374
  else
@@ -386,7 +387,7 @@ class RedisClient
386
387
  else
387
388
  ensure_connected(retryable: transaction._retryable?) do |connection|
388
389
  commands = transaction._commands
389
- Middlewares.call_pipelined(commands, config) do
390
+ @middlewares.call_pipelined(commands, config) do
390
391
  connection.call_pipelined(commands, nil)
391
392
  end.last
392
393
  end
@@ -649,7 +650,7 @@ class RedisClient
649
650
  def connect
650
651
  @pid = Process.pid
651
652
 
652
- connection = Middlewares.connect(config) do
653
+ connection = @middlewares.connect(config) do
653
654
  config.driver.new(
654
655
  config,
655
656
  connect_timeout: connect_timeout,
@@ -667,13 +668,13 @@ class RedisClient
667
668
  # The connection prelude is deliberately not sent to Middlewares
668
669
  if config.sentinel?
669
670
  prelude << ["ROLE"]
670
- role, = Middlewares.call_pipelined(prelude, config) do
671
+ role, = @middlewares.call_pipelined(prelude, config) do
671
672
  connection.call_pipelined(prelude, nil).last
672
673
  end
673
674
  config.check_role!(role)
674
675
  else
675
676
  unless prelude.empty?
676
- Middlewares.call_pipelined(prelude, config) do
677
+ @middlewares.call_pipelined(prelude, config) do
677
678
  connection.call_pipelined(prelude, nil)
678
679
  end
679
680
  end
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.9.0
4
+ version: 0.11.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: 2022-09-22 00:00:00.000000000 Z
11
+ date: 2022-11-01 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: connection_pool