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. - #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.
Constructor Details
#initialize(servers = DEFAULT_REDIS_URLS, options = {}) ⇒ Client
Create a distributed lock manager implementing redlock algorithm. Params:
serversThe array of redis connection URLs or Redis connection instances. Or a mix of both.
optionsretry_countbeing how many times it'll try to lock a resource (default: 3)retry_delaybeing how many ms to sleep before try to lock again (default: 200)retry_jitterbeing how many ms to jitter retry delay (default: 50)redis_timeoutbeing how the Redis timeout will be set in seconds (default: 0.1)time_sourcebeing 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_infothe 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:
resourcethe 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:
resourcethe resource (or key) string to be locked.
ttlThe time-to-live in ms for the lock.
optionsHash of optional parameters
retry_count: seeinitializeretry_delay: seeinitializeretry_jitter: seeinitializeextend: A lock ("lock_info") to extend.extend_only_if_locked: Boolean, ifextendis given, only acquire lock if currently heldextend_only_if_life: Deprecated, same asextend_only_if_lockedextend_life: Deprecated, same asextend_only_if_locked
blockan 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_infothe 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
280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 |
# File 'lib/redlock/client.rb', line 280 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_infothe 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_infothe 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_infothe 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 |