Skip to main content

CSEP 552: Design doc example

This page is an example of a CSEP 552-style design doc for a distributed protocol. The protocol ensures mutual exclusion—when a node acquires the global lock, it is guaranteed that nobody else holds the lock. (For example, this might be useful if the node needs to perform some actions with the guarantee that no other nodes are performing similar actions at the same time.)

Preface

  • Goals:
    • Clients are assigned ownership of a global lock with the guarantee that no other client currently holds the lock. When they are done with the lock, they release it so that other clients may acquire it.
    • Clients that do not hold the lock are always requesting it.
    • Clients are not guaranteed to eventually acquire the lock if they ask for it - as we don't maintain a queue at the server. Instead, a single client can repeatedly re-acquire the lock even though others are also waiting.
  • Desired fault model:
    • Asynchronous unreliable message delivery with message reordering. Assumes clients and the server obey the protocol, and that neither clients nor the server crash.
  • Challenges:
    • An asynchronous, reordering, and duplicating network means that it can be difficult for nodes to tell whether a message is current, or a copy of an old already handled message.

Protocol

  • Kinds of node:
    • One unique server node that manages the lock state.
    • Any number of client nodes trying to acquire the lock.
  • State at each kind of node:
    • At the server:
      • current holder: client who currently holds the lock or None if nobody holds the lock
        • init: None
      • numGranted: The number of times the lock has been acquired
        • init: 0
    • At the client:
      • sequenceNum
        • sequenceNum: 0
        • 0 if the lock was never held by this client. If the lock is currently held by this client, the number of times the the lock has been granted to anyone. Otherwise the client's sequenceNum at the last point in time when the lock was held by this client.
      • mode: {None, Held, Releasing, Released}
        • init: None
        • None means this client does not hold the lock.
        • Held means that this client currently holds the lock.
        • Releasing means that this client had the lock and has released it, but the the server may or may not know.
        • Released means that this client no longer holds the sequenceNum version of the lock (and the server also knows); the server in the meantime might have re-granted the lock.
        • The client does not know whether other clients hold the lock or not, except in state Held - in that case, it knows it is the only node with the lock.
  • Messages:

    A figure sketching the relationship of the messages to one another:

    Timeline with Client and Server as vertical lines, time flowing down. Client sends Request to Server; Server grants the lock and sends Grant; Client does stuff with the lock, then sends Release; Server frees the lock and sends Release Ack.

    • Request
      • Source: Client
      • Destination: Server
      • Contents: client ID
      • Sent on startup and whenever the client takes a timeout and the lock is not held. In other words, the client always requests the lock.
      • What happens at the destination when it is received?
        • The server checks whether the lock is currently held by the requester.
        • If yes, it replies with the current numGranted.
        • If the lock is held by some other client, the server ignores the request. (The client will retry.)
        • If the lock is not held, it increments numGranted, sets the current holder to the sender of the request and sends a Grant message with the new value of numGranted back to the sender.
    • Grant
      • Source: Server
      • Destination: Client that sent Request
      • Contents: current value of numGranted
      • Sent whenever a Request arrives and either the lock is not held or the the lock is currently held by the client making the request.
      • What happens at the destination when it is received?
        • The client compares the client sequenceNum against numGranted in the message. If numGranted is higher, the lock is acquired, the client goes into Held and updates the client sequenceNum. Note that the client cannot already be in Held in this case. Otherwise the message is ignored.
        • The client can now perform whatever critical section it needs to do. If there is no need for the lock, or when the client is done with its critical section, it sends Release and sets mode to Releasing.
    • Release
      • Source: client
      • Destination: server
      • Contents: sequenceNum - set to the current sequenceNum at the client
      • When is it sent? When the critical section finishes, or when the client timer expires and the client mode is Releasing (the client doesn't know if the previous Release made it to the server).
      • What happens at the destination when it is received? The server checks the message sequenceNum against the current numGranted. If they match, this is a valid Release, and the holder is set to None. If the sequenceNum is smaller than numGranted, this is a duplicate. (The sequenceNum can never be larger than numGranted.) Either way, the server replies with a ReleaseAck to ensure that the client knows that the lock has been released (possibly some time ago).
    • ReleaseAck
      • Source: server
      • Destination: client that sent Release
      • Contents: current value of numGranted
      • When is it sent? In reply to a Release message
      • What happens at the destination when it is received? If the current value of the sequenceNum at the client is larger than numGranted, this is a replay to an older Release and we can safely ignore it (we've moved on and re-acquired the lock). Otherwise, if the state is Releasing, changed to Released. (The state could already be Released if this is a duplicate Ack.) We do not update the sequenceNum until we re-acquire the lock, and we don't immediately issue another Request but instead wait for a timeout to do that.
  • Timers:

    • ClientRetransmit: On expiration, if the client is in state None or Released, it issues a Request message. If the client is in state Releasing (the server may not have received the lock release), we send a Release message. If the client is in Held, do nothing.
    • The server does not have a timer.

Correctness/Liveness

  • The table below sketches the error handling for each message type.
MessageDropDuplicateReorderDelay
RequestClient will resend on timeoutServer will ignore if it has already granted the lock (to this client or a different one)If delivered after a later Release message, it is ignored by the serverNo effect (except to make it less likely this client will get the lock)
GrantClient will resend Request message; server will respond with resend of the Grant messageClient ignores if it already knows it has the lock, or if the lock has already been releasedClient will wait until it receives some Grant before it uses the critical sectionMerely delays when the client can use the lock
ReleaseClient will resend Release until the server replies with a ReleaseAck (or Grant)No effect if the server already received the Release (except to resend the ReleaseAck in case it didn't get through)If delivered after a later Request is granted (e.g., if this is also a duplicate), no effectNo effect except to delay the next Grant
ReleaseAckSee Release; server will respond with resend of the ReleaseAckClient ignoresClient won't send a new request until the previous release was acked, except a previous request duplicate can be delivered late and treated as a fresh request (giving the lock back to the client)Same as reorder
  • The table summarizes how message drops, duplicates, reorders, and delays are handled. Unless the lock is currently held by the client, the client will always retry, and so either the lock will be released (if the release was lost) or the lock will be acquired (if the lock is currently free).
  • The system does not handle node failures. Server failures, or client failures while holding the lock will cause the system to deadlock.
  • If the server node or client node holding the lock disappears, then the system is livelocked until the node reconnects. If a client node that's not holding the lock disappears, then it will have no effect on the system.
  • The system does not handle partitions. These can cause the system to deadlock.

Conclusion

  • Goals achieved: Mutual exclusion. Only one client at a time will hold the lock, and it will never think it holds the lock when another client holds the lock.
  • Limitations: Does not handle failures or partitions.
  • What goals are only achieved sometimes? Clients waiting for a lock may only sometimes get the lock, if they are unlucky as to when their requests arrive at the server.