Expand description
Locking capability trait. See plan/ecosystem/02-capabilities.md §Locking.
A LockingPlugin provides distributed mutual exclusion keyed on a
string. The canonical use case is “ensure this scheduled job fires once
across the cluster, not once per instance” — hence the E2 pairing with
the Scheduled retrofit — but the same shape is useful for leader
election, exactly-once webhook delivery, and coordinating expensive
cache rebuilds.
§Design notes
- Contention is not failure.
try_lockreturnsOk(None)when the lock is held by someone else andErr(..)when the backend itself is broken (Redis unreachable, Postgres connection dropped). Callers almost always want to branch on that distinction — retry a moment later on contention, alarm an operator on backend failure — so encoding it in the type beats a singleLockErrorenum with aHeldvariant. This is the load-bearing shape choice and is the reason the trait does not use a typed error enum. - Everything else is
Result<_, String>.renewandreleasecannot meaningfully distinguish “lock expired out from under us” from “backend failure” for a caller: in both cases the correct response is “log and move on, you lost the lock.” Folding them intoErr(String)keeps the trait lean and WASM-ABI friendly (matches the convention established bycrate::scheduled::ScheduledPluginandcrate::lifecycle::LifecyclePlugin). - Opaque
LockHandle. The handle carries alock_id(a random token minted by the plugin when the lock was acquired) plus thekeyit was taken on. Backends use the token to implement CAS-style release — Redlock SETs withNX PXand a random value, then the release script checks the value beforeDEL— so a renew or release from a stale caller cannot stomp on a newer lock holder. The token shape is aStringso WASM guests can marshal it through the JSON ABI unchanged. - Sync trait. Matches the convention in
crate::scheduled::ScheduledPluginandcrate::session::SessionPlugin. Backends that need an async client drive their own runtime inside the call (same approach asbext-session-redisandbext-tracer-otlp). - No vendor leaks. The trait has no
redis_url, noadvisory_lock_key, nottl_ms_override_for_etcd. Configuration lives on the concrete plugin’s constructor; the trait is one shape across memory / Redis / Postgres / etcd.
§Backends
Three reference backends ship alongside this trait in crates/bext-impls/:
bext-locking-memory— single-node fallback,Mutex<HashMap>.bext-locking-redis— Redlock-styleSET NX PX+ Lua CAS release.bext-locking-pg— Postgrespg_try_advisory_lock(hashtext(key)).
§Use by the Scheduled capability
When a crate::scheduled::ScheduledPlugin declares a
LockingHint::RequireGlobal schedule, the host-owned scheduler in
bext-core::scheduler acquires a LockingPlugin lock keyed on the
schedule id before invoking
ScheduledPlugin::run. On
contention (Ok(None)) the scheduler skips this tick — another node
is running it. On backend failure (Err(..)) the scheduler logs and
skips, matching the existing “lost the lock” semantics.
Modules§
- fuel
- Fuel budgets for WASM locking plugin calls. Matches the convention
in
crate::scheduled::fuel.
Structs§
- Lock
Handle - Handle returned by a successful
LockingPlugin::try_lock.
Traits§
- Locking
Plugin - A plugin that provides distributed mutual exclusion.