redis-client 0.8.1 → 0.10.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: 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