Skip to content

imharjot/lockd

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

16 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

lockd

A small reservation engine for unique inventory, written in Go.

The use case is the "add to cart locks the item, and the lock auto-releases if you don't check out" pattern. You're selling one-of-a-kind things, a cut diamond, a concert seat, a specific serial number, and two shoppers must never hold the same item at once. lockd is the lock primitive for that, built from scratch instead of leaning on Redis so the concurrency model is fully in your own process and your own control.

The whole engine is one key, one holder, qty=1. It is deliberately not a general key-value store. The API is four operations:

  • acquire a key for a holder, with a TTL
  • renew to extend the TTL while you still hold it
  • release when you're done
  • fence to read the current fencing token

If a holder goes quiet and the TTL lapses, the lease expires on its own and the next waiter in line gets the key.

Quick start

In-process:

eng := lockd.New(lockd.Config{Shards: 16})
defer eng.Close()

ctx := context.Background()
g, err := eng.Acquire(ctx, "stone-42", "cart-7", 30*time.Second)
if err != nil {
    // key is held by someone else, or ctx was cancelled while queued
}

// hold it, renew while the shopper is active
g, _ = eng.Renew("stone-42", "cart-7", g.Token, 30*time.Second)

// release on checkout (or just let the TTL expire)
eng.Release("stone-42", "cart-7", g.Token)

Acquire blocks while the key is held and serves waiters in FIFO order. Pass a context with a deadline or cancel to bound the wait; a cancelled acquire gives up its place in the queue immediately.

HTTP server

go run ./cmd/lockd-server -addr :8080 -wal lockd.wal -metrics
curl -s -XPOST localhost:8080/v1/acquire \
  -d '{"key":"stone-42","holder":"cart-7","ttl_ms":30000,"wait_ms":2000}'
# {"key":"stone-42","holder":"cart-7","token":1,"expires_at_unix_ms":...}

curl -s 'localhost:8080/v1/fence?key=stone-42'
# {"key":"stone-42","held":true,"token":1}

curl -s -XPOST localhost:8080/v1/release \
  -d '{"key":"stone-42","holder":"cart-7","token":1}'

wait_ms of 0 (or omitted) makes acquire non-blocking: a held key returns 409 right away instead of queueing. With -wal set the server journals every grant and replays the log on the next start.

Fencing tokens

Every grant carries a token that strictly increases per key. The token is the defense against a paused or partitioned client. If process A acquires the key, stalls long enough for its lease to expire, and process B then acquires the same key, A is holding a now-stale token. When A wakes up and tries to act on the resource it locked, it presents the old token; whatever sits downstream (the database write, the order placement) compares tokens and rejects the smaller one. That is how a single-node lock stays safe across client pauses.

Layout

lease/              lease model and per-key state machine (no I/O)
clock/              injectable clock, real and fake
.                   engine: sharded actors, timing wheel, FIFO queue, fencing
wal/                append-only write-ahead log
transport/httpapi/  HTTP/JSON server
metrics/            optional Prometheus collector
cmd/lockd-server/   runnable binary

Tests

go test ./... -race
go test . -bench . -benchmem

The suite includes table-driven unit tests, a property test over randomized operation interleavings, a crash-and-recover test against a file-backed WAL, and a linearizability check (via porcupine) that asserts the lease register admits a valid sequential ordering under concurrency.

See DESIGN.md for why it's built the way it is, and the limitations.

About

A single-node reservation engine for unique inventory in Go — TTL leases, fencing tokens, fair per-key queueing, WAL recovery, linearizability-checked.

Topics

Resources

License

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors

Languages