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
- 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
- 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.
- 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.
- 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:
- Process P's own request is at the head of P's request queue.
- 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.
- 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.)
- 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.
Further Reading:
Logical clocks are described in detail in the following
classic paper. This paper is one of the most often cited papers
in the distributed computing literature.
Leslie Lamport,
Time, clocks, and the ordering of events in a distributed system.
Communications of the ACM,
Vol. 27, No. 7, July 1978, pp. 558-565.
To receive credit for this lab, you should:
- Clean up and print out your code. (Don't turn it in, but
save it for your code/design review.)
- Turn in a
Project Evaluation Form near the beginning of
class on the day you want to do your demonstration and code/design
review. You should be
prepared to demonstrate your working application, explain your
design and code, and answer questions.