FizzBee
GitHub Discord Toggle Dark/Light/Auto mode Toggle Dark/Light/Auto mode Toggle Dark/Light/Auto mode Back to homepage

Distributed Lock Pattern

Multiple clients competing for lock with lease expiration

Demonstrates: distributed locking, fencing tokens, lease management

Large state space (131,367 nodes); run locally with fizz.

# Example 0072: Distributed Lock Pattern
# Multiple clients competing for lock with lease expiration
# Demonstrates: distributed locking, fencing tokens, lease management

---
deadlock_detection: false
---

MAX_FENCE_TOKEN = 3

role LockService:
    action Init:
        self.owner = None
        self.fence_token = 0
        self.lease_active = False

    atomic func AcquireLock(client_id):
        # Grant lock if available
        if self.owner != None:
            return None  # Lock already held

        # Bound fence tokens to limit state space
        if self.fence_token >= MAX_FENCE_TOKEN:
            return None

        self.owner = client_id
        self.fence_token = self.fence_token + 1
        self.lease_active = True
        return self.fence_token

    atomic func ReleaseLock(client_id, token):
        # Release only if owner and token matches
        if self.owner == client_id and token == self.fence_token:
            self.owner = None
            self.lease_active = False
            return True
        return False

    atomic action LeaseExpire:
        # Lease can expire if active (unfair to avoid state explosion)
        if not self.lease_active:
            return

        # Expire the lease (owner loses lock) atomically
        self.owner = None
        self.lease_active = False

role Client:
    action Init:
        self.token = None
        self.operations = 0

    atomic action AcquireLock:
        require self.token == None
        require self.operations < MAX_OPERATIONS

        # Try to acquire lock
        token = lock_service.AcquireLock(self.__id__)
        if token != None:
            self.token = token

    atomic action UseResource:
        require self.token != None

        # Use the resource while holding lock
        self.operations = self.operations + 1

    atomic action ReleaseLock:
        require self.token != None

        # Release the lock
        success = lock_service.ReleaseLock(self.__id__, self.token)
        if success:
            self.token = None

NUM_CLIENTS = 2
MAX_OPERATIONS = 1

action Init:
    lock_service = LockService()
    clients = []
    for i in range(NUM_CLIENTS):
        clients.append(Client())

# Safety: at most one client holds VALID lock (matching current fence token)
always assertion AtMostOneValidOwner:
    valid_holders = [c for c in clients if c.token == lock_service.fence_token]
    return len(valid_holders) <= 1

# Safety: service owner must have valid token (reverse may not hold due to lease expiry)
always assertion ServiceOwnerHasToken:
    if lock_service.owner != None:
        # If service has owner, that client should have the token
        for client in clients:
            if client.__id__ == lock_service.owner:
                return client.token == lock_service.fence_token
    return True

# Safety: fence tokens are monotonic
always assertion MonotonicTokens:
    return lock_service.fence_token >= 0

Expected result (from fizz):

StateSpaceOptions: options:{max_actions:100 max_concurrent_actions:2} deadlock_detection:false
Nodes: 131367, queued: 0
Skipping dotfile generation. Too many Nodes: 131367
Valid Nodes: 131367 Unique states: 131367
IsLive: true
PASSED: Model checker completed successfully

Source on GitHub

Language features used

Roles, Assertions, Flow Modifiers, Functions