redis-client 0.8.1 → 0.10.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: e4699c416da87ac3ed64b078ac60643a3baaa7fc2e3884feb58cc86aeb619083
4
- data.tar.gz: 5305b72684041089b57c12f493b16e81901718654058e2a5f920b18bb2b83792
3
+ metadata.gz: 27142ef61a44133a72e2a087afc39986ebcfe5fae30a7ee5f6eb3f702ef35ad2
4
+ data.tar.gz: f078880a52dec2f62d3b68188cfea8108ec433742f2f7e697def7a7bece332ac
5
5
  SHA512:
6
- metadata.gz: 119c4cf4e1a0eed4f9e67ad2be84fc6a63404cde61519a697efd85b643779cb41b30b32f765ec3308ccaf7fa4903684c9dab1ecb86b1289db2566b1943ac7010
7
- data.tar.gz: dbcba8c22c20d4ac33d1e7a495bf95cb30bca7a8b63d98d8944f2c61abfefbf4a40fc3d3222591a85ab8936c5b9e83c946095458b9b31748fe3e3e4e4e37db9e
6
+ metadata.gz: 76c7b23cb0ba419fdb9505e5cd4eb58c1c6d38d2e2fcd82778429b346dfa02a94ce4a9077c28252d8c71d1667cc45270bfc0790190e42b0d37341493bbb105f5
7
+ data.tar.gz: 071ec50fabaa72512101d0f408154e432e5f9950927607d8d6bcf8c7f3d5d4c9f49f40de3a24aa6957019751a598b70ae9db525002c03d7beb05312fc082e053
data/CHANGELOG.md CHANGED
@@ -1,8 +1,18 @@
1
1
  # Unreleased
2
2
 
3
+ # 0.10.0
4
+
5
+ - Added instance scoped middlewares. See: #53
6
+ - Allow subclasses of accepted types as command arguments. Fix: #51
7
+ - Improve hiredis driver error messages.
8
+
9
+ # 0.9.0
10
+
11
+ - Automatically reconnect if the process was forked.
12
+
3
13
  # 0.8.1
4
14
 
5
- - Make the client resilient to `Timeout.timeout` or `Thread#kill` use (it still still very much discouraged to use).
15
+ - Make the client resilient to `Timeout.timeout` or `Thread#kill` use (it still is very much discouraged to use either).
6
16
  Use of async interrupts could cause responses to be interleaved.
7
17
  - hiredis: handle commands returning a top-level `false` (no command does this today, but some extensions might).
8
18
  - Workaround a bug in Ruby 2.6 causing a crash if the `debug` gem is enabled when `redis-client` is being required. Fix: #48
data/Gemfile.lock CHANGED
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- redis-client (0.8.1)
4
+ redis-client (0.10.0)
5
5
  connection_pool
6
6
 
7
7
  GEM
data/README.md CHANGED
@@ -343,12 +343,13 @@ end
343
343
 
344
344
  ## Production
345
345
 
346
- ### Instrumentation
346
+ ### Instrumentation and Middlewares
347
347
 
348
- `redis-client` offers a public instrumentation API monitoring tools.
348
+ `redis-client` offers a public middleware API to aid in monitoring and library extension. Middleware can be registered
349
+ either globally or on a given configuration instance.
349
350
 
350
351
  ```ruby
351
- module MyRedisInstrumentation
352
+ module MyGlobalRedisInstrumentation
352
353
  def connect(redis_config)
353
354
  MyMonitoringService.instrument("redis.connect") { super }
354
355
  end
@@ -361,10 +362,17 @@ module MyRedisInstrumentation
361
362
  MyMonitoringService.instrument("redis.pipeline") { super }
362
363
  end
363
364
  end
364
- RedisClient.register(MyRedisInstrumentation)
365
+ RedisClient.register(MyGlobalRedisInstrumentation)
365
366
  ```
366
367
 
367
- Note that this instrumentation is global.
368
+ Note that `RedisClient.register` is global and apply to all `RedisClient` instances.
369
+
370
+ To add middlewares to only a single client, you can provide them when creating the config:
371
+
372
+ ```ruby
373
+ redis_config = RedisClient.config(middlewares: [AnotherRedisInstrumentation])
374
+ redis_config.new_client
375
+ ```
368
376
 
369
377
  ### Timeouts
370
378
 
@@ -437,15 +445,6 @@ Contrary to the `redis` gem, `redis-client` doesn't protect against concurrent a
437
445
  To use `redis-client` in concurrent environments, you MUST use a connection pool, or
438
446
  have one client per Thread or Fiber.
