redlock 2.0.6 → 2.1.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: 9dd90bf121367947d80385e0e24ec5aabe59e0e5452ee0d6dc7904fbd0a93fc7
4
- data.tar.gz: d42662fb6a39139c47a9b0eff70efcdf5f59c067a1ab9d2205d0d881a94f2a73
3
+ metadata.gz: 229c8a5a2a6c074810df969cd50e20bac41f8bf47e3d5714fe21975888702fb0
4
+ data.tar.gz: d9e1079662c2ef7ddd23d3e03696c48d6b9ae7fe37c0b65760c3a90be07bc239
5
5
  SHA512:
6
- metadata.gz: 3461e69ab931dd23bb5cf7a6d6d819a0fecf76eccb62a37e058e27a40780a5a9141f56b3d274ca94e4e51dd3edd9cd0c767ed8b3b2d6d6c33a5590d7b4531136
7
- data.tar.gz: e29da6290a7192dc20ed68171c793a8b682a8849ecced7fab68d72a289f033661296846f4c908a82579a43bb5dbfa51cdd5392bef8ef728eaca5f4275d61addd
6
+ metadata.gz: 6a3436508b649c79682b310ff57094aa06a532ebde60b193d720dcc88116166a9bacc5e60701515528ef9cf8b7946e246c2bc8341a75eb948cc23ecd1a7be8ff
7
+ data.tar.gz: d2fc1c15c559a684f87b08ccd1e7e3adc9988323111803c1ca66f2df5381890c09802218dc86477e1de2099bd7aafca1e5e368c9f15fd85ea9f5d433846c0928
@@ -13,7 +13,7 @@ jobs:
13
13
 
14
14
  strategy:
15
15
  matrix:
16
- ruby-version: [3.2, 3.1, "3.0", "2.7", "2.6", "2.5", "ruby-head"]
16
+ ruby-version: [3.2, 3.1, "3.0", "2.7", "ruby-head"]
17
17
 
18
18
  steps:
19
19
  - uses: actions/checkout@v2
data/Makefile CHANGED
@@ -1,9 +1,9 @@
1
1
  default: test
2
2
  test:
3
- docker-compose run --rm test
3
+ docker compose run --rm test
4
4
 
5
5
  build:
6
- docker-compose run --rm test gem build redlock.gemspec
6
+ docker compose run --rm test gem build redlock.gemspec
7
7
 
8
8
  publish:
9
- docker-compose run --rm test gem push `ls -lt *gem | head -n 1 | awk '{ print $$9 }'`
9
+ docker compose run --rm test gem push `ls -lt *gem | head -n 1 | awk '{ print $$9 }'`
@@ -244,11 +244,25 @@ module Redlock
244
244
  end
245
245
  end
246
246
 
247
+ # Exception classes that may be raised for NOSCRIPT errors.
248
+ # RedisClient::CommandError is raised when using redis-client directly.
249
+ # Redis::CommandError (or its subclass Redis::NoScriptError) is raised
250
+ # when using the redis gem wrapper.
251
+ # See: https://github.com/leandromoreira/redlock-rb/issues/124
252
+ # See: https://github.com/leandromoreira/redlock-rb/issues/148
253
+ def script_error_classes
254
+ @script_error_classes ||= begin
255
+ classes = [RedisClient::CommandError]
256
+ classes << Redis::CommandError if defined?(Redis::CommandError)
257
+ classes
258
+ end
259
+ end
260
+
247
261
  def recover_from_script_flush
248
262
  retry_on_noscript = true
249
263
  begin
250
264
  yield
251
- rescue RedisClient::CommandError => e
265
+ rescue *script_error_classes => e
252
266
  # When somebody has flushed the Redis instance's script cache, we might
253
267
  # want to reload our scripts. Only attempt this once, though, to avoid
254
268
  # going into an infinite loop.
@@ -1,3 +1,3 @@
1
1
  module Redlock
