Skip to content

l0.DependencyControl.Lock

Lock = require "l0.DependencyControl.Lock"
local Lock = require("l0.DependencyControl.Lock")

Lock

Cooperative, named lock with per-resource granularity. Each distinct (scope, namespace, resource) maps to its own OS lock, so unrelated resources lock independently. A lock is mutually exclusive across every Lock instance -- and, for Global scope, across every process -- that targets the same tuple.

Scope (see Lock.Scope) selects the primitive and reach of exclusion: Process: a named semaphore whose name embeds the pid; only Lua states within this process contend. Global: an OS advisory file lock (FileLock) shared by every process in the session -- use for resources shared between Aegisub instances (e.g. a config file). The kernel releases it if the holder crashes, so it never stays stuck; it cannot, however, be taken from a holder that is alive but hung.

While held, the holder's identity and lease are recorded in a per-resource side file for diagnostics. Long operations should call renew! periodically to extend the recorded lease so waiters don't mistake a busy holder for a crashed one.

Constructor

lock = Lock args
local lock = Lock(args)

Creates a lock for the given resource.

param type description
args LockArgs

Instance methods

getActiveHolder

lock\getActiveHolder!
lock:getActiveHolder()

Returns the holder currently believed to hold this lock, or nil if it appears free. Reads the side file lock-free, so the result is advisory: a holder whose lease has lapsed (likely crashed) is reported as free, and a brand-new holder may not be visible yet. Reports this instance too when it holds the lock. Requires holder recording.

Returns:

  • record table? — The holder record (holderName, pid, namespace, resource, ...).

lock

lock\lock timeout, lockWaitInterval
lock:lock(timeout, lockWaitInterval)

Attempts to acquire the lock, waiting up to timeout milliseconds.

param type description
timeout? number Maximum time to wait in milliseconds (default math.huge).
lockWaitInterval? number Poll interval in milliseconds while waiting (default 250).

Returns:

  • state LockState — Held on success, Unavailable on timeout.
  • timePassed number — Milliseconds spent waiting.

tryLock

lock\tryLock!
lock:tryLock()

Attempts to acquire the lock without waiting.

Returns:

  • state LockState — Held if the lock was acquired, Unavailable if it's held elsewhere.
  • timePassed number — Milliseconds spent waiting (always 0).

release

lock\release!
lock:release()

Releases the lock held by this instance.

Returns:

  • released boolean? — True on success, nil if the lock wasn't held by this instance.
  • statusOrErr LockState|string — Available on success, or an error message when the lock wasn't held.

renew

lock\renew expiryThreshold
lock:renew(expiryThreshold)

Refreshes the held lock's lease when it is close to expiring, re-stamping its recorded expiry to @expiresAfter from now. This only affects the metadata on this Lock instance and the side file, so waiters don't mistake a busy holder for a crashed one. The underlying OS lock remains held until explicitly released. To avoid unnecessary writes, the side file is only updated when the remaining lease is approaching expiry.

param type description
expiryThreshold? number Renew only if the remaining lease is <= this many milliseconds; -1 forces an unconditional refresh. Defaults to the larger of half the lease or the safety margin, capped at the full lease.

Returns:

  • renewed boolean? — True if refreshed, false if still fresh, nil if the lock isn't held.
  • err string? — Set (with nil renewed) only when the lock isn't held.

Class methods

guard

Lock\guard args, body
Lock:guard(args, body)

Acquires a lock for the given args and runs the provided body function. Releases the lock when the body completes or throws. The held Lock is passed to the body so it can call renew on it if needed.

param type description
args GuardArgs Lock constructor args plus optional timeout/lockWaitInterval for the acquire.
body fun(lock: Lock): ... Called with the held Lock; its return values are passed through.

Returns:

  • ... any — The body's return values on success, or nil + err if the lock couldn't be acquired.

Fields

field type description
state LockState Held when this instance holds the lock, otherwise Unknown (a foreign holder's state can't be told without acquiring). Read-only.

Enums

LockState

member value description
Unknown -1 the state can't be determined without trying to acquire (e.g. a foreign holder)
Unavailable 0 the lock is held elsewhere and couldn't be acquired
Available 1 the lock is free to acquire
Held 2 this instance holds the lock

LockScope

member value description
Process "process" only Lua states within this process contend
Global "global" every process in the session contends via an advisory file lock

Types

LockArgs

field type description
namespace? string Logical namespace component of the locked resource (default "").
resource? string Resource component within the namespace (default "").
holderName? string Human-readable holder name recorded for diagnostics (default "unknown").
logger? Logger
expiresAfter? number Lease duration in seconds before a holder is considered stale (default 300).
scope? LockScope Scope selecting the primitive and reach of exclusion (default "process").
recordHolder? boolean Write a holder side file while held (default true).
overrideExpiry? boolean Judge foreign holders against this instance's expiresAfter rather than their recorded lease (default false).

GuardArgs

field type description
timeout? number Acquire timeout in milliseconds (default math.huge).
lockWaitInterval? number Poll interval in milliseconds while waiting (default 250).