# Stage 4 — Schedulers

Verdict on this Mac: **PASS**. Linux: **PENDING** until the workflow's ExUnit run includes `test/sched_test.exs`.

`abi_dirty_cpu()` and `abi_dirty_io()` are generated from `ERL_NIF_DIRTY_JOB_CPU_BOUND` and `ERL_NIF_DIRTY_JOB_IO_BOUND`. `scripts/check-abi.sh` diffs those integers with the header. On this OTP they are 1 and 2.

`Mojoler.Sched.burn/1` and `hold/2` are registered with the CPU flag. `io_ping/0` uses the I/O flag. The DSL generator rejects any other schedule name.

E22: in a VM started with `+S 1`, a 200 ms dirty burn let 18 heartbeats through. The same loop with flags 0 let 0 through. The normal-scheduler library is `experiments/sched/normal_burn.mojo`, built into a temp directory.

`hold/2` copies the binary into a `calloc` buffer, frees the inspect descriptor, then spins, then writes a new binary. The borrowed view does not span the spin.

`Task.yield(task, 30)` returned nil while `burn(200)` was running. `Task.await` then returned `:ok`. The timeout stopped the wait, not the native loop. There is no cancellation API.

A dirty scheduler keeps the normal scheduler free. It does not contain a crash. The abort probe is a separate process for that reason.