2
- VERSION = '2.0.6'
2
+ VERSION = '2.1.0'
3
3
  end
data/redlock.gemspec CHANGED
@@ -5,14 +5,15 @@ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
5
5
  require 'redlock/version'
6
6
 
7
7
  Gem::Specification.new do |spec|
8
- spec.name = 'redlock'
9
- spec.version = Redlock::VERSION
10
- spec.authors = ['Leandro Moreira']
11
- spec.email = ['leandro.ribeiro.moreira@gmail.com']
12
- spec.summary = 'Distributed lock using Redis written in Ruby.'
13
- spec.description = 'Distributed lock using Redis written in Ruby. Highly inspired by https://github.com/antirez/redlock-rb.'
14
- spec.homepage = 'https://github.com/leandromoreira/redlock-rb'
15
- spec.license = 'BSD-2-Clause'
8
+ spec.name = 'redlock'
9
+ spec.version = Redlock::VERSION
10
+ spec.authors = ['Leandro Moreira']
11
+ spec.email = ['leandro.ribeiro.moreira@gmail.com']
12
+ spec.summary = 'Distributed lock using Redis written in Ruby.'
13
+ spec.description = 'Distributed lock using Redis written in Ruby. Highly inspired by https://github.com/antirez/redlock-rb.'
14
+ spec.homepage = 'https://github.com/leandromoreira/redlock-rb'
15
+ spec.license = 'BSD-2-Clause'
16
+ spec.required_ruby_version = '>= 2.7.0'
16
17
 
17
18
  spec.files = `git ls-files -z`.split("\x0")
18
19
  spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
@@ -21,8 +22,8 @@ Gem::Specification.new do |spec|
21
22
  spec.add_dependency 'redis-client', '>= 0.14.1', '< 1.0.0'
22
23
 
23
24
  spec.add_development_dependency 'connection_pool', '~> 2.2'
24
- spec.add_development_dependency 'coveralls', '~> 0.8'
25
- spec.add_development_dependency 'json', '>= 2.3.0', '~> 2.3.1'
25
+ spec.add_development_dependency 'coveralls_reborn', '~> 0.29'
26
+ spec.add_development_dependency 'json', '>= 2.3.0'
26
27
  spec.add_development_dependency 'rake', '>= 11.1.2', '~> 13.0'
27
28
  spec.add_development_dependency 'rspec', '~> 3', '>= 3.0.0'
28
29
  end
data/spec/client_spec.rb CHANGED
@@ -108,17 +108,13 @@ RSpec.describe Redlock::Client do
108
108
  it 'fails to acquire a lock if majority of Redis instances are not available' do
109
109
  redlock = Redlock::Client.new(servers_without_quorum)
110
110
 
111
- expected_msg = <<~MSG
112
- failed to acquire lock on 'Too many Redis errors prevented lock acquisition:
113
- RedisClient::CannotConnectError: Connection refused - connect(2) for 127.0.0.1:46864
114
- RedisClient::CannotConnectError: Connection refused - connect(2) for 127.0.0.1:46864'
115
- MSG
116
-
117
111
  expect {
118
112
  redlock.lock(resource_key, ttl)
119
113
  }.to raise_error do |error|
120
114
  expect(error).to be_a(Redlock::LockAcquisitionError)
121
- expect(error.message).to eq(expected_msg.chomp)
115
+ expect(error.message).to include('Too many Redis errors prevented lock acquisition')
116
+ expect(error.message).to include('RedisClient::CannotConnectError')
117
+ expect(error.message).to include('Connection refused')
122
118
  expect(error.errors.size).to eq(2)
123
119
  end
124
120
  end
@@ -381,6 +377,108 @@ RSpec.describe Redlock::Client do
381
377
  end
382
378
  end
383
379
 
