Self-Expiring Leases

Step 2: Self-Expiring Leases

If a client crashes while holding a lock, that lock is held forever and the whole system deadlocks. Real distributed locks are leases: they come with a TTL and expire on their own. A crashed holder's lock quietly frees itself.

With an injected clock, a lock is free when nobody holds it or the current holder's lease has expired.

  • acquire(name, client_id, ttl): acquire if free or the existing lease has expired; the new lease lasts ttl seconds.
  • is_locked(name): is the lock currently held by a live (non-expired) lease?
Hints
Starter code
import time


class LockManager:
    def __init__(self, clock=time.time):
        self.clock = clock
        self._locks = {}   # name -> (client_id, expiry)

    def acquire(self, name, client_id, ttl):
        """Acquire if free or the current lease has expired."""
        now = self.clock()
        cur = self._locks.get(name)
        # TODO: cur holds the current (client_id, expiry) pair, or None when free.
        #   Take the lock when there is no holder, or when the stored expiry has
        #   already reached now, saving client_id with an expiry ttl seconds past
        #   now and answering True. A still live lease answers False untouched.
        pass

    def release(self, name, client_id):
        cur = self._locks.get(name)
        if cur and cur[0] == client_id:
            del self._locks[name]
            return True
        return False

    def is_locked(self, name):
        """True only if a live (non-expired) lease holds the lock."""
        # TODO: fetch the self._locks entry for name and answer True only when
        #   one exists and its stored expiry is still strictly ahead of the
        #   current clock reading. Missing or expired both answer False.
        pass
Write it on a laptop

The editor and the test runner need a wide screen. Open this page on a desktop browser and your progress will be waiting.