ubgo is in early alpha: libraries and APIs may still change.

Skip to content
ubgo Alpha

Fencing tokens stop stale writes

A process that paused while holding a lock can wake up and write old data; a fencing token lets the storage refuse that write.

Based on the lock README, section “3. Fencing tokens — defend against the GC-pause scenario” About lock →

Step 1 of 7

A takes the lock

Process A acquires payment-export and reads its fencing token with holder.Token(). Tokens are monotonic, so each new holder gets a higher one. Call A's token N.

All steps as text
  1. A takes the lock. Process A acquires payment-export and reads its fencing token with holder.Token(). Tokens are monotonic, so each new holder gets a higher one. Call A's token N.
  2. A is paused. A long garbage-collection pause freezes A. It still believes it holds the lock, but its lease keeps running out.
  3. The lease expires. The lock's TTL runs out and the lock is reclaimed automatically. A does not know this yet.
  4. B takes the lock. Process B acquires the same lock and gets a higher token, N+1.
  5. B writes with its token. B sends its write together with token N+1. The storage wrapper records N+1 as the highest token it has seen and accepts the write.
  6. A wakes up and is rejected. A resumes and tries to write its stale data with token N. The wrapper rejects any write whose token is lower than the highest it has seen, so A's write fails and B's data stays.
  7. Without fencing. If the storage did not check tokens, A's late write would overwrite B's fresh one, even though A no longer held the lock.

The code from the README. The storage side (here s3.PutWithFence) is your own small wrapper that remembers the highest token and refuses anything lower:

holder, _ := locks.Acquire(ctx, "payment-export")
defer holder.Release()
fence := holder.Token()  // monotonic uint64

if err := s3.PutWithFence(ctx, "payments/today.csv", data, fence); err != nil {
    return err
}

This is the defence from Martin Kleppmann’s “How to do distributed locking”. The lock alone cannot protect you here, because the paused process does not know its lease has ended; the token check at the storage is what catches it.