380
+ context 'when using the redis gem (Redis::CommandError)' do
381
+ # The redis gem raises Redis::CommandError (or Redis::NoScriptError) instead of
382
+ # RedisClient::CommandError. This tests that both error hierarchies are handled.
383
+ # See: https://github.com/leandromoreira/redlock-rb/issues/124
384
+ # See: https://github.com/leandromoreira/redlock-rb/issues/148
385
+
386
+ before(:all) do
387
+ # Define Redis::CommandError if not already defined (simulates redis gem being loaded)
388
+ unless defined?(Redis::CommandError)
389
+ module Redis
390
+ class CommandError < StandardError; end
391
+ end
392
+ @redis_command_error_defined_by_test = true
393
+ end
394
+ end
395
+
396
+ after(:all) do
397
+ # Clean up the mock class if we defined it
398
+ if @redis_command_error_defined_by_test
399
+ Redis.send(:remove_const, :CommandError)
400
+ Object.send(:remove_const, :Redis) if Redis.constants.empty?
401
+ end
402
+ end
403
+
404
+ describe '#script_error_classes' do
405
+ it 'includes RedisClient::CommandError' do
406
+ redis_instance = lock_manager.instance_variable_get(:@servers).first
407
+ expect(redis_instance.send(:script_error_classes)).to include(RedisClient::CommandError)
408
+ end
409
+
410
+ it 'includes Redis::CommandError when defined' do
411
+ redis_instance = lock_manager.instance_variable_get(:@servers).first
412
+ # Clear memoization to pick up the newly defined class
413
+ redis_instance.instance_variable_set(:@script_error_classes, nil)
414
+ expect(redis_instance.send(:script_error_classes)).to include(Redis::CommandError)
415
+ end
416
+ end
417
+
418
+ context 'when Redis::CommandError NOSCRIPT is raised' do
419
+ it 'recovers by reloading scripts' do
420
+ redis_instance = lock_manager.instance_variable_get(:@servers).first
421
+ # Clear memoization to ensure Redis::CommandError is included
422
+ redis_instance.instance_variable_set(:@script_error_classes, nil)
423
+
424
+ call_count = 0
425
+ allow(redis_instance).to receive(:synchronize).and_wrap_original do |original_method, &block|
426
+ call_count += 1
427
+ if call_count == 1
428
+ # First call raises Redis::CommandError with NOSCRIPT
429
+ raise Redis::CommandError, 'NOSCRIPT No matching script'
430
+ else
431
+ original_method.call(&block)
432
+ end
433
+ end
434
+
435
+ expect(redis_instance).to receive(:load_scripts).and_call_original
436
+
437
+ # Should recover and successfully lock
438
+ lock_info = lock_manager.lock(resource_key, ttl)
439
+ expect(lock_info).to be_truthy
440
+ lock_manager.unlock(lock_info) if lock_info
441
+ end
442
+
443
+ it 'does not retry more than once per operation' do
444
+ redis_instance = lock_manager.instance_variable_get(:@servers).first
445
+ redis_instance.instance_variable_set(:@script_error_classes, nil)
446
+
447
+ # Always raise Redis::CommandError NOSCRIPT
448
+ allow(redis_instance).to receive(:synchronize).and_raise(
449
+ Redis::CommandError, 'NOSCRIPT No matching script'
450
+ )
451
+
452
+ load_scripts_count = 0
453
+ allow(redis_instance).to receive(:load_scripts) { load_scripts_count += 1 }
454
+
455
+ expect {
456
+ lock_manager.lock(resource_key, ttl)
457
+ }.to raise_error(Redlock::LockAcquisitionError)
458
+
459
+ # Should have called load_scripts once per retry attempt (8 times total:
460
+ # lock + unlock for each of retry_count+1 attempts, but unlock swallows errors)
461
+ expect(load_scripts_count).to eq(8)
462
+ end
463
+
464
+ it 're-raises non-NOSCRIPT Redis::CommandError' do
465
+ redis_instance = lock_manager.instance_variable_get(:@servers).first
466
+ redis_instance.instance_variable_set(:@script_error_classes, nil)
467
+
468
+ allow(redis_instance).to receive(:synchronize).and_raise(
469
+ Redis::CommandError, 'ERR some other error'
470
+ )
471
+
472
+ expect {
473
+ lock_manager.lock(resource_key, ttl)
474
+ }.to raise_error(Redlock::LockAcquisitionError) do |e|
475
+ expect(e.errors[0]).to be_a(Redis::CommandError)
476
+ expect(e.errors[0].message).to eq('ERR some other error')
477
+ end
478
+ end
479
+ end
480
+ end
481
+
384
482
  describe 'block syntax' do
