Class: Redstruct::Hls::Lock
- Inherits:
-
Types::Base
- Object
- Types::Base
- Redstruct::Hls::Lock
- Includes:
- Utils::Coercion, Utils::Scriptable
- Defined in:
- lib/redstruct/hls/lock.rb
Overview
Implementation of a simple binary lock (locked/not locked), with option to block and wait for the lock. Uses two redis structures: a string for the lease, and a list for blocking operations.
Constant Summary collapse
- DEFAULT_EXPIRY =
The default expiry on the underlying redis keys, in milliseconds
1000- DEFAULT_TIMEOUT =
The default timeout when blocking, in seconds; a nil value means it is non-blocking
nil
Instance Attribute Summary collapse
-
#expiry ⇒ Fixnum
readonly
expiry of the underlying redis structures in milliseconds.
-
#timeout ⇒ Fixnum?
readonly
the timeout to wait when attempting to acquire the lock, in seconds.
-
#token ⇒ ::String?
readonly
the current token or nil.
Attributes inherited from Types::Base
Serialization collapse
-
.from_h(hash, factory) ⇒ Lock
Builds a lock from a hash.
-
#to_h ⇒ Hash<Symbol, Object>
Returns a hash representation of the object.
Instance Method Summary collapse
-
#acquire ⇒ Boolean
Attempts to acquire the lock.
- #acquire_script ⇒ ::String
-
#blocking? ⇒ Boolean
Whether or not the lock will block when attempting to acquire it.
-
#initialize(expiry: DEFAULT_EXPIRY, timeout: DEFAULT_TIMEOUT, **options) ⇒ Lock
constructor
A new instance of Lock.
-
#inspectable_attributes ⇒ Object
Helper method for easy inspection.
-
#locked { ... } ⇒ Object
Executes the given block if the lock can be acquired.
-
#release ⇒ Boolean
Releases the lock only if the current token is the value of the lease.
- #release_script ⇒ Fixnum
Methods included from Utils::Scriptable
Methods included from Utils::Coercion
Methods inherited from Types::Base
Methods included from Utils::Inspectable
Constructor Details
#initialize(expiry: DEFAULT_EXPIRY, timeout: DEFAULT_TIMEOUT, **options) ⇒ Lock
Returns a new instance of Lock.
26 27 28 29 30 31 32 33 34 35 36 37 |
# File 'lib/redstruct/hls/lock.rb', line 26 def initialize(expiry: DEFAULT_EXPIRY, timeout: DEFAULT_TIMEOUT, **) super(**) @token = nil @expiry = expiry @timeout = timeout.to_i create do |factory| @lease = factory.string('lease') @tokens = factory.list('tokens') end end |
Instance Attribute Details
#expiry ⇒ Fixnum (readonly)
expiry of the underlying redis structures in milliseconds
13 14 15 |
# File 'lib/redstruct/hls/lock.rb', line 13 def expiry @expiry end |
#timeout ⇒ Fixnum? (readonly)
the timeout to wait when attempting to acquire the lock, in seconds
13 14 15 |
# File 'lib/redstruct/hls/lock.rb', line 13 def timeout @timeout end |
#token ⇒ ::String? (readonly)
the current token or nil
13 14 15 |
# File 'lib/redstruct/hls/lock.rb', line 13 def token @token end |
Class Method Details
.from_h(hash, factory) ⇒ Lock
Builds a lock from a hash.
157 158 159 160 |
# File 'lib/redstruct/hls/lock.rb', line 157 def from_h(hash, factory) hash[:factory] = factory return new(**hash) end |
Instance Method Details
#acquire ⇒ Boolean
Attempts to acquire the lock. First attempts to grab the lease (a redis string). If the current token is already the lease token, the lock is considered acquired. If there is no current lease, then sets it to the current token. If there is a current lease that is not the current token, then:
1) If this not a blocking lock (see Lock#blocking?), return false
2) If this is a blocking lock, block and wait for the next token to be pushed on the tokens list
3) If a token was pushed, set it as our token and refresh the expiry
61 62 63 64 65 66 67 68 69 70 71 72 |
# File 'lib/redstruct/hls/lock.rb', line 61 def acquire acquired = false token = non_blocking_acquire(@token) token = blocking_acquire if token.nil? && blocking? unless token.nil? @token = token acquired = true end return acquired end |
#acquire_script ⇒ ::String
109 110 111 112 113 114 115 116 117 118 119 120 |
# File 'lib/redstruct/hls/lock.rb', line 109 defscript :acquire_script, <<~LUA local token = ARGV[1] local expiry = tonumber(ARGV[2]) redis.call('set', KEYS[1], token, 'NX') if redis.call('get', KEYS[1]) == token then redis.call('pexpire', KEYS[1], expiry) return token end return false LUA |
#blocking? ⇒ Boolean
Whether or not the lock will block when attempting to acquire it
49 50 51 |
# File 'lib/redstruct/hls/lock.rb', line 49 def blocking? return @timeout.positive? end |
#inspectable_attributes ⇒ Object
Helper method for easy inspection
170 171 172 |
# File 'lib/redstruct/hls/lock.rb', line 170 def inspectable_attributes super.merge(expiry: @expiry, blocking: blocking?) end |
#locked { ... } ⇒ Object
Executes the given block if the lock can be acquired
41 42 43 44 45 |
# File 'lib/redstruct/hls/lock.rb', line 41 def locked yield if acquire ensure release end |
#release ⇒ Boolean
Releases the lock only if the current token is the value of the lease. If the lock is a blocking lock (see Lock#blocking?), push the next token on the tokens list.
77 78 79 80 81 82 |
# File 'lib/redstruct/hls/lock.rb', line 77 def release return false if @token.nil? next_token = SecureRandom.uuid return coerce_bool(release_script(keys: [@lease.key, @tokens.key], argv: [@token, next_token, @expiry])) end |
#release_script ⇒ Fixnum
128 129 130 131 132 133 134 135 136 137 138 139 140 141 |
# File 'lib/redstruct/hls/lock.rb', line 128 defscript :release_script, <<~LUA local currentToken = ARGV[1] local nextToken = ARGV[2] local expiry = tonumber(ARGV[3]) if redis.call('get', KEYS[1]) == currentToken then redis.call('set', KEYS[1], nextToken, 'PX', expiry) redis.call('lpush', KEYS[2], nextToken) redis.call('pexpire', KEYS[2], expiry) return true end return false LUA |
#to_h ⇒ Hash<Symbol, Object>
Returns a hash representation of the object
147 148 149 |
# File 'lib/redstruct/hls/lock.rb', line 147 def to_h return super.merge(token: @token, expiry: @expiry, timeout: @timeout) end |