439
447
 
440
- ### Fork Safety
441
-
442
- `redis-client` doesn't try to detect forked processes. You MUST disconnect all clients before forking your process.
443
-
444
- ```ruby
445
- redis.close
446
- Process.fork ...
447
- ```
448
-
449
448
  ## Development
450
449
 
451
450
  After checking out the repo, run `bin/setup` to install dependencies. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
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
@@ -12,8 +12,9 @@ class RedisClient
12
12
  DEFAULT_DB = 0
13
13
 
14
14
  module Common
15
- attr_reader :db, :password, :id, :ssl, :ssl_params, :command_builder,
16
- :connect_timeout, :read_timeout, :write_timeout, :driver, :connection_prelude, :protocol
15
+ attr_reader :db, :password, :id, :ssl, :ssl_params, :command_builder, :inherit_socket,
16
+ :connect_timeout, :read_timeout, :write_timeout, :driver, :connection_prelude, :protocol,
17
+ :middlewares_stack
17
18
 
18
19
  alias_method :ssl?, :ssl
19
20
 
@@ -32,7 +33,9 @@ class RedisClient
32
33
  protocol: 3,
33
34
  client_implementation: RedisClient,
34
35
  command_builder: CommandBuilder,
35
- reconnect_attempts: false
36
+ inherit_socket: false,
37
+ reconnect_attempts: false,
38
+ middlewares: false
36
39
  )
37
40
  @username = username
38
41
  @password = password
@@ -54,10 +57,20 @@ class RedisClient
54
57
  end
55
58
 
56
59
  @command_builder = command_builder
60
+ @inherit_socket = inherit_socket
57
61
 
58
62
  reconnect_attempts = Array.new(reconnect_attempts, 0).freeze if reconnect_attempts.is_a?(Integer)
59
63
  @reconnect_attempts = reconnect_attempts
60
64
  @connection_prelude = build_connection_prelude
65
+
66
+ middlewares_stack = Middlewares
67
+ if middlewares && !middlewares.empty?
68
+ middlewares_stack = Class.new(Middlewares)
69
+ middlewares.each do |mod|
70
+ middlewares_stack.include(mod)
71
+ end
72
+ end
73
+ @middlewares_stack = middlewares_stack
61
74
  end
62
75
 
63
76
  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.8.1"
4
+ VERSION = "0.10.0"
5
5
  end
data/lib/redis_client.rb CHANGED
@@ -67,6 +67,7 @@ class RedisClient
67
67
  @read_timeout = read_timeout
68
68
  @write_timeout = write_timeout
69
69
  @command_builder = config.command_builder
70
+ @pid = Process.pid
70
71
  end
71
72
 
72
73
  def timeout=(timeout)
@@ -144,7 +145,7 @@ class RedisClient
144
145
  end
145
146
 
146
147
  def register(middleware)
147
- Middlewares.extend(middleware)
148
+ Middlewares.include(middleware)
148
149
  end
149
150
  end
150
151
 
@@ -152,6 +153,7 @@ class RedisClient
152
153
 
153
154
  def initialize(config, **)
154
155
  super
156
+ @middlewares = config.middlewares_stack.new(self)
155
157
  @raw_connection = nil
156
158
  @disable_reconnection = false
157
159
  end
@@ -194,7 +196,7 @@ class RedisClient
194
196
  def call(*command, **kwargs)
195
197
  command = @command_builder.generate(command, kwargs)
196
198
  result = ensure_connected do |connection|
197
- Middlewares.call(command, config) do
199
+ @middlewares.call(command, config) do
198
200
  connection.call(command, nil)
199
201
  end
200
202
  end
@@ -209,7 +211,7 @@ class RedisClient
209
211
  def call_v(command)
210
212
  command = @command_builder.generate(command)
211
213
  result = ensure_connected do |connection|
212
- Middlewares.call(command, config) do
214
+ @middlewares.call(command, config) do
213
215
  connection.call(command, nil)
214
216
  end
215
217
  end
@@ -224,7 +226,7 @@ class RedisClient
224
226
  def call_once(*command, **kwargs)
225
227
  command = @command_builder.generate(command, kwargs)
226
228
  result = ensure_connected(retryable: false) do |connection|
227
- Middlewares.call(command, config) do
229
+ @middlewares.call(command, config) do
228
230
  connection.call(command, nil)
229
231
  end
230
232
  end
@@ -239,7 +241,7 @@ class RedisClient
239
241
  def call_once_v(command)
240
242
  command = @command_builder.generate(command)
