A lightweight TCP server for named locks
— a distributed mutex without the distributed system.
ghcr.io/monolock-dev/monolock
One connection,
one lock
A TCP connection is one claim on one named lock. Closing the connection is the release — there is no release call to forget, and the next waiter in the FIFO queue is promoted at once.
Promotion is a push, not a poll: the server sends ACQUIRED on its own initiative, so handover latency is one one-way trip.
Read the mental model →| Owner's exit | Handover latency |
|---|---|
| Graceful (connection closed) | immediate |
| Force-released by an admin | immediate |
| Process killed, socket reset by the OS | immediate |
| Hang, GC pause, network partition | ≤ the owner's lease |
| Slow reader/writer (stuck socket) | ≤ io-timeout per operation |
Taking a lock
import monolock "github.com/monolock-dev/monolock-go" c := monolock.New(monolock.Config{Address: "127.0.0.1:7070"}) err := c.Do(ctx, "nightly-import", 2*time.Second, func(ctx context.Context, token uint64) error { for { select { case <-ctx.Done(): return nil // the lock is gone; Do reports why case job := <-jobs: if err := process(ctx, job, token); err != nil { return err } } } })
This design removes a whole class of client bugs.
Fencing tokens built in
Every grant carries a monotonically growing token. Pass it to the resource the lock guards and stale holders fence themselves out — safety on top of liveness.
Fencing tokens →Client-chosen leases
Each client picks its own failure-detection window per connection. Heartbeats are RTT-aware, and a dead holder is detected within one lease — while a healthy one can hold forever.
Leases & heartbeats →No dependencies, no database
A single static binary built on the Go standard library alone. No config file, no storage, no cluster to babysit — one process is the whole deployment.
Deployment →Including the rows monolock loses
Honest side-by-side comparisons with the locks you already know.
| monolock | Redis / Redlock | Postgres advisory | etcd | |
|---|---|---|---|---|
| Ownership | connection-scoped | TTL key, renewed | session-scoped | lease + keepalive |
| Release | closing the connection | explicit, owner-checked | unlock or disconnect | explicit, lease revoke |
| Fencing token | every grant | none — the Redlock debate | none | revision numbers |
| Waiters | FIFO queue, pushed | retry loops, no order | blocking wait | watch on a prefix |
| Survives losing its node | ✗ no replication | with replicas, contested | with HA Postgres | ✓ quorum |
| What you operate | one static binary, one port | a server — or five for Redlock | a database, and its HA setup | a 3–5 node cluster to keep healthy |
What you don't get
A single point of coordination — no replication, no quorum, no consensus. Know exactly what you get, and what you don't.
