# CS333 Lab A-2: Logical Clocks

A system is distributed if the message transmission delay is not negligible compared to the time between events in a single process.

-- Leslie Lamport, "Time, clocks, and the ordering of events in a distributed system"

## Goal of this lab:

• Learn about the the differences between real time and logical time in an asynchronous distributed system.
• Understand and implement an algorithm for maintaining logical clocks.
• Use logical clocks to implement a simple mutual exclusion algorithm for resource allocation.

## Lab Guru:

If you have questions about this lab, you may contact Srihari Sampathkumar (Sam), srihari@cs.wustl.edu, or the instructor.

## Introduction:

A distributed system consists of a collection of processes that communicate by passing messages to each other. So, each event in a distributed system is either a local step of a process, a send event, or a receive event. In studying a distributed system, we often need to assign an order to the events. However, since the processes in the system do not have synchronized clocks, the "logical time" order of events in the system does not necessarily agree with the clock times associated with the events at the various processes. For example, we expect a logical view of the system in which the send event for a given message happens before the receive event for that message. However, if the clocks at the sender and the receiver are sufficiently skewed, a clock-based trace of the events might report that the receive occurred before the send.

One approach to this problem is to run algorithms to keep clocks closely synchronized, within some tolerance. However, a simpler approach is to maintain logical clocks at the processes. Logical clocks do not "tick" like real time clocks, but instead keep track of the order of events that occur at each process, and ensure that events are assigned consistent logical times according to the following happens before relation.

We say that event A happens before event B if and only if

1. A and B occur at the same process (either internal local steps, send events, or receive events) and event A occurs before B at that process, or
2. A is a send event and B is a receive events for the same message.

We can construct a logical clock algorithm that will assign logical times to all the events in the system in a way that is consistent with the happens before relation, as follows.

• Each process keeps an integer, initially 0, that represents its internal logical clock.
• Whenever a process takes a local step, it increments its logical time by 1, and the incremented time is considered to be the time of the local event.
• Whenever a process sends a message (send event), it increments its logical time by 1, and sends that new time with the message. This time is considered to be the logical time of the send event.
• Whenever a process receives a message (receive event), it first compares its own logical clock time to the logical time sent with the message, and sets its own logical clock to be the maximum of the two times. Then, it increments its logical time by 1, and the incremented time is considered to be the time of the receive event.

Notice that the logical times of events at a given process always increase as the execution proceeds. Furthermore, notice that the logical send time is always less than the logical receive time. The logical times assigned to the events gives us a partial order on all events. Convince yourself that the partial order is consistent with the happens before relation.

## Directions:

Read over the entire assignment before starting.

1. Implement a Logical Clock: Write a Playground module that implements the logical clock algorithm described above. Publish your logical clock so that it's available for visualization in EUPHORIA. Test your implementation by having the modules take internal steps and send messages randomly. Each time an event occurs at a module, have it print out the logical time of the event. Check that the logical times assigned to your events respect the happens before relation.

2. Use Logical Clocks to Implement Mutual Exclusion: After you have thoroughly tested your logical clock implementation, use it to implement the following resource allocation strategy.

Consider a collection of processes that contend for a shared resource (such as a file, printer, etc.). It is important that only one process access the resource at a time, or bad things may happen (inconsistent data in the file, garbled printer output, etc.). Therefore, the processes cooperate by running a mutual exclusion protocol to ensure that at most one process has access to the resource at a given time. There are many different approaches to this problem, but the following one exploits the logical clocks you have just implemented.

• Let each module have a unique identifier. (Set up your module so that these can be assigned from the command line on start up.)
• Let each process maintain a request queue, not seen by any other process. Each queue entry contains the unique identifier of a requesting process and the logical time of the request. The queue is kept in increasing logical time order (i.e., the entry at the head of the queue has the least logical time).
• To request the resource, a process broadcasts a request message to all other processes.
• Whenever a process receives a request, it puts the request in its queue and sends an acknowledgement message to the requesting process.
• A process P can use the resource whenever the following conditions are satisfied:
1. Process P's own request is at the head of P's request queue.
2. Process P has received a message (could be an acknowledgement or any other message) from every other process with a logical timestamp (send event time) greater than P's request time.
• When a process finishes using the resource, it sends a release message to every other process.
• Whenever a process receives a release message, it removes the sender's request from its request queue.

3. Convince yourself that the algorithm satisfies the following properties:

Mutual Exclusion (safety):
No two processes access the resource simultaneoulsy.
No Starvation (liveness):
Provided that any process holding the resource will eventually release it, a process that requests the resource will eventually get it. (Furthermore, it can be bypassed at most once by each other process.)

4. Exercise your implementation by having the modules periodically request the resource, hold the resource for a short time, and release the resource. Let each module publish a variable indicating whether it is currently waiting for the resource, and another indicating whether it holds the resource. Use these variables to visualize the state of the distributed system in EUPHORIA. Test your implementation thoroughly.