241
243
  result = ensure_connected(retryable: false) do |connection|
242
- Middlewares.call(command, config) do
244
+ @middlewares.call(command, config) do
243
245
  connection.call(command, nil)
244
246
  end
245
247
  end
@@ -255,7 +257,7 @@ class RedisClient
255
257
  command = @command_builder.generate(command, kwargs)
256
258
  error = nil
257
259
  result = ensure_connected do |connection|
258
- Middlewares.call(command, config) do
260
+ @middlewares.call(command, config) do
259
261
  connection.call(command, timeout)
260
262
  end
261
263
  rescue ReadTimeoutError => error
@@ -275,7 +277,7 @@ class RedisClient
275
277
  command = @command_builder.generate(command)
276
278
  error = nil
277
279
  result = ensure_connected do |connection|
278
- Middlewares.call(command, config) do
280
+ @middlewares.call(command, config) do
279
281
  connection.call(command, timeout)
280
282
  end
281
283
  rescue ReadTimeoutError => error
@@ -346,7 +348,7 @@ class RedisClient
346
348
  else
347
349
  results = ensure_connected(retryable: pipeline._retryable?) do |connection|
348
350
  commands = pipeline._commands
349
- Middlewares.call_pipelined(commands, config) do
351
+ @middlewares.call_pipelined(commands, config) do
350
352
  connection.call_pipelined(commands, pipeline._timeouts)
351
353
  end
352
354
  end
@@ -366,7 +368,7 @@ class RedisClient
366
368
  begin
367
369
  if transaction = build_transaction(&block)
368
370
  commands = transaction._commands
369
- results = Middlewares.call_pipelined(commands, config) do
371
+ results = @middlewares.call_pipelined(commands, config) do
370
372
  connection.call_pipelined(commands, nil)
371
373
  end.last
372
374
  else
@@ -385,7 +387,7 @@ class RedisClient
385
387
  else
386
388
  ensure_connected(retryable: transaction._retryable?) do |connection|
387
389
  commands = transaction._commands
388
- Middlewares.call_pipelined(commands, config) do
390
+ @middlewares.call_pipelined(commands, config) do
389
391
  connection.call_pipelined(commands, nil)
390
392
  end.last
391
393
  end
@@ -416,7 +418,7 @@ class RedisClient
416
418
  end
417
419
 
418
420
  def close
419
- raw_connection&.close
421
+ @raw_connection&.close
420
422
  @raw_connection = nil
421
423
  self
422
424
  end
@@ -597,6 +599,8 @@ class RedisClient
597
599
  end
598
600
 
599
601
  def ensure_connected(retryable: true)
602
+ close if !config.inherit_socket && @pid != Process.pid
603
+
600
604
  if @disable_reconnection
601
605
  if block_given?
602
606
  yield @raw_connection
@@ -614,7 +618,6 @@ class RedisClient
614
618
  connection
615
619
  end
616
620
  rescue ConnectionError, ProtocolError => error
617
- connection&.close
618
621
  close
619
622
 
620
623
  if !@disable_reconnection && config.retry_connecting?(tries, error)
@@ -631,7 +634,6 @@ class RedisClient
631
634
  @disable_reconnection = true
632
635
  yield connection
633
636
  rescue ConnectionError, ProtocolError
634
- connection&.close
635
637
  close
636
638
  raise
637
639
  ensure
@@ -646,7 +648,9 @@ class RedisClient
646
648
  end
647
649
 
648
650
  def connect
649
- connection = Middlewares.connect(config) do
651
+ @pid = Process.pid
652
+
653
+ connection = @middlewares.connect(config) do
650
654
  config.driver.new(
651
655
  config,
652
656
  connect_timeout: connect_timeout,
@@ -664,13 +668,13 @@ class RedisClient
664
668
  # The connection prelude is deliberately not sent to Middlewares
665
669
  if config.sentinel?
666
670
  prelude << ["ROLE"]
667
- role, = Middlewares.call_pipelined(prelude, config) do
671
+ role, = @middlewares.call_pipelined(prelude, config) do
668
672
  connection.call_pipelined(prelude, nil).last
669
673
  end
670
674
  config.check_role!(role)
671
675
  else
672
676
  unless prelude.empty?
673
- Middlewares.call_pipelined(prelude, config) do
677
+ @middlewares.call_pipelined(prelude, config) do
674
678
  connection.call_pipelined(prelude, nil)
675
679
  end
676
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.8.1
4
+ version: 0.10.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-16 00:00:00.000000000 Z
11
+ date: 2022-10-10 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: connection_pool