Class: Redlock::Client
- Inherits:
-
Object
- Object
- Redlock::Client
- Defined in:
- lib/redlock/client.rb,
lib/redlock/testing.rb
Defined Under Namespace
Classes: RedisInstance
Constant Summary collapse
- DEFAULT_REDIS_HOST =
ENV["DEFAULT_REDIS_HOST"] || "localhost"
- DEFAULT_REDIS_PORT =
ENV["DEFAULT_REDIS_PORT"] || "6379"
- DEFAULT_REDIS_URLS =
["redis://#{DEFAULT_REDIS_HOST}:#{DEFAULT_REDIS_PORT}"]
- DEFAULT_REDIS_TIMEOUT =
0.1
- DEFAULT_RETRY_COUNT =
3
- DEFAULT_RETRY_DELAY =
200
- DEFAULT_RETRY_JITTER =
50
- CLOCK_DRIFT_FACTOR =
0.01
Class Attribute Summary collapse
-
.testing_mode ⇒ Object
Returns the value of attribute testing_mode.
Class Method Summary collapse
-
.default_time_source ⇒ Object
Returns default time source function depending on CLOCK_MONOTONIC availability.
Instance Method Summary collapse
-
#get_remaining_ttl_for_lock(lock_info) ⇒ Object
Gets remaining ttl of a resource.
-
#get_remaining_ttl_for_resource(resource) ⇒ Object
Gets remaining ttl of a resource.
-
#initialize(servers = DEFAULT_REDIS_URLS, options = {}) ⇒ Client
constructor
Create a distributed lock manager implementing redlock algorithm.
-
#lock(resource, ttl, options = {}, &block) ⇒ Object
Locks a resource for a given time.
-
#lock!(resource, *args) ⇒ Object
Locks a resource, executing the received block only after successfully acquiring the lock, and returning its return value as a result.
-
#locked?(resource) ⇒ Boolean
- Checks if a resource is locked Params:
lock_info
-
the lock that has been acquired when you locked the resource.
- Checks if a resource is locked Params:
- #testing_mode=(mode) ⇒ Object
- #try_lock_instances(resource, ttl, options) ⇒ Object
- #try_lock_instances_without_testing ⇒ Object
-
#unlock(lock_info) ⇒ Object
Unlocks a resource.
-
#unlock_without_testing ⇒ Object
Unlocks a resource.
-
#valid_lock?(lock_info) ⇒ Boolean
- Checks if a lock is still valid Params:
lock_info
-
the lock that has been acquired when you locked the resource.
- Checks if a lock is still valid Params:
Constructor Details
#initialize(servers = DEFAULT_REDIS_URLS, options = {}) ⇒ Client
Create a distributed lock manager implementing redlock algorithm. Params:
servers
-
The array of redis connection URLs or Redis connection instances. Or a mix of both.
options
-
‘retry_count` being how many times it’ll try to lock a resource (default: 3)
-
‘retry_delay` being how many ms to sleep before try to lock again (default: 200)
-
‘retry_jitter` being how many ms to jitter retry delay (default: 50)
-
‘redis_timeout` being how the Redis timeout will be set in seconds (default: 0.1)
-
‘time_source` being a callable object returning a monotonic time in milliseconds
(default: see #default_time_source)
-
48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 |
# File 'lib/redlock/client.rb', line 48 def initialize(servers = DEFAULT_REDIS_URLS, = {}) redis_timeout = [:redis_timeout] || DEFAULT_REDIS_TIMEOUT @servers = servers.map do |server| if server.is_a?(String) RedisInstance.new(url: server, timeout: redis_timeout) else RedisInstance.new(server) end end @quorum = (servers.length / 2).to_i + 1 @retry_count = [:retry_count] || DEFAULT_RETRY_COUNT @retry_delay = [:retry_delay] || DEFAULT_RETRY_DELAY @retry_jitter = [:retry_jitter] || DEFAULT_RETRY_JITTER @time_source = [:time_source] || self.class.default_time_source end |
Class Attribute Details
.testing_mode ⇒ Object
Returns the value of attribute testing_mode.
6 7 8 |
# File 'lib/redlock/testing.rb', line 6 def testing_mode @testing_mode end |
Class Method Details
.default_time_source ⇒ Object
Returns default time source function depending on CLOCK_MONOTONIC availability.
30 31 32 33 34 35 36 |
# File 'lib/redlock/client.rb', line 30 def self.default_time_source if defined?(Process::CLOCK_MONOTONIC) proc { (Process.clock_gettime(Process::CLOCK_MONOTONIC) * 1000).to_i } else proc { (Time.now.to_f * 1000).to_i } end end |
Instance Method Details
#get_remaining_ttl_for_lock(lock_info) ⇒ Object
Gets remaining ttl of a resource. The ttl is returned if the holder currently holds the lock and it has not expired, otherwise the method returns nil. Params:
lock_info
-
the lock that has been acquired when you locked the resource
125 126 127 128 129 |
# File 'lib/redlock/client.rb', line 125 def get_remaining_ttl_for_lock(lock_info) ttl_info = try_get_remaining_ttl(lock_info[:resource]) return nil if ttl_info.nil? || ttl_info[:value] != lock_info[:value] ttl_info[:ttl] end |
#get_remaining_ttl_for_resource(resource) ⇒ Object
Gets remaining ttl of a resource. If there is no valid lock, the method returns nil. Params:
resource
-
the name of the resource (string) for which to check the ttl
135 136 137 138 139 |
# File 'lib/redlock/client.rb', line 135 def get_remaining_ttl_for_resource(resource) ttl_info = try_get_remaining_ttl(resource) return nil if ttl_info.nil? ttl_info[:ttl] end |
#lock(resource, ttl, options = {}, &block) ⇒ Object
Locks a resource for a given time. Params:
resource
-
the resource (or key) string to be locked.
ttl
-
The time-to-live in ms for the lock.
options
-
Hash of optional parameters
* +retry_count+: see +initialize+
* +retry_delay+: see +initialize+
* +retry_jitter+: see +initialize+
* +extend+: A lock ("lock_info") to extend.
* +extend_only_if_locked+: Boolean, if +extend+ is given, only acquire lock if currently held
* +extend_only_if_life+: Deprecated, same as +extend_only_if_locked+
* +extend_life+: Deprecated, same as +extend_only_if_locked+
block
-
an optional block to be executed; after its execution, the lock (if successfully
acquired) is automatically unlocked.
78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 |
# File 'lib/redlock/client.rb', line 78 def lock(resource, ttl, = {}, &block) lock_info = try_lock_instances(resource, ttl, ) if [:extend_only_if_life] && !Gem::Deprecate.skip warn 'DEPRECATION WARNING: The `extend_only_if_life` option has been renamed `extend_only_if_locked`.' [:extend_only_if_locked] = [:extend_only_if_life] end if [:extend_life] && !Gem::Deprecate.skip warn 'DEPRECATION WARNING: The `extend_life` option has been renamed `extend_only_if_locked`.' [:extend_only_if_locked] = [:extend_life] end if block_given? begin yield lock_info !!lock_info ensure unlock(lock_info) if lock_info end else lock_info end end |
#lock!(resource, *args) ⇒ Object
Locks a resource, executing the received block only after successfully acquiring the lock, and returning its return value as a result. See Redlock::Client#lock for parameters.
111 112 113 114 115 116 117 118 |
# File 'lib/redlock/client.rb', line 111 def lock!(resource, *args) fail 'No block passed' unless block_given? lock(resource, *args) do |lock_info| raise LockError, resource unless lock_info return yield end end |
#locked?(resource) ⇒ Boolean
Checks if a resource is locked Params:
lock_info
-
the lock that has been acquired when you locked the resource
144 145 146 147 |
# File 'lib/redlock/client.rb', line 144 def locked?(resource) ttl = get_remaining_ttl_for_resource(resource) !(ttl.nil? || ttl.zero?) end |
#testing_mode=(mode) ⇒ Object
9 10 11 12 13 14 15 |
# File 'lib/redlock/testing.rb', line 9 def testing_mode=(mode) warn 'DEPRECATION WARNING: Instance-level `testing_mode` has been removed, and this ' + 'setter will be removed in the future. Please set the testing mode on the `Redlock::Client` ' + 'instead, e.g. `Redlock::Client.testing_mode = :bypass`.' self.class.testing_mode = mode end |
#try_lock_instances(resource, ttl, options) ⇒ Object
266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 |
# File 'lib/redlock/client.rb', line 266 def try_lock_instances(resource, ttl, ) retry_count = [:retry_count] || @retry_count tries = [:extend] ? 1 : (retry_count + 1) last_error = nil tries.times do |attempt_number| # Wait a random delay before retrying. sleep(attempt_retry_delay(attempt_number, )) if attempt_number > 0 lock_info = lock_instances(resource, ttl, ) return lock_info if lock_info rescue => e last_error = e end raise last_error if last_error false end |
#try_lock_instances_without_testing ⇒ Object
17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 |
# File 'lib/redlock/testing.rb', line 17 def try_lock_instances(resource, ttl, ) retry_count = [:retry_count] || @retry_count tries = [:extend] ? 1 : (retry_count + 1) last_error = nil tries.times do |attempt_number| # Wait a random delay before retrying. sleep(attempt_retry_delay(attempt_number, )) if attempt_number > 0 lock_info = lock_instances(resource, ttl, ) return lock_info if lock_info rescue => e last_error = e end raise last_error if last_error false end |
#unlock(lock_info) ⇒ Object
Unlocks a resource. Params:
lock_info
-
the lock that has been acquired when you locked the resource.
104 105 106 |
# File 'lib/redlock/client.rb', line 104 def unlock(lock_info) @servers.each { |s| s.unlock(lock_info[:resource], lock_info[:value]) } end |
#unlock_without_testing ⇒ Object
Unlocks a resource. Params:
lock_info
-
the lock that has been acquired when you locked the resource.
33 34 35 |
# File 'lib/redlock/testing.rb', line 33 def unlock(lock_info) @servers.each { |s| s.unlock(lock_info[:resource], lock_info[:value]) } end |
#valid_lock?(lock_info) ⇒ Boolean
Checks if a lock is still valid Params:
lock_info
-
the lock that has been acquired when you locked the resource
152 153 154 155 |
# File 'lib/redlock/client.rb', line 152 def valid_lock?(lock_info) ttl = get_remaining_ttl_for_lock(lock_info) !(ttl.nil? || ttl.zero?) end |