A lease can expire while its holder is still running. That single property separates a distributed lease from an ordinary in-process mutex. The coordinator may grant ownership to another client after a deadline, yet the former holder can resume after a long pause and continue issuing operations based on authority it no longer has.
The coordinator has done its job: it stopped treating the old client as the current holder. The shared resource has a different problem. Unless operations carry evidence of ownership order, the resource may have no basis for distinguishing a current holder from a stale one.
A fencing token closes that gap. Each successful lease acquisition receives a token greater than every token previously issued for the same protected domain. The resource remembers the greatest token it has accepted and rejects operations carrying smaller tokens. Expiration then becomes more than a coordinator decision; stale authority becomes detectable at the point where state is changed.
Expiration does not revoke a running process
Consider two clients, A and B, competing for a lease with a finite duration. Client A receives token 41 and begins work. It then stops making progress long enough for the lease to expire. The cause could be a scheduler pause, a stalled runtime, a network partition, or any other delay longer than the lease interval.
The coordinator can now grant a new lease to B, assigning token 42.
time --->
A: acquire(41) ---- pause -------------------- resume ---- write(41)
|
| lease expires
v
B: acquire(42) ---- write(42)Nothing in the expiration event forces A to terminate. A timeout is state maintained by the coordinator, not a remote instruction that can synchronously erase A’s execution. When A resumes, local memory can still contain the work item, resource identifier, and code path that existed before the pause.
If the target accepts both writes without checking lease order, the stale operation from A can arrive after the valid operation from B. The lease prevented simultaneous recognized ownership at the coordinator, but it did not by itself impose an acceptance rule on the target resource.
This distinction matters for lease designs built on any coordinator with time-bounded ownership. The exact acquisition primitive can vary. The stale-holder problem follows from the combination of expiration and an independently accessible resource.
A token turns ownership into an ordered capability
A fencing token is commonly represented as an integer, although the representation is less important than its ordering property. For one protected resource or namespace, a later successful acquisition must receive a token that compares greater than an earlier acquisition.
The resource can apply a compact rule:
accept(operation, token):
if token < greatest_accepted_token:
reject operation
else:
apply operation
greatest_accepted_token = tokenSuppose B reaches the resource first with token 42. The resource records 42. When the resumed client A later sends token 41, the operation is rejected because 41 < 42.
The token does not prove that a lease is still inside its wall-clock duration. It establishes an ordering between lease generations. That is enough to reject a holder once a newer generation has already reached the protected resource.
This also exposes a boundary condition. If A sends an operation with token 41 before any operation carrying 42 reaches the resource, a token comparison alone does not establish that 41 has expired. Fencing is an ordering mechanism, not a synchronized clock check. Its protection appears when newer authority advances the resource’s accepted generation.
The resource must participate
Generating tokens at the lease coordinator is insufficient if the protected resource ignores them. The acceptance check has to occur at a boundary that can prevent the stale side effect.
For a database-backed resource, the token can be stored with the protected row and included in the write predicate:
UPDATE document_state
SET payload = :payload,
fence_token = :token
WHERE document_id = :id
AND fence_token <= :token;The application must also inspect the affected-row count. A zero-row result can indicate that a greater token has already been recorded, assuming the row exists and the predicate is the only condition that can block the update.
A schema constraint alone cannot express the full rule across successive updates unless the database operation compares the incoming token with persisted state. The useful property comes from an atomic check-and-write at the resource.
Other resources need an equivalent mechanism. A storage service might expose conditional writes against version metadata. A custom service can keep the greatest accepted token in durable state and validate every mutating request. If an external system offers no conditional update, version check, or other way to reject stale generations, attaching a token to a request does not create fencing by itself.
Monotonicity is scoped, not global
Tokens need to increase across lease generations that can contend for the same protected state. They do not necessarily need to form one global sequence for an entire system.
A coordinator protecting independent objects can maintain a sequence per object:
object X: 17, 18, 19, ...
object Y: 4, 5, 6, ...Comparing token 19 for X with token 6 for Y has no useful meaning. Their order matters only inside the domain where stale operations can conflict.
This scope affects storage design. A single global counter provides a simple total order, but it also couples unrelated acquisitions to one sequence. Per-resource counters preserve the property needed for fencing while avoiding a semantic claim that unrelated leases share meaningful order.
The required guarantee is stricter than uniqueness. Random identifiers can distinguish lease instances but do not state which instance is newer. A fencing check needs an order that the resource can compare. A UUID can serve as an acquisition identity, but an ordinary UUID is not automatically a fencing token.
Renewal and reacquisition are different events
Lease renewal deserves explicit semantics. If renewal extends the current lease generation, keeping the same fencing token is coherent: the holder’s authority has not been superseded by another successful acquisition.
Reacquisition after loss is different. Once a lease has expired and another acquisition can succeed, a client that obtains ownership again needs a new generation and therefore a greater token.
Treating a recovered client as if its old token remained current breaks the ordering model. The coordinator’s state transition should make the distinction visible: renewal preserves a generation; a fresh acquisition creates one.
There is also a race near the renewal deadline. A client may send a renewal request before its local deadline yet receive the response after the coordinator has already considered the lease expired. Correct behavior depends on the coordinator’s authoritative state, not the client’s estimate of elapsed time. A failed or ambiguous renewal cannot safely be interpreted as continued ownership.
Tokens do not make side effects transactional
Fencing protects operations only at participating resources. It does not automatically combine several resources into one atomic action.
Suppose a worker with token 52 updates a database that enforces fencing and then calls an external API that has no token-aware conditional operation. A stale worker with token 51 can be blocked at the database, but the external API remains outside that protection boundary.
The same limitation applies to irreversible side effects such as sending a message through a system that cannot reject stale generations. A token can be included as metadata when the receiver supports deduplication or ordering checks. Without receiver participation, the sender cannot unilaterally impose fencing semantics.
This is not a defect in the token model. It identifies the exact location of the guarantee. Fencing converts lease generation into state that a resource can validate. It cannot provide validation where no such check exists.
Idempotency solves a different ambiguity
Fencing and idempotency are sometimes adjacent because both can reject unwanted operations, but they answer different questions.
An idempotency key identifies repeated execution of the same logical request. If a network timeout causes a client to retry an operation, the key can allow the receiver to return the prior result or suppress a duplicate side effect.
A fencing token identifies the authority generation under which an operation is attempted. Two distinct operations from an expired holder can carry different idempotency keys and still both be stale. Conversely, two retries from the current holder can share one idempotency key while carrying the same valid fencing token.
Systems that face both retry ambiguity and expiring ownership may need both pieces of metadata. Combining them into one field obscures their separate invariants.
The useful invariant sits at the write boundary
A lease coordinator can state which client it currently recognizes. That fact is not enough to control a process that already passed the acquisition point and later became stale. The protected resource needs a fact it can evaluate locally when a mutation arrives.
Fencing tokens provide that fact as an ordered generation. Once the resource has accepted generation n, any operation from a generation below n is stale by construction and can be rejected without consulting the old holder.
The design is strongest when its scope is stated precisely: the coordinator issues monotonically ordered generations, each mutation carries its generation, and the resource atomically refuses generations older than the greatest one it has accepted. Under those conditions, lease expiration no longer relies on a stale process noticing that its authority ended. The resource itself enforces the boundary.