Capstone Projects
Learning Objectives
Choose a capstone at your level and deliver it with an explicit concurrency model, correctness invariants, failure and cancellation behavior, tests (including race/stress cases), resource and shutdown management, reproducible performance measurements, security/overload considerations and a README.
Assess your work against the rubric.
Plain-Language Explanation
Capstones turn knowledge into evidence. Each project below is small enough to finish but realistic enough to hit real concurrency problems — overload, cancellation, shutdown, races — and every one must be shipped with the same engineering discipline a production service would need.
Precise Technical Explanation
Threaded counter with correct synchronization race demo → lock → atomic → per-thread counters, with a stress test bounded buffer with condition variables, close/shutdown, exactly-once test Async timer and task visualizer asyncio/JS timers, task states, a text or web timeline Promise/future chaining playground chaining, combinators, error paths, timeouts
Concurrent file processor with bounded workers pool sizing, bounded queue, progress, cancellation
Async HTTP client with timeouts and cancellation deadlines, AbortSignal/CancellationToken, retries Thread-safe cache with expiry locks or concurrent maps, single-flight, TTL, eviction Worker pool with graceful shutdown lifecycle, draining, deadlines, leak checks tasks vs microtasks, timers, host differences
Bounded channel and backpressure simulation full policies, throughput vs latency
Concurrent web crawler with domain rate limits per-domain token buckets, dedup, politeness, cancellation
Parallel data-processing pipeline with measured performance stages, backpressure, scaling curves
Async service client with retries, deadlines, circuit breaker, cancellation Concurrent queue with correctness tests linearizability checking, stress, TSan Runtime/task profiler dashboard task counts, loop lag, pool metrics
Deadlock detection and lock-order analyzer (educational) wait-for graphs, lock hierarchy checker RTOS-style task scheduler simulation priorities, periods, PI, deadline misses
Lock-based vs atomic counters under multiple workloads benchmark methodology, contention, false sharing
Structured task scope with cancellation and failure propagation scope semantics, exception groups, shielding Work-stealing scheduler simulation deques, steal policies, granularity
Model-check a small synchronization protocol state-space exploration, invariants, liveness
Concurrent service with bounded queues, overload protection, tracing, graceful shutdown the whole course
Real-time producer/consumer with priority and deadline analysis RM/EDF, WCET, PI Every capstone must include
Problem statement and architecture diagram.
Explicit concurrency model (threads/tasks/processes, who owns what, how they communicate).
Correctness invariants and how they are enforced.
Failure and cancellation behavior.
Tests, including race/stress cases where applicable (with timeouts).
Resource and shutdown management.
Performance measurements with reproducible methodology.
Security and overload considerations.
A README with setup, run instructions and known limitations.
Insufficient (0) invariants stated and enforced; race detector/stress clean works under normal load races, lost work or hangs explicit ownership and communication; simplest correct design implicit/shared everything Failure & cancellation timeouts, cancellation, cleanup, tested Shutdown & resources bounded everything; graceful drain with deadline; no leaks leaks or hangs on exit deterministic + stress + failure-path tests happy path + some stress reproducible measurements, percentiles, scaling curves Security & overload limits, backpressure, threat model README, diagram, limitations
Why It Matters in Real Systems
The checklist above is exactly what code reviewers and on-call engineers look for; practising it on small projects builds the habit.
Key Terms
Who runs what, who owns which data, how they communicate Property that must always hold
Draining work within a deadline and releasing resources Benchmark whose setup and method others can repeat
Diagram or Execution Timeline
flowchart LR
P[problem + invariants] --> M[concurrency model]
M --> I[implementation]
I --> T[tests: deterministic, stress, failure paths]
T --> B[benchmarks: reproducible]
B --> R[README + limitations]Syntax and API Overview
This is a concurrency concept, not a language construct; each project uses the APIs of its chosen language (Module 17).
Complete Code Example
A reference skeleton for the intermediate "worker pool with graceful shutdown" capstone — bounded queue, cancellation, per-task errors, deadline-bounded drain and a leak check (CPython 3.13.1 asyncio):
import asyncio
class WorkerPool:
def __init__(self, workers, capacity):
self.queue = asyncio.Queue(capacity) # bounded: backpressure
self.workers = [asyncio.create_task(self._worker(i), name=f"worker-{i}") for i in range(workers)]
self.results, self.errors = [], []
async def submit(self, job, timeout=1.0):
await asyncio.wait_for(self.queue.put(job), timeout) # caller waits (bounded) when full
async def _worker(self, i):
while True:
job = await self.queue.get()
try:
self.results.append(await job())
except asyncio.CancelledError:
raise
except Exception as e: # one failing job does not kill the worker
self.errors.append(f"{type(e).__name__}: {e}")
finally:
self.queue.task_done()
async def shutdown(self, drain_deadline):
try:
async with asyncio.timeout(drain_deadline):
await self.queue.join() # graceful: finish queued work
drained = True
except TimeoutError:
drained = False
…graceful drain: True | results: [0, 1, 4, 9, 16, 25, 36, 64, 81] | errors: ['ValueError: job 7 failed']
slow job, 0.1 s drain deadline -> drained: False (remaining work cancelled)
tasks still alive after shutdown: []Line-by-Line Explanation
The bounded queue makes submit wait (up to a timeout) when workers fall behind — backpressure.
Workers catch ordinary exceptions per job (recorded, not fatal) but re-raise cancellation.
shutdown first drains within a deadline (queue.join() under asyncio.timeout), then cancels and awaits the workers; it reports whether the drain completed.
The leak check confirms no task outlives the pool.
Output
(see the program output above)Interleaving or State Trace
10 jobs, 3 workers, capacity 4: submits 0–6 fill the workers and queue; submit 7 waits until a worker takes a job; job 7 fails and is recorded; after all task_dones, join() returns, workers are cancelled, gathered, and the pool is empty.
Correctness and Safety Analysis
Invariants: every submitted job is either completed (result), failed (error), or — after a missed deadline — cancelled; workers never die from job errors; no tasks leak after shutdown.
Common Mistakes and Misconceptions
Unbounded queues, workers that die on the first exception, shutdown that cancels before draining (losing work) or drains without a deadline (hangs), and no leak check.
Failure Modes and Debugging Steps
Shutdown hangs → a job ignoring cancellation; lost results → exceptions escaping workers; memory growth → unbounded queues — Modules 13, 14, 23.
Performance and Scalability Implications
Measure throughput and latency percentiles versus worker count and queue capacity; include the drain time distribution.
Security Considerations
Bound the queue and per-job time; never let untrusted jobs run without timeouts.
Platform and Runtime Caveats
The skeleton uses Python 3.11+ (asyncio.timeout); ports to other languages use their pools and cancellation tokens.
Real-World Applications
Every job runner, crawler and background-processing service has this shape.
Best Practices
Ship the checklist items with the code; measure before and after changes; keep the design as simple as the requirements allow.
Exercises
E1 · Beginner. Add a progress counter to the pool safely.
Answer: Increment it in the worker after each job (single-threaded asyncio: no lock needed between awaits) and expose it read-only.
E2 · Intermediate. Add per-job timeouts.
Answer: Wrap await job() in asyncio.timeout(job_timeout) inside the worker and record TimeoutError as a job error.
E3 · Advanced challenge. Port the pool to threads with queue.Queue and a threading.Event for shutdown.
Answer: Workers loop on q.get(timeout=…) checking the event; shutdown sets the event after q.join() (bounded with a timeout by polling unfinished_tasks), then joins worker threads with timeouts; errors are collected in a lock-protected list.
Quiz
? What should graceful shutdown do first?
+ Stop accepting work and drain in-flight work within a deadline. :: Then cancel what remains.
- Cancel every worker immediately. :: That loses queued work.
- Wait forever for all work. :: A slow job would hang shutdown.
- Exit the process. :: Resources and work would be lost.
? Why do workers catch ordinary exceptions but re-raise CancelledError?
+ One bad job should not kill a worker, but cancellation must still stop it. :: Correct separation.
- CancelledError is not an exception. :: It is a BaseException subclass.
- To hide errors. :: Errors are recorded.
- asyncio requires it. :: It is a design choice.
? Which belong in every capstone README? (select all that apply)
+ Concurrency model and invariants :: Required.
+ Known limitations :: Required.
- Only screenshots :: Not sufficient.
+ How to run tests and benchmarks :: Required.Summary
Capstones at four levels apply the whole course; each must state its concurrency model and invariants, handle failure, cancellation, overload and shutdown, test races and failure paths, measure reproducibly and document limitations — assessed with the rubric.
Further Reading
The references of every module.
"The Twelve-Factor App" (disposability: fast startup and graceful shutdown).