385
483
  context 'when lock is available' do
386
484
  it 'locks' do
metadata CHANGED
@@ -1,14 +1,13 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: redlock
3
3
  version: !ruby/object:Gem::Version
4
- version: 2.0.6
4
+ version: 2.1.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Leandro Moreira
8
- autorequire:
9
8
  bindir: bin
10
9
  cert_chain: []
11
- date: 2023-10-30 00:00:00.000000000 Z
10
+ date: 1980-01-02 00:00:00.000000000 Z
12
11
  dependencies:
13
12
  - !ruby/object:Gem::Dependency
14
13
  name: redis-client
@@ -45,19 +44,19 @@ dependencies:
45
44
  - !ruby/object:Gem::Version
46
45
  version: '2.2'
47
46
  - !ruby/object:Gem::Dependency
48
- name: coveralls
47
+ name: coveralls_reborn
49
48
  requirement: !ruby/object:Gem::Requirement
50
49
  requirements:
51
50
  - - "~>"
52
51
  - !ruby/object:Gem::Version
53
- version: '0.8'
52
+ version: '0.29'
54
53
  type: :development
55
54
  prerelease: false
56
55
  version_requirements: !ruby/object:Gem::Requirement
57
56
  requirements:
58
57
  - - "~>"
59
58
  - !ruby/object:Gem::Version
60
- version: '0.8'
59
+ version: '0.29'
61
60
  - !ruby/object:Gem::Dependency
62
61
  name: json
63
62
  requirement: !ruby/object:Gem::Requirement
@@ -65,9 +64,6 @@ dependencies:
65
64
  - - ">="
66
65
  - !ruby/object:Gem::Version
67
66
  version: 2.3.0
68
- - - "~>"
69
- - !ruby/object:Gem::Version
70
- version: 2.3.1
71
67
  type: :development
72
68
  prerelease: false
73
69
  version_requirements: !ruby/object:Gem::Requirement
@@ -75,9 +71,6 @@ dependencies:
75
71
  - - ">="
76
72
  - !ruby/object:Gem::Version
77
73
  version: 2.3.0
78
- - - "~>"
79
- - !ruby/object:Gem::Version
80
- version: 2.3.1
81
74
  - !ruby/object:Gem::Dependency
82
75
  name: rake
83
76
  requirement: !ruby/object:Gem::Requirement
@@ -150,7 +143,6 @@ homepage: https://github.com/leandromoreira/redlock-rb
150
143
  licenses:
151
144
  - BSD-2-Clause
152
145
  metadata: {}
153
- post_install_message:
154
146
  rdoc_options: []
155
147
  require_paths:
156
148
  - lib
@@ -158,15 +150,14 @@ required_ruby_version: !ruby/object:Gem::Requirement
158
150
  requirements:
159
151
  - - ">="
160
152
  - !ruby/object:Gem::Version
161
- version: '0'
153
+ version: 2.7.0
162
154
  required_rubygems_version: !ruby/object:Gem::Requirement
163
155
  requirements:
164
156
  - - ">="
165
157
  - !ruby/object:Gem::Version
166
158
  version: '0'
167
159
  requirements: []
168
- rubygems_version: 3.4.10
169
- signing_key:
160
+ rubygems_version: 4.0.3
170
161
  specification_version: 4
171
162
  summary: Distributed lock using Redis written in Ruby.
172
163
  test_files: