What this simulates
A lease-based distributed lock — acquire with a TTL, the lock auto-expires if you don't renew it — has a real hole: nothing stops a client's process from pausing (garbage collection, VM live migration, the OS simply not scheduling it) for longer than the TTL after acquiring the lock but before finishing its work. This tool simulates exactly that race: Client A acquires the lock, pauses for however long you set, and Client B acquires the lock the moment Client A's lease expires — then you can see whether a fencing token catches Client A's stale write when it finally resumes.
Why a longer TTL isn't the fix
It's tempting to set the TTL to something comfortably longer than any pause you'd expect, and the Safe preset shows why that works — right up until a pause genuinely unbounded in length happens anyway. A GC pause, a hypervisor migration, or a scheduler simply not running a process are all real, observed causes of multi-second (or longer) pauses that no timeout you pick in advance can rule out. The TTL only changes howlikely the race is, not whether it's possible — which is exactly Martin Kleppmann's 2016 critique of Redis' Redlock algorithm: the only way to be safe against this class of failure is for the protected resource itself to reject stale writes, not to make the pause less likely.
How fencing tokens close the hole
A fencing token is a monotonically increasing number handed out every time the lock is granted. The protected resource — not the lock service — tracks the highest token it has seen and rejects any write carrying a lower one. In the Race, with fencing preset, Client A's stale write still arrives after Client B has already written with a higher token, but the resource recognizes it's stale and refuses it. ZooKeeper's sequential ephemeral znodes and etcd's MVCC revision number both give you this property natively, which is why they're the usual recommendation over a lock primitive that doesn't hand out a comparable, ordered token at all.
FAQ
Is anything sent anywhere?
No — this is a deterministic, client-side simulation. Nothing leaves your browser.
Does this model a real Redis, ZooKeeper, or etcd deployment?
No — it models the abstract mechanism common to all lease-based locks: a TTL, a pause, and whether the resource checks a token. Real systems add their own details (Redlock's multi-instance quorum, ZooKeeper's watches, etcd's lease API) on top of this same underlying hazard. See Distributed Locks Explained for how each system actually implements fencing.
Why does Client B acquire the lock at exactly the TTL, instead of some time after?
That's the worst case, and the one worth designing for — a competing client polling aggressively (or itself racing to acquire the instant it sees the lease has expired) can realistically acquire it immediately. Modeling any gap would understate the risk.
What's a fencing token in practice — do I have to build one myself?
If your lock service hands out something monotonically increasing and comparable already (a Snowflake ID, a ULID, or a plain counter), that's a usable fencing token — see Distributed ID Explorer for generating and comparing exactly that shape of value. What matters is that the resource being protected actually checks it against the highest one it's seen, which the lock service alone can't enforce for you.