Skip to content

Introduction

monolock is a small TCP server for named locks: a distributed mutex without the distributed system. Many processes on many machines must agree that only one of them runs the nightly import, starts the deploy, or compacts the shard. monolock gives them one simple location for this agreement.

Quick startHow it works

A client opens a TCP connection and asks for a lock by name. The client becomes the owner, or it goes into a queue behind the current owner. This is the full model:

  • One connection is one claim on one lock. The connection is the lease. When the connection closes, the server releases the lock. There is no RELEASE command that you can forget. There is no session token that you can lose.
  • Named locks, FIFO (first in, first out) waiters. Lock names are raw UTF-8 strings with a maximum length of 255 bytes. The server promotes the waiters strictly in the sequence of their arrival.
  • Connection-scoped ownership. After a controlled stop of the owner, the subsequent waiter gets the lock immediately. If the owner stops or the network fails, the server moves the lock when the session is silent for a full lease.
  • Client-selected leases, RTT (round-trip time)-aware heartbeats. Each client selects the detection speed for a dead holder. The server has no global policy.
  • Fencing tokens. Each grant contains a number that is strictly larger than the number of each earlier grant. Thus the guarded resource can reject writes from stale holders.

The server is one static Go binary. It has no dependencies outside the standard library: no database, no config file, no sidecar. The necessary operations functions are all included:

monolock is simple by design: a single point of coordination. There is no replication, no quorum, and no consensus. The advantages are a very small operational footprint and behavior that is easy to analyze. The cost is availability. When the server is down or restarts, new acquisitions are not possible until the server is available again. A passive standby makes this window short. But the trade does not change: the lock state is lost, and the clients acquire again.

This trade-off is correct for much real work: cron jobs, deploy mutexes, and leader election for tasks that permit a short coordination gap. The trade-off is not correct when the guarded resource needs guarantees that continue after a coordinator failure. Then you need a consensus-backed lock service (etcd, ZooKeeper, Consul). Do not try to change monolock into one.

Fencing tokens make this gap much smaller. They protect the resource also when a holder becomes stale. But their guarantee has explicit limits. The documentation gives these limits honestly and does not hide them.