#locking #native #cross #geoffreygarrett

cross-locks

FIFO-fair global locks that work identically on native, browser-WASM and head-less WASM

3 releases

new 0.1.2 May 23, 2025
0.1.1 May 23, 2025
0.1.0 May 21, 2025

#300 in Web programming

Download history 204/week @ 2025-05-17

211 downloads per month

MIT/Apache

74KB
1.5K SLoC

cross-locks

crates.io docs.rs CI

cross-locks provides a single-file, zero-dependency¹ implementation of named, FIFO, re-entrant global locks that Just Work everywhere – native back-ends, WASM in the browser, and single-threaded test binaries – with identical APIs and semantics.

¹ Apart from the optional runtime adapters (tokio, wasm-bindgen) and the tiny derive helpers async-trait / thiserror.


✨ Why?

  • Cross-platform parity – ship the same locking guarantees in browsers (via Navigator.locks) or on the server without extra feature flags in your application code.
  • FIFO fairness on native (using a Tokio queue); browser fairness delegated to the spec.
  • Zero-timeout “try-lock” semantics that mirror Supabase / GoTrue behaviour.
  • Task-local re-entrancy – a lock holder can call itself recursively without dead-locking.
  • Arc<T> auto-impl – store the lock directly in shared state (e.g. Axum Extension).
  • Compile-time selection – one concrete backend is compiled in; no runtime branches.

🚀 Quick start

# Cargo.toml
[dependencies]
cross-locks = { version = "0.1", default-features = false, features = ["native"] } # or "browser"
tokio = { version = "1", optional = true, features = ["sync", "rt-multi-thread"] }
use cross_locks::{GlobalLock, DefaultLock};
use std::time::Duration;

#[tokio::main]
async fn main() {
    let lock = DefaultLock::default();

    lock.with_lock("auth.cs", Duration::from_secs(1), || async {
        // … critical section …
    }).await.unwrap();
}

Feature matrix

Target --features Crate size Guarantees
Native (Tokio multi-thread) native tiny FIFO, timeout, re-entry
Browser WASM (2022+) browser tiny Delegates to Navigator.locks
Tests / single-thread CLI (none) tiny No-op passthrough

Safari ≥ 16.4, Chrome ≥ 69, Firefox ≥ 94 already ship the Web Locks API.

🧩 Using your own runtime

GlobalLock is an async-trait with a blanket impl for Arc<T>, so you can plug in bespoke back-ends (e.g. a Postgres advisory lock):

struct PgLock {
    pool: sqlx::PgPool
}

#[async_trait::async_trait]
impl GlobalLock for PgLock {
    async fn with_lock<R, F, Fut>(
        &self, name: &str, timeout: Duration, op: F
    ) -> Result<R, cross_locks::LockError>
    where
        F: FnOnce() -> Fut + Send + 'static,
        Fut: std::future::Future<Output=R> + Send + 'static,
        R: Send + 'static,
    {
        // acquire advisory lock, run op, release …
    }
}

🛠️ Tooling & tests

# exhaustive suite on native
cargo test --features native

# smoke-test in headless Firefox
wasm-pack test --firefox --headless --features browser

The repository ships a tiny dual_test! macro so you can write once, run everywhere:

cross_locks::dual_test! { arc_inner_reentrancy {
    /* body compiled as a Tokio test natively and as a wasm_bindgen_test in browsers */
}}

📜 License

Licensed under either 🅰 Apache-2.0 or 🅱 MIT at your option – see LICENSE-* for details.

❤️ Acknowledgements

Inspired by Supabase’s JS SDK and the GoTrue service; thanks to the Web Locks API editors for a sane browser primitive.


Happy hacking & safe locking! — @geoffreygarrett

Dependencies

~0.2–8.5MB
~77K SLoC