COMP 3150Operating Systems

01 Overview

CourseCOMP 3150 / CMIS 3150 — Operating Systems
UnitUnit 7 — Deadlocks
WeightPart of the 5% participation grade (each discussion is worth approximately 0.4% of the final grade)
OpensMonday of Week 7
Initial post dueThursday of Week 7, 23:59
Peer replies dueSunday of Week 7, 23:59
FormatOne initial post (500–700 words, including a diagram or a table of the lock ordering) and at least two substantive peer replies (150–250 words each)
CLOs assessedCLO3, CLO6, CLO8

02 Learning outcomes addressed

By participating in this discussion you will

  • Explain the four necessary conditions for deadlock and the meaning of each (U7.1, U7.2).
  • Construct and interpret a resource allocation graph and identify the cycles it contains (U7.3).
  • Apply the standard prevention and avoidance techniques and evaluate their costs (U7.3, U7.4).
  • Design a resource-acquisition protocol that is provably deadlock-free (U7.3, U7.4).
  • Evaluate the trade-offs between prevention, avoidance, detection, and recovery (U7.5).

This discussion assesses CLO3 (analyse deadlock solutions), CLO6 (apply Linux systems programming), and CLO8 (design OS-related components).

03 Discussion prompt

The scenario

You are the lead engineer on a new caching subsystem for a key-value store. The subsystem maintains the following shared data structures, each protected by its own mutex:

  • table_lock — protects the hash table that maps keys to cache entries.
  • lru_lock — protects the doubly-linked list that tracks the least-recently-used order of entries.
  • stats_lock — protects a small structure of hit and miss counters, updated on every lookup.
  • flush_lock — protects a queue of entries waiting to be written back to the backing store.

The subsystem must support the following operations, each of which may hold more than one lock at a time:

  • get(key) — look up a key in the hash table, move the entry to the front of the LRU list, and increment the hit/miss counter.
  • put(key, value) — insert or update an entry, which may require evicting an entry from the LRU list and enqueueing it on the flush queue.
  • evict() — remove the least-recently-used entry and enqueue it on the flush queue.
  • flush() — drain the flush queue and write each entry to the backing store.
  • stats() — read the hit/miss counters.

A junior engineer has implemented the operations with locks acquired in the order that seemed natural, and the subsystem deadlocks under load. Your job is to redesign the lock-acquisition protocol so that deadlock cannot occur, while keeping the subsystem's performance acceptable.

Your task

  1. Propose a global lock order. Assign a numeric rank to each lock and state the rule that all callers must follow. Draw the resulting lock hierarchy — a small table or a diagram is fine.
  2. Apply the order to each operation. For each of the five operations, list the locks it acquires, in the order your rule prescribes, and the locks it releases. If an operation needs to acquire a lock with a lower rank than one it already holds, explain how you restructure it to avoid the violation (for example, by releasing and re-acquiring, by using a separate "acquisition phase" and "work phase", or by splitting the operation).
  3. Argue correctness. Explain why your protocol makes deadlock impossible. Your argument should refer to the four necessary conditions and show that at least one of them cannot hold. Specifically:
    • If you choose to break circular wait, show that no cycle can form in the wait-for graph because every thread acquires locks in strictly increasing rank order.
    • If you choose to break hold-and-wait instead, explain the "acquire all or none" mechanism and its cost.
    • If you choose to use trylock with back-off, explain why no cycle persists indefinitely.
  4. Evaluate the trade-offs. Address:
    • What is the cost of your protocol in the common case (an uncontended lookup)? In the contended case?
    • Does your protocol permit the natural concurrency of the subsystem (for example, two get calls on different keys happening in parallel)?
    • Does your protocol introduce the risk of starvation — a thread that waits indefinitely while others proceed? If so, how do you bound it?
    • What would you have to give up to simplify the protocol further (for example, by using a single global lock for the whole subsystem)? Is the simplification worth it?
  5. Compare with an alternative. Discuss one alternative to your protocol — for example, a single global lock, a lock-free design for one of the structures (with atomics and RCU), or a partitioned design with one lock per hash bucket. State what the alternative would gain and what it would cost. State which one you would recommend and why.

