Class: Redstruct::Lock
- Inherits:
-
Factory::Object
- Object
- Factory::Object
- Redstruct::Lock
- Includes:
- Utils::Coercion, Utils::Scriptable
- Defined in:
- lib/redstruct/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 seconds; can be between 0 and 1 as a float for milliseconds
1
- DEFAULT_TIMEOUT =
The default timeout when blocking, in seconds
nil
Instance Attribute Summary collapse
-
#expiry ⇒ Float, Integer
readonly
The expiry of the underlying redis structure in seconds.
-
#resource ⇒ String
readonly
The resource name (or ID of the lock).
-
#timeout ⇒ Integer
readonly
If greater than 0, will block until timeout is reached or the lock is acquired.
-
#token ⇒ String
readonly
The current token.
Attributes inherited from Factory::Object
Instance Method Summary collapse
-
#acquire ⇒ Boolean
Attempts to acquire the lock.
-
#blocking? ⇒ Boolean
Whether or not the lock will block when attempting to acquire it.
-
#delete ⇒ Boolean
Deletes all traces of this lock.
-
#initialize(resource, expiry: DEFAULT_EXPIRY, timeout: DEFAULT_TIMEOUT, **options) ⇒ Lock
constructor
A new instance of Lock.
-
#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.
Methods included from Utils::Coercion
Methods included from Utils::Scriptable
Methods inherited from Factory::Object
Methods included from Utils::Inspectable
Constructor Details
#initialize(resource, expiry: DEFAULT_EXPIRY, timeout: DEFAULT_TIMEOUT, **options) ⇒ Lock
Returns a new instance of Lock.
37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 |
# File 'lib/redstruct/lock.rb', line 37 def initialize(resource, expiry: DEFAULT_EXPIRY, timeout: DEFAULT_TIMEOUT, **) super(**) @resource = resource @token = nil @expiry = expiry @acquired = Redstruct::Utils::AtomicCounter.new @timeout = case timeout when nil then nil when Float::INFINITY then 0 else timeout.to_i end factory = @factory.factory(@resource) @lease = factory.string('lease') @tokens = factory.list('tokens') end |
Instance Attribute Details
#expiry ⇒ Float, Integer (readonly)
Returns the expiry of the underlying redis structure in seconds.
29 30 31 |
# File 'lib/redstruct/lock.rb', line 29 def expiry @expiry end |
#resource ⇒ String (readonly)
Returns the resource name (or ID of the lock).
23 24 25 |
# File 'lib/redstruct/lock.rb', line 23 def resource @resource end |
#timeout ⇒ Integer (readonly)
Returns if greater than 0, will block until timeout is reached or the lock is acquired.
32 33 34 |
# File 'lib/redstruct/lock.rb', line 32 def timeout @timeout end |
#token ⇒ String (readonly)
Returns the current token.
26 27 28 |
# File 'lib/redstruct/lock.rb', line 26 def token @token 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
93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 |
# File 'lib/redstruct/lock.rb', line 93 def acquire acquired = false token = non_blocking_acquire token = blocking_acquire if token.nil? && blocking? unless token.nil? @lease.expire(@expiry) @token = token @acquired.increment acquired = true end return acquired end |
#blocking? ⇒ Boolean
Whether or not the lock will block when attempting to acquire it
81 82 83 |
# File 'lib/redstruct/lock.rb', line 81 def blocking? return !@timeout.nil? end |
#delete ⇒ Boolean
Deletes all traces of this lock
59 60 61 |
# File 'lib/redstruct/lock.rb', line 59 def delete return coerce_bool(delete_script(keys: [@lease.key, @tokens.key])) end |
#locked { ... } ⇒ Object
Executes the given block if the lock can be acquired
65 66 67 68 69 70 71 72 73 74 75 76 77 |
# File 'lib/redstruct/lock.rb', line 65 def locked Thread.handle_interrupt(Exception => :never) do begin if acquire Thread.handle_interrupt(Exception => :immediate) do yield end end ensure release end end 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.
113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 |
# File 'lib/redstruct/lock.rb', line 113 def release return false if @token.nil? released = true if @acquired.decrement.zero? keys = [@lease.key, @tokens.key] argv = [@token, generate_token, (@expiry.to_f * 1000).floor] released = coerce_bool(release_script(keys: keys, argv: argv)) @token = nil end return released end |