Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion PLANS.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ plans tracker: [crates/windows-file-enumeration-sys/PLANS.md](crates/windows-fil
| [CHECKLIST-thread-ambient.md](CHECKLIST-thread-ambient.md) | in progress | M22-M23: extract the captured-context composite into `windows-thread-ambient-sys`, a standalone platform layer that captures a thread's ambient state and applies it on another thread. M24-M26: `windows-namespace-request-sys`, marshalable Win32 namespace call parameter sets, over a round-one entry list audited from three real consumers (this repository's watcher and enumeration crates, and `MikeGrier/Globazog-rs`) rather than guessed. M27: `windows-platform-probes`, a durable home for the measurements this workspace's designs rest on, under a three-tier scheme (asserted / ignored / binary-only) where every tier is compiled by an ordinary build. Feature-scoped and deleted when complete; it is the whole of the `mikegrier/thread-ambient` branch's work, and is deliberately separate from the deferred namespace-facility items in [CHECKLIST.md](CHECKLIST.md). | [crates/windows-thread-ambient-sys/DESIGN-NOTES.md](crates/windows-thread-ambient-sys/DESIGN-NOTES.md) |
| [crates/windows-overlapped-io-sys/CHECKLIST.md](crates/windows-overlapped-io-sys/CHECKLIST.md) | not started | M14: finish the contract audit -- categories 1, 2, 6, 8, 9 were not examined -- and sweep `outstanding()` for the advisory-predicate hazard. | [crates/windows-overlapped-io-sys/DESIGN-NOTES.md](crates/windows-overlapped-io-sys/DESIGN-NOTES.md) |
| [crates/windows-ioring-sys/CHECKLIST.md](crates/windows-ioring-sys/CHECKLIST.md) | in progress | Memory-safe Rust over the Windows `IoRing` submission/completion ring, as a new crate. M1-M7 (ring lifecycle through the `ring-copy` topology-aligned sample) are complete and archived. The parked, pinned-thread `M6+` work and the new M10 contract audit remain. | [crates/windows-ioring-sys/DESIGN-NOTES.md](crates/windows-ioring-sys/DESIGN-NOTES.md) |
| [crates/windows-platform-probes/CHECKLIST.md](crates/windows-platform-probes/CHECKLIST.md) | not started | M1: stream a probe's report as it is measured. The one-sink refactor has each renderer compose its report into a `String`, which buys the seam that lets a probe's findings be asserted rather than eyeballed, and gives up output appearing as it is measured. `emit_report` recovers that for an unwinding panic only; Ctrl-C and an abort during unwinding still discard the buffer. Costs most on `probe-cancel-io`, which runs about twenty seconds precisely when the wedge it hunts for occurs -- precisely when a reader interrupts. | [crates/windows-platform-probes/DESIGN-NOTES.md](crates/windows-platform-probes/DESIGN-NOTES.md#d-buffered-report) |
| [crates/windows-platform-probes/CHECKLIST.md](crates/windows-platform-probes/CHECKLIST.md) | in progress | M1 (streaming reports) is done and archived: every probe now writes into the sink as it measures, through a `fmt::Write` adapter that left all 332 `writeln!` call sites untouched, and the `catch_unwind`/`resume_unwind` pair is gone because there is no longer a buffer to rescue. Measured with a control -- a probe killed 300 ms into a 0.8 s run keeps its banner and heading on six runs of six, where the previous build kept nothing on six of six. M2 remains: check correspondence *between* a report's parts, which is the defect class no per-part instrument in this crate can see. | [crates/windows-platform-probes/DESIGN-NOTES.md](crates/windows-platform-probes/DESIGN-NOTES.md#d-streaming-report) |
| [CHECKLIST-mutation-survivors.md](CHECKLIST-mutation-survivors.md) | not started | Work queued from the workspace-wide cargo-mutants sweep of 2026-09-02, whose findings are kept in [mutation-sweeps/2026-09-02/](mutation-sweeps/2026-09-02/README.md) rather than re-derived -- the run took roughly fourteen hours. 2,792 caught, 1,112 survived, 198 timed out. **The headline numbers mislead in three ways and the README says how**: a timeout in a blocking-API crate is usually a detection that lost its name rather than a gap (measured: one of `windows-waitable-queues`' 120 timeouts fails four tests in 0.00s when re-injected alone), a low score on an executable probe crate is measuring the wrong thing, and three kinds of survivor -- equivalent mutants, unreachable code, and constants that want a `const` assertion -- are not missing tests at all. M1 covers the shipping crates; M2 holds the two crates that are not libraries and whose scope is an engineer's decision; M3 re-runs and prunes rather than hand-editing the tool's output into a second source of truth. | [mutation-sweeps/2026-09-02/README.md](mutation-sweeps/2026-09-02/README.md) |

Add a row here when new work is planned, against [CHECKLIST.md](CHECKLIST.md) or any crate's.
50 changes: 0 additions & 50 deletions crates/windows-platform-probes/CHECKLIST.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,56 +5,6 @@ separately, in the workspace [CHECKLIST-thread-ambient.md](../../CHECKLIST-threa
M27; that file is feature-scoped and is deleted when its feature completes, so durable follow-up work
for the crate belongs here instead.

## M1 -- Stream a probe's report as it is measured

The report sink introduced with [src/report.rs](src/report.rs) has each renderer compose its whole
report into a `String`, which `emit_report` then hands to a [`Report`]. That buys the seam the crate
wanted -- a probe's findings can be asserted rather than eyeballed -- and it gave up a property the
previous line-by-line `println!` had for free: output appearing as it is measured.

`emit_report` recovers it for an **unwinding panic** only, by catching, emitting what was composed, and
resuming. A termination that does not unwind still discards the buffer:

- **Ctrl-C.** The default Windows console handler terminates the process; no unwind runs.
- **Abort from a panic raised while already unwinding.**

The case that costs most is `probe-cancel-io`: four attempts against a five-second watchdog, so about
twenty seconds, and it runs that long *precisely when the wedge it hunts for occurs* -- which is
precisely when a reader gives up and interrupts. The measurement most worth having is the one most
likely to be thrown away.

See [DESIGN-NOTES.md](DESIGN-NOTES.md) -> [The report is buffered, and what that
costs](DESIGN-NOTES.md#d-buffered-report) for why it was built this way and why the fix is a separate
piece of work rather than a correction to that one.

- [ ] **M1.1** -- Decide how a formatted line reaches the sink, because that choice is what makes the
rest mechanical. Every renderer today writes through `let _ = writeln!(out, ...)` against a
`String`'s `fmt::Write` -- upwards of 160 sites across the probes -- so the sink must accept
*formatted* output, not just `&str`, or every site grows a `format!` and an allocation per line.
The options differ in what they cost callers, and the choice is the engineer's:
(a) give `Report` a method taking `fmt::Arguments` plus a `report_line!` macro, so a call site stays
one line and reads almost as it does now;
(b) implement `fmt::Write` for the sink types, so `writeln!(out, ...)` keeps working verbatim against
a `&mut dyn Report` -- smallest diff at the call sites, but `fmt::Write` is line-agnostic, so the sink
must split on newlines internally and `Captured`'s one-line-per-entry guarantee has to be re-established
rather than assumed;
(c) leave the renderers writing to a `String` and flush it to the sink at each line boundary, which
streams without touching the call sites but keeps two buffers.

- [ ] **M1.2** -- Convert every renderer to write into the sink as it measures, and simplify
`emit_report` accordingly: once lines leave as they are produced, catching the unwind is no longer
what makes partial output work, and the `catch_unwind`/`resume_unwind` pair should be removed rather
than left as machinery that no longer earns its place. Keep `Captured` working -- it is what every
in-process test asserts against.

- [ ] **M1.3** -- Verify by interruption, not by reasoning. Sending Ctrl-C to a probe part-way through
must leave the already-measured lines on the terminal; today it leaves nothing. Assert the in-process
half (a renderer that panics mid-report still has its finished lines in a `Captured`) as a unit test,
and record the Ctrl-C observation in [DESIGN-NOTES.md](DESIGN-NOTES.md) -- an interactive signal is not
something to assert in CI, but it is the property the milestone exists for, so it must be measured
once rather than assumed.


## M2 -- Check correspondence between the report's parts, not just each part

A pull-request review found a state where [src/topology_report.rs](src/topology_report.rs) printed
Expand Down
95 changes: 95 additions & 0 deletions crates/windows-platform-probes/COMPLETED-CHECKLIST.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
# Completed checklists: windows-platform-probes

Append-only. Newest groups at the bottom.

## Moved 2026-09-09 19:00:17 -04:00 -- M1: a probe's report streams as it is measured

## M1 -- Stream a probe's report as it is measured

The report sink introduced with [src/report.rs](src/report.rs) has each renderer compose its whole
report into a `String`, which `emit_report` then hands to a [`Report`]. That buys the seam the crate
wanted -- a probe's findings can be asserted rather than eyeballed -- and it gave up a property the
previous line-by-line `println!` had for free: output appearing as it is measured.

`emit_report` recovers it for an **unwinding panic** only, by catching, emitting what was composed, and
resuming. A termination that does not unwind still discards the buffer:

- **Ctrl-C.** The default Windows console handler terminates the process; no unwind runs.
- **Abort from a panic raised while already unwinding.**

The case that costs most is `probe-cancel-io`: four attempts against a five-second watchdog, so about
twenty seconds, and it runs that long *precisely when the wedge it hunts for occurs* -- which is
precisely when a reader gives up and interrupts. The measurement most worth having is the one most
likely to be thrown away.

See [DESIGN-NOTES.md](DESIGN-NOTES.md) -> [The report is buffered, and what that
costs](DESIGN-NOTES.md#d-buffered-report) for why it was built this way and why the fix is a separate
piece of work rather than a correction to that one.

- [x] **M1.1** -- Decide how a formatted line reaches the sink, and build it. **Option (b): `LineSink`,
an adapter implementing `std::fmt::Write` over a `&mut dyn Report`.** Decision and reasoning in
[DESIGN-NOTES.md](DESIGN-NOTES.md#d-streaming-report).

**The estimate in this item was wrong, and re-measuring it decided the question.** It said "upwards
of 160" `writeln!` sites; there are **332** across this crate's production renderers, written into
the `&mut String` of 18 functions. Option (a) -- a `Report` method taking `fmt::Arguments` plus a
macro -- is the most explicit and would have rewritten all 332; that is affordable at 160 and is not
at 332. Option (b) moves the 18 signatures and leaves the 332 untouched, because `String`
implements `fmt::Write` too and a call site cannot tell the difference. Option (c) was declined as a
half-measure that keeps two buffers.

The cost this item predicted for (b) is real and is now paid: `fmt::Write` is line-agnostic, so
`LineSink` holds a partial line and emits completed ones, and `Captured`'s one-line-per-entry
guarantee is re-established by test rather than assumed. Seven tests pin it, including the two
properties that are easy to get wrong -- a final `write!` with no trailing newline still emits its
line, and `split('\n')` rather than `lines()` because only the former distinguishes a finished line
from a partial one. Both were verified by sabotage (failing three tests and two respectively), not
by reading.

- [x] **M1.2** -- Convert every renderer to write into the sink as it measures, and simplify
`emit_report` accordingly. All thirteen probes now take `out: &mut dyn std::fmt::Write`; the
`catch_unwind`/`resume_unwind` pair is deleted, because with lines leaving as they are produced
there is no buffer to rescue and keeping it would imply partial output still depends on the panic
unwinding. `Captured` is unchanged and its tests pass untouched.

**Every probe in this crate needed only the signature change**, because each already went through
`emit_report` rather than composing a `String` and calling `emit` itself. That is what the
one-sink refactor bought, and it is why converting thirteen probes is one function plus one line
per renderer. Three further probes under development on a branch do not hold that property and
are converted where they land, since they are not in this crate yet.

**Verified with a control, because several of these probes are not deterministic.** A direct
before/after comparison flagged four of the thirteen reports, which is not evidence -- they print
measured nanoseconds and branch their verdicts on them. Running the *same* build twice differs in
**five**, by the same amount or more in every case: `probe-doorbell-cost` 34 lines against 30,
`probe-request-cost` 32 against 32, `probe-pool-growth` 14 against 14, `probe-device-map` 4
against 4, and `probe-cancel-io` 2 against **0** -- that last one being the sharpest, since a
probe whose output varies run to run happened to match across the change and would have counted
as evidence of no change had the control not existed. The eight reports the control showed to be
genuinely deterministic were byte-identical. A before/after diff on a probe means nothing without
that control.

- [x] **M1.3** -- Verify by interruption, not by reasoning. Both halves done, and the in-process half
needed a test this item did not describe.

**The unit test as specified would not have caught a regression.** "A renderer that panics
mid-report still has its finished lines in a `Captured`" passes under a *buffered* report too --
emit the buffer after catching the unwind and it holds, which is exactly what the pre-M1.2 code
did. What distinguishes streaming is not what a reader has at the end but **when** the sink
receives it, so `a_line_reaches_the_sink_before_the_renderer_returns` observes the sink from
*inside* the renderer through a shared `Rc<RefCell<..>>`. Restoring the old buffered
`emit_report_to` fails it with its own message; the panic test alone would have stayed green on
the mechanism and gone red only on the missing catch.

**The interruption half is measured, with a control**, and recorded in
[DESIGN-NOTES.md](DESIGN-NOTES.md). `probe-doorbell-cost` (~0.8 s, the longest-running probe
here), stdout redirected, killed at 300 ms, six runs of each build with every run confirmed still
alive at the kill: the streaming build captured **129 characters** (banner and heading) on all
six, the pre-conversion build **0** on all six. The control is what makes it evidence rather than
an observation.

`TerminateProcess` was used rather than Ctrl-C deliberately: it runs no handler at all, where
Ctrl-C still lets the runtime unwind its exit path, so surviving it subsumes the interactive case.
The reason any of it works is that Rust's `Stdout` wraps a `LineWriter` and flushes at each
newline even when redirected -- had stdout been block-buffered this milestone would have needed a
per-line flush too.
Loading