Class: Honker::Lock

Inherits:
Object
  • Object
show all
Defined in:
lib/honker/lock.rb

Overview

Advisory lock. Returned by ‘Database#try_lock`; `nil` means another owner holds it.

Prefer explicit ‘release` — finalizers are best-effort. Holding the `Lock` instance does NOT guarantee you still own the lock; the extension’s TTL can expire and a new owner take it. Use ‘heartbeat(ttl_s:)` periodically and check its return value.

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(db, name, owner) ⇒ Lock

Returns a new instance of Lock.



14
15
16
17
18
19
20
21
22
23
# File 'lib/honker/lock.rb', line 14

def initialize(db, name, owner)
  @db = db
  @name = name
  @owner = owner
  @released = false
  # Best-effort cleanup if the caller forgets to release. We capture
  # closed-over primitives (not `self`) so the finalizer can run
  # without keeping the Lock itself alive.
  ObjectSpace.define_finalizer(self, self.class._finalizer(db, name, owner))
end

Instance Attribute Details

#nameObject (readonly)

Returns the value of attribute name.



12
13
14
# File 'lib/honker/lock.rb', line 12

def name
  @name
end

#ownerObject (readonly)

Returns the value of attribute owner.



12
13
14
# File 'lib/honker/lock.rb', line 12

def owner
  @owner
end

Class Method Details

._finalizer(db, name, owner) ⇒ Object

Class-level factory so the finalizer doesn’t close over ‘self`.



26
27
28
29
30
31
32
33
34
35
36
37
38
# File 'lib/honker/lock.rb', line 26

def self._finalizer(db, name, owner)
  proc do
    begin
      db.db.get_first_row(
        "SELECT honker_lock_release(?, ?)",
        [name, owner],
      )
    rescue StandardError
      # Finalizers run after the interpreter teardown can begin;
      # swallowing is the only safe option here.
    end
  end
end

Instance Method Details

#heartbeat(ttl_s:) ⇒ Object

Extend the TTL. Returns true if we still hold the lock; false if it was stolen (the TTL elapsed and another owner acquired it). The underlying SQL is the same as ‘try_lock`, but keyed on our existing `(name, owner)` pair so it refreshes rather than blocks.



61
62
63
64
65
66
# File 'lib/honker/lock.rb', line 61

def heartbeat(ttl_s:)
  @db.db.get_first_row(
    "SELECT honker_lock_acquire(?, ?, ?)",
    [@name, @owner, ttl_s],
  )[0] == 1
end

#releaseObject

Release the lock. Idempotent — calling release twice is a no-op on the second call.



42
43
44
45
46
47
48
49
50
# File 'lib/honker/lock.rb', line 42

def release
  return false if @released

  @released = true
  @db.db.get_first_row(
    "SELECT honker_lock_release(?, ?)",
    [@name, @owner],
  )[0] == 1
end

#released?Boolean

True if the caller has already released this handle.

Returns:

  • (Boolean)


53
54
55
# File 'lib/honker/lock.rb', line 53

def released?
  @released
end