Your post should be 500–700 words. Include the lock table or diagram. Cite at least one source (a textbook section, a paper, or a documentation page on lock ordering or on a real system that uses one — for example, the Linux kernel's lockdep, or the lock-ordering documentation of a database).

04 Notes on the techniques

The following notes may help you frame your argument. They are not exhaustive; you are expected to bring your own reasoning.

4.1 Global lock ordering

Assign a unique numeric rank to each lock. Every thread must acquire locks in strictly increasing rank order. Then no cycle can form in the wait-for graph: if thread T1 holds lock A and is waiting for lock B, then rank(A) < rank(B); the next thread in any cycle would need to wait for a lock with rank greater than rank(B), and so on, and the chain of ranks would have to be strictly increasing all the way around — which is impossible for a finite set of locks. This is the standard technique and the one used by most production systems, including the Linux kernel (through lockdep) and PostgreSQL (through documented lock order).

4.2 Try-lock with back-off

Instead of blocking on a lock, a thread can call pthread_mutex_trylock. If the lock is held, the thread releases the locks it holds and retries after a random delay. This breaks the "no preemption" condition: the resource can be effectively preempted from the holder's perspective, because the caller decides not to wait. The technique avoids deadlock but can livelock if the retry policy is poor; a bounded number of retries and a randomised back-off are essential.

4.3 A single global lock

The simplest way to eliminate deadlock is to use a single lock for the entire subsystem. There is never a second lock to acquire, so circular wait cannot form. The cost is concurrency: no two operations can proceed in parallel, and every operation serialises on the global lock. For a subsystem whose operations are short (as a cache lookup typically is), the simplicity is often worth the loss of concurrency. For a subsystem whose operations are long or involve I/O, the global lock becomes a bottleneck.

4.4 Partitioning

A middle ground: partition the hash table into buckets, each with its own lock. Two get calls on keys in different buckets proceed in parallel. But the LRU list is shared across all buckets, so operations that need to touch both the bucket lock and the LRU lock must follow a lock order: always acquire the bucket lock first, then the LRU lock. (Or the LRU list can be partitioned too, at the cost of a global view of LRU order.)

4.5 Lock-free designs

A fully lock-free design avoids deadlock by not using locks at all: the data structures are updated using atomic operations, and readers use read-copy-update (RCU) or hazard pointers to avoid seeing partially updated state. This eliminates the deadlock problem entirely but shifts the complexity to the memory-reclamation strategy and the algorithm design. The Linux kernel uses RCU extensively for read-mostly data structures.

05 Grading rubric

The discussion is marked out of 100 points. The criteria and their weights are given below.

Discussion 7 grading rubric — weights
Criterion Weight
Post quality (protocol design, correctness argument, trade-offs) 40%
Peer response quality 30%
Engagement and timeliness 20%
Netiquette and clarity 10%

5.1 Post quality (40 points)

Level Descriptor
Exemplary (90–100%) A clear lock order with a table or diagram. Each of the five operations is correctly mapped to the locks it acquires in the prescribed order, with any restructuring explained. The correctness argument is rigorous: it refers to the four conditions and shows that at least one cannot hold. The trade-offs are analysed in detail, including concurrency, starvation, and the common-case cost. An alternative design is discussed fairly and a recommendation is made. A source is cited.
Proficient (80–89%) A clear lock order with a table. Each operation is correctly mapped. The correctness argument is sound but not fully rigorous. The trade-offs are mentioned but not fully analysed. An alternative is discussed but no recommendation is made.
Developing (70–79%) A lock order is proposed but it is not applied correctly to all operations, or it does not cover all the locks. The correctness argument is descriptive rather than rigorous. The trade-offs and the alternative are missing or superficial.
Beginning (60–69%) No coherent lock order. The operations are described but the lock acquisition is not analysed. The correctness argument is absent.
Not demonstrated (<60%) The post is missing, off-topic, or a restatement of the prompt without a design.

5.2 Peer response quality (30 points)

The descriptors are the same as for Discussion 1. An exemplary reply might:

  • Identify a lock-ordering violation the classmate missed (for example, a code path that acquires two locks in the opposite order).
  • Propose an alternative lock order and compare its trade-offs with the classmate's.
  • Challenge the claim that the protocol is starvation-free and propose a scenario in which a thread could wait indefinitely.

5.3 Engagement and timeliness (20 points)

The descriptors are the same as for Discussion 1.

5.4 Netiquette and clarity (10 points)

The descriptors are the same as for Discussion 1.

06 Resources

  • Silberschatz, A., Galvin, P. B., & Gagne, G. Operating System Concepts, 10th ed., Chapter 8 (Deadlocks).
  • Arpaci-Dusseau, R. H., & Arpaci-Dusseau, A. C. Operating Systems: Three Easy Pieces, Chapter 32 (Concurrency Bugs: Deadlock).
  • Tanenbaum, A. S., & Bos, H. Modern Operating Systems, 4th ed., Chapter 6 (Deadlock).
  • Coffman, E. G., Elphick, M. J., & Shoshani, A. System Deadlocks. ACM Computing Surveys, 1971.
  • Havender, J. W. Avoiding Deadlock in Multitasking Systems. IBM Systems Journal, 1968.
  • Linux kernel documentation: Documentation/locking/lockdep-design.rst (the lockdep lock-order validator).
  • Linux man-pages: man 3 pthread_mutex_lock, man 3 pthread_mutex_trylock, man 3 pthread_mutexattr_setprotocol (for priority inheritance).
  • PostgreSQL documentation: Lock Management (documented lock levels and lock ordering).
  • Herlihy, M., & Shavit, N. The Art of Multiprocessor Programming, 2nd ed., Chapters 8 and 9 (Monitors and Linked Lists).
👤