# Heap, scheduling, I/O and IPC — fixing exercises

Eight deliberately broken programs, two per subject. For each one you get the broken
starting point, an interview-style prompt, the exact command that reproduces the failure,
the evidence to collect, bounded success criteria, progressive hints, a revealable
solution, a separate fixed source file, a `.patch` from one to the other, and a check that
proves both halves.

These are companion material to the **Heap allocation**, **OS scheduling**, **I/O** and
**IPC** chapters of the *macOS operating-system concepts* reference. They stand alone:
nothing here reads that page, and nothing here needs a network.

```bash
./run-all.sh          # validate the entire bundle — about 25 seconds
./run-all.sh 05 07    # run only the named exercises
./run-all.sh --list   # print the exercise names and stop
```

---

## Safety — read this before running anything

**Several of these programs waste CPU, saturate every core, or are killed by a signal on
purpose.** None of them can wedge the machine, because every one carries two independent
bounds:

1. an **in-process watchdog thread** that calls `_exit(75)` after a fixed budget, reporting
   through `write(2)` and exiting through `_exit(2)` rather than `printf`/`exit` — a wedged
   process may hold a lock that stdio or an `atexit` handler would need;
2. an **external hard timeout** in every `check.sh` that `SIGKILL`s well before that.

**Exit status 75 means a watchdog fired.** **Exit status 141 means death by `SIGPIPE`, and
for exercise 08's broken build that is the expected, correct result** — a run that exited 0
would be the failure.

Exercise 01 allocates up to about 2.5 GB and exercises 02 up to about 1.1 GB, briefly. On a
machine with little free memory, run those two on their own. Nothing writes outside
`$TMPDIR`, nothing needs elevation, and nothing touches the network.

**None of the broken code belongs in production**, and most of it compiles without a single
warning — exercise 01 is clean under Swift 6 strict concurrency — which is exactly why it is
worth fixing by hand.

---

## The exercises

| # | Subject | Failure mode | Language | What it teaches |
| --- | --- | --- | --- | --- |
| 01 | heap | strong reference cycle | Swift 6 | ARC is not a collector; a leak is not high memory use; measure the slope, not the point |
| 02 | heap | fragmentation | C | `free()` does not return pages; when an allocation belongs in its own mapping |
| 03 | scheduling | busy-wait | C | Runnable costs a core; blocked costs a stack; when spinning is right |
| 04 | scheduling | undeclared QoS | C | QoS is placement, not speed; the defect lives in the tail; every fix has a price |
| 05 | I/O | unbuffered reads | C | The syscall is the cost; where the buffer-size curve flattens |
| 06 | I/O | non-atomic publication | C | `rename` is the atomic primitive; what `fsync` is actually for; the durability ladder |
| 07 | IPC | message framing | C | A stream carries bytes, not messages; framing is correctness, not tuning |
| 08 | IPC | unhandled `SIGPIPE` | C | A peer's death is part of your API; per-descriptor beats process-wide |

---

## How to use them

**Cold, one at a time, without the solution open.** The broken starting points are
**immutable**: copy one, repair your copy, and leave the original for the next time you want
the drill cold.

For each exercise:

1. Read the prompt and answer its "before you run anything" question **out loud**. Every one
   of them is the actual interview question hiding inside the exercise.
2. Reproduce the symptom. Do not skip this. "I read the code and saw the bug" is not the
   skill being practised.
3. Collect the evidence the README names, and notice which evidence is *direct* (a `deinit`
   that never runs; one byte per system call) and which is merely *consistent* (a footprint
   that does not fall).
4. Fix it, then run `./check.sh`.
5. Answer the last success criterion, which is always some version of **"what does your fix
   cost?"**. Every repair in this bundle costs something, and every README says what.

---

## What the checks actually do

Each `check.sh`:

- compiles the broken and the fixed source **each alone in a fresh temporary directory**,
  and fails on any unexpected warning;
- applies `solution.patch` to a **copy** of the broken file and requires the result to equal
  the fixed file byte for byte;
- runs the broken build and asserts its documented symptom;
- runs the fixed build and asserts its documented success;
- asserts that the two builds produce the **same answer** wherever an answer exists — a
  checksum, a line count, a record count. A repair that changes the result is a different
  program, not a fix;
- repeats every timing-sensitive or probabilistic measurement.

**Where a measurement is deterministic the check asserts it exactly** (`tornReads == 0`,
`bytesPerSyscall == 1.0`, exit status 141). **Where it is timing-sensitive it asserts a
ratio with a generous bound**, and the bound's reasoning is written in the check's own
header, so the exercise still validates on hardware that behaves quite differently.

Every absolute figure in these READMEs is one machine under one load:

> Apple M4 Pro (10 performance + 4 efficiency cores), macOS 26.3 (25D125), Swift 6.2.4
> (swiftlang-6.2.4.1.4), Apple clang 17.0.0, Xcode 26.3, 16 KiB pages, APFS on internal SSD.

**Reproduce them; do not quote them.**

---

## Requirements

- macOS with the Xcode command line tools (`xcode-select --install`)
- `clang` and `swiftc` on `PATH`
- No network, no elevation, no package manager, no Xcode project

Every script resolves its paths from its own location, so an extracted copy of the archive
works anywhere on disk.

---

## What these exercises deliberately do not cover

- **Anything requiring elevation.** `fs_usage`, `dtrace` and the privileged paths of
  `spindump` need `sudo`, so no check depends on them. Where they would help, the README
  says so and names the unprivileged evidence used instead.
- **Instruments.** No check opens the GUI and no screenshot appears anywhere. Where an
  instrument would show the same thing, the README names the instrument and the view.
- **Power-failure durability.** Exercise 06 makes publication atomic against concurrent
  readers, deterministically. No fixture can pull the plug, and that exercise says so.
- **XPC and Mach ports as fixtures.** Exercises 07 and 08 use pipes and Unix sockets because
  they reproduce identically in a self-contained program with no service registration. The
  XPC and Mach behaviour they point at is discussed in the chapter and in the solutions, and
  is documentation rather than something this bundle measures.
- **Threads, locks and data races.** Those have their own bundle,
  `threads-locks-exercises`, and are not repeated here.
