Quick start
-
Start the server.
Terminal window docker run -p 7070:7070 ghcr.io/monolock-dev/monolockThat is a complete production-shaped server: no config file, no database, no dependencies. Every knob is a flag with a matching environment variable — see Configuration.
-
Take a lock from your code.
Terminal window go get github.com/monolock-dev/monolock-goimport ("context""time"monolock "github.com/monolock-dev/monolock-go")func main() {c := monolock.New(monolock.Config{Address: "127.0.0.1:7070"})// Blocks until this process owns "nightly-import", runs the// function, and releases the lock when it returns.err := c.Do(context.Background(), "nightly-import", 2*time.Second,func(ctx context.Context, token uint64) error {// Only one process across your fleet runs this at a time.// ctx is cancelled the moment ownership stops being// confirmed; token fences out stale holders (see below).return doTheWork(ctx, token)})if err != nil {// ...}} -
Watch the handover.
Run the program twice at the same time. The second process queues up in FIFO order and prints nothing — until you stop the first one, at which point the second is promoted immediately, without waiting out any timeout. Kill the first one with
kill -9instead and the handover takes at most the lease (2 seconds above): that is the failure-detection window you chose in the call.
What just happened
Section titled “What just happened”sequenceDiagram
participant A as worker A
participant S as monolock
participant B as worker B
A->>S: ACQUIRE "nightly-import" lease=2s
S-->>A: ACQUIRED token=41
B->>S: ACQUIRE "nightly-import" lease=2s
S-->>B: WAITING (queued, FIFO)
par holder works
loop every lease/4
A->>S: HEARTBEAT
S-->>A: ACQUIRED token=41
end
and waiter keeps its place
loop every lease/4
B->>S: HEARTBEAT
S-->>B: WAITING
end
end
A--xS: connection closed
S-->>B: ACQUIRED token=42
Each connection claims one named lock. The owner holds it for as long as its heartbeats keep arriving; the waiter heartbeats too, keeping its place in the queue. When the owner’s connection closes — a graceful exit — the next waiter is promoted at once. When the owner dies silently, the server waits out the lease that client chose and then moves on.
Note the tokens: 41, then 42. Every grant carries a fencing token strictly
larger than every earlier one. Hand it to the resource your lock guards and a
stale holder — one that lost the lock but doesn’t know it yet — fences itself
out. That is the difference between hoping mutual exclusion holds and having
the resource enforce it: read Fencing tokens.
Next steps
Section titled “Next steps”- How it works — leases, heartbeats and handover in depth.
- Deployment — Docker, systemd and Kubernetes manifests.
- TLS & mTLS — encrypt and authenticate before leaving localhost.

