Measure what a doorbell and a namespace request cost - #83
Conversation
…t cost Fifth slice peeled off `mikegrier/deferred-namespace-ops` (#56), after #79, #80, #81 and #82. Two probes, peeled together because neither answers its question alone. The two-layer ring design assumes signalling a waiting consumer is expensive enough to be worth avoiding, and proposes an eventcount -- publish intent to park, re-check the queue, then wait -- so a producer rings the doorbell only on the empty-to-non-empty edge. Publish-recheck-park is exactly where lost wakeups live, so building it because the cost was *assumed* would be taking on the highest-risk part of the design without evidence. `probe-doorbell-cost` supplies that evidence: `SetEvent` against an already signalled event, a full set/reset cycle, a satisfied wait, and an empty `SubmitIoRing`, each against an uncontended `fetch_add` floor. It also times a real park-and-wake round trip, which is what is actually paid when the consumer sleeps rather than spins. `probe-request-cost` asks the other half. The submission queue was specified to carry POD descriptors with no allocation on push, and a deferred `CreateFileW` carries a path, which is neither -- so that requirement and the namespace plane's needs cannot both hold as written. `windows-namespace-request-sys` already solves the hard part by making an `OpenFile` an owned, `Send` parameter set, so the queue can carry a request by value; what remains is what building one costs against the doorbell that would carry it. Read alone, either probe invites the wrong conclusion; read together they say whether the queue's mechanics or the request's own cost deserves the attention. Both run `--release` in CI, and only these two do. Every other probe here measures BEHAVIOUR -- what an API refuses, what a handle reports -- which does not change with the optimisation level. These two compare operations tens of nanoseconds apart, where the loop and closure indirection around each measured call carry overhead that does not shrink uniformly across them, and the ratios are what the design reads. The park-and-wake handshake is bounded rather than INFINITE deliberately: its first implementation DEADLOCKED, because an auto-reset event does not count signals and the waiter's count never caught up. Two tests pin that it completes, and they are bounded by construction -- a probe that can hang is a probe that can hang a build. Also adds the two probe-table rows in `lib.rs` that the source branch never had. The table is headed "what each probe establishes" and lists binary-only probes, so omitting these two made it wrong about its own scope. Measured on an x86_64 16p/8c host: a doorbell cycle costs 255x an uncontended atomic, a real park-and-wake round trip 43x that again, and building a pathed request 212 ns. The doorbell probe declines to derive a "doorbell is N% of a syscall" figure from its own `SubmitIoRing` number, because 214 ns is too cheap for a kernel transition and is almost certainly short-circuiting in user mode -- a confident wrong answer being worse than no answer.
…d bare `DOORBELL_NS_REFERENCE` is deliberately not re-baselined: it is the figure the 2026-08-30 design session recorded, and silently replacing it would leave that session citing a number that exists nowhere. Its own doc comment says so, and says the like-for-like comparison a reader wants is the two probes' outputs in the same CI job, which run under `--release` together. Three of the four places that mention it carry that qualification. The module doc did not -- it read "the doorbell that would carry it (~165 ns, per `probe-doorbell-cost`)", which states the figure as what that probe measures. It is not: on the x86_64 host in front of me `probe-doorbell-cost` reports 208.9 ns for an already-signalled `SetEvent` and 530.7 ns for a full set/reset cycle, so a reader who took the module doc at face value would find the probe contradicting it in the same log. Now qualified like the other three, and pointing at the local figure rather than implying the constant is one.
…id not establish Two independent reviews of the peel found six defects, five of them the class this component keeps hitting: a statement that is true of the machine it was written on and printed as fact everywhere else. **The report drew a design conclusion that inverts on this host.** It stated, under "What it does support", that for an open-heavy workload doorbell tuning would be optimizing the small half. That is only true when the doorbell is the smaller of the two, which it was on the development machine (165 ns against a 453 ns build) and is not here: `probe-doorbell-cost` reports a ~531 ns cycle against ~210 ns to build a request, so the doorbell is the LARGE half. Both probes run in the same CI job, so the sentence was contradicted a few lines down the same log. The existing caveats covered the printed ratio and the operation-type scope; neither guarded a sentence phrased as a finding. It now states the comparison, names the figure to read from the sibling probe, and declines the verdict -- which this probe cannot reach, because it measures only one side of it. Swept into the module doc, which carried the same claim. **The machine-readable line handed a consumer the ratio the prose forbids.** The report tells a human an empty `SubmitIoRing` is not a fair denominator and that any figure derived from it is a confident wrong answer -- then emitted `"doorbell_share_of_submit"`, unqualified, under a name meaning exactly that forbidden share. Renamed to `doorbell_over_empty_submit`, so the field states its own denominator and a query looking for a share of a syscall does not find it. The accessor keeps its name and its warning; only the wire field, which no mining pass reads docs for, changes. **The short-circuit diagnosis is now measured rather than asserted.** The caution block claimed unconditionally that the empty submit was "far too cheap for a kernel transition -- almost certainly short-circuiting in user mode", written around a 79 ns development-machine reading. Here it is 216 ns against 206 ns for an already-signalled `SetEvent`, so the claim is not merely unsupported but false. It is now decided against this probe's own measured syscalls; the denominator advice, which holds either way, stays unconditional. **Two optimizations were credited with each other's savings.** The module doc said an inline-storage or recycling scheme recovers the allocation part; the report said the same pair recovers `build - clone`, which is the Win32 resolution the module doc says an allocator cannot touch. They are different schemes: recycling a resolved path pays the clone instead of the build and recovers the difference, inline storage removes the allocation and copy and recovers at most the clone. Both texts now say so. **`time_loop` includes the drop, and now says it does.** `black_box` takes the returned value and it falls at the end of the statement, so the three heap-owning timings are construct-and-destroy cycles reported as construction. Documented rather than restructured: retaining every value -- which the captured-handle loop does, because dropping one calls `CloseHandle` and that is a second kernel transition -- would hold 100_000 live allocations and measure an allocator that never reuses a block. Neither regime is the shipping one, so the honest course is naming which this is. Raised for the engineer rather than settled here. **And the probe-table rows added in the previous commit over-claimed.** They said the doorbell probe establishes cost "against the `SubmitIoRing` it would guard" and the request probe "against the doorbell that would carry it" -- the two denominators these probes specifically decline to stand behind. They now name the absolute costs and the batching arithmetic actually established.
There was a problem hiding this comment.
🟡 Changes recommended
request_cost::measure reintroduces a hard-coded C: path (can fail on non-C: installs) and the new module docs include stray //! text that should be corrected before merging.
Once you've addressed the issues Copilot identified, you can request another Copilot review.
Pull request overview
Adds two new windows-platform-probes binaries (probe-doorbell-cost, probe-request-cost) to measure nanosecond-scale costs that inform the two-layer ring “doorbell vs batching” design decision, and wires them into CI under --release for stable ratios.
Changes:
- Introduce
doorbell_costandrequest_costprobe modules plus corresponding binaries and CI steps (release-only for these two). - Add an asserted deadlock-regression test for the doorbell park-and-wake handshake.
- Extend the crate’s probe table and dependencies to include the new probes and the real request type.
File summaries
| File | Description |
|---|---|
| crates/windows-platform-probes/src/tests.rs | Adds bounded tests for measure_park_and_wake to prevent deadlock regressions and NaN averages. |
| crates/windows-platform-probes/src/request_cost.rs | New probe module to measure request/path/handle duplication construction costs. |
| crates/windows-platform-probes/src/lib.rs | Exposes new modules and documents them in the probe capability table. |
| crates/windows-platform-probes/src/doorbell_cost.rs | New probe module to measure doorbell-related syscall/atomic costs and a bounded park-and-wake handshake. |
| crates/windows-platform-probes/src/bin/request_cost.rs | New binary that renders the request-cost report and emits NDJSON. |
| crates/windows-platform-probes/src/bin/doorbell_cost.rs | New binary that renders the doorbell-cost report and emits NDJSON. |
| crates/windows-platform-probes/Cargo.toml | Registers the two new bins and adds windows-namespace-request-sys + a windows-sys feature. |
| Cargo.lock | Adds windows-namespace-request-sys to the probes crate dependency set. |
| .github/workflows/ci.yml | Runs the two new probes in CI under --release (and only these two). |
Review details
Suppressed comments (1)
crates/windows-platform-probes/src/request_cost.rs:73
- This doc comment line ends with a stray
//!, which will show up in rustdoc output as literal text.
//! which half is which is the point of measuring both.//!
- Files reviewed: 8/9 changed files
- Comments generated: 2
- Review effort level: Lite
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
…rsed its own arithmetic
A second review round found seven defects, and the fixes from the previous
round are where several of them were -- which is the pattern this component
keeps producing and the reason the round was run before merging rather than
after.
**The doorbell probe recommended the opposite of what it measured.** Its
closing advice was that "the skip-when-busy rule is a refinement, not a
prerequisite ... a first implementation can always-signal and stay honest",
drawn from a table that divides ONE doorbell across a batch. That is the cost of
signalling once per drained batch -- coalescing on the empty-to-non-empty edge.
A producer that signals on every push amortizes nothing and pays a redundant
`SetEvent` per operation, which this probe measures directly at 208 ns against a
2.1 ns push: 100x, at every batch size. So the arithmetic supports the reverse
of the sentence. It now separates the two: coalescing is the prerequisite, the
parked-consumer check is the refinement, and the always-signal cost is printed
rather than assumed away. This was the probe's headline finding and was quoted
as such in the pull request description.
**The round trip was called what a wake costs.** `measure_park_and_wake`
documents its number as "an upper bound on what one wakeup costs rather than the
cost itself" -- it is two handoffs -- while the report said it "is what is paid
when the consumer genuinely sleeps". The report now says what the function says.
**The short-circuit diagnosis survived in two places the previous fix missed.**
That fix made the binary decide per host; the module doc and the accessor doc
kept asserting the development machine's reading, and the accessor used it as
the *reason* the ratio is meaningless ("a denominator that is not a syscall") --
a reason that is false on a host where the empty submit sits among the real
syscalls. Both now rest the argument on *carries no work*, which holds
everywhere.
**`GetFullPathNameW` was called a syscall.** `windows-namespace-request-sys`
documents it as lexical -- resolving `.` and `..` "without touching the
filesystem" -- so attributing the measured remainder to a kernel transition
contradicts the owning crate and names a mechanism a timing loop cannot
establish. The conclusion that mattered survives unchanged: whatever that work
is, an allocator cannot remove it.
**"Comparable" was concluded from a test for order.** The branch fires whenever
capture <= build, which is every ratio from 0.99 to 0.01, and reported all of
them as "comparable, and neither dominates". It now prints the ratio and leaves
the threshold to a reader who has one.
**The construct-and-destroy cycles now say so where a consumer reads them.**
The previous round documented in `time_loop` that the drop is inside the timed
region, and left the report saying "building ... costs" and the NDJSON emitting
`build_open_request_ns`. Documenting a caveat in source a miner never reads is
not disclosure: the wire fields are now `*_cycle_ns` and the table says which
rows include the drop.
And two doc lines carried a stray `//!` glued to the end of a sentence, from
splices in the previous commit, which rustdoc rendered as literal text.
|
Correction to the description above, from a second review round ( The "What it found" section quoted the probe's own closing advice -- that the skip-when-busy rule is a refinement and a first implementation can always-signal. That was wrong, and it reversed the probe's own arithmetic. The batching table divides ONE doorbell across a batch of N. That is the cost of signalling once per drained batch, i.e. coalescing on the empty-to-non-empty edge. A producer that signals on every push amortizes nothing: it pays a redundant The corrected finding, which is the one to read:
The design conclusion is therefore narrower than the description claimed: the risky eventcount can still be deferred, but the cheap edge-triggered coalescing cannot. Six other findings in the same round, all fixed in Three of those seven were introduced or left behind by the previous round's fixes, which is why the round ran before merge rather than after. |
There was a problem hiding this comment.
🔵 Needs a closer look
request_cost::measure still hard-codes a C:\... long-path sample which can panic on non-C installations, undermining the probe’s stated host portability.
Review details
Suppressed comments (2)
Previously missed (1) — in code that hasn't changed since the last review.
crates/windows-platform-probes/src/doorbell_cost.rs:128
doorbell_share_of_submitis named like a meaningful "share" of a submit, but it is explicitly a ratio over an emptySubmitIoRing. The probe’s NDJSON already exposes this asdoorbell_over_empty_submit; renaming this accessor (and updating the couple of call sites/docs) would make the API harder to misread and keep the terminology consistent.
crates/windows-platform-probes/src/request_cost.rs:184
long_texthard-codes aC:\...absolute path, which can still panic on machines without aC:volume (the same class of failure the surrounding comment says this probe is avoiding forsystem_dll). Build the long-path sample off the probed system directory (or at least its drive prefix) so the "prepare_long_path" loop remains runnable on non-C installations.
let long_text = format!(r"C:\{}\file.txt", vec!["directory"; 24].join("\\"));
let long = Wtf16String::from(long_text.as_str());
- Files reviewed: 8/9 changed files
- Comments generated: 0 new
- Review effort level: Lite
…ing a mechanism Two corrections from review, both about claiming more than a run established. Name the platform. The reference constants and every figure derived from them said "the development machine" -- a label only its author can resolve -- and were then read as though they described machines in general. They were measured on a Snapdragon X2 (ARM64). That matters because the two architectures do not merely differ in magnitude, they differ in sign: the doorbell cycle is 164.9 ns on ARM64 against ~531 ns on the x86_64 host measured during review, so the "which half is larger" comparison inverts, and the empty `SubmitIoRing` sits below that machine's syscalls on one and among them on the other. Two observations across two architectures is exactly the sample size from which no universal follows. Both probes already emitted an `arch` field, so only the prose was anonymous. Stop naming a mechanism. A previous commit replaced "a syscall cost" with "lexical" for `GetFullPathNameW`; the replacement is wrong in the same way the original was. `GetFullPathNameW` resolves against the process current directory, and for a drive-relative path against the per-drive current directory in the `=C:` environment variables -- process state, not string work. The reports now state the cost, say it touches no filesystem and is not an allocation, and decline to say whether it enters the kernel, which is the part the timing loop actually established. Queues M2.6 for the layer that owns the answer: `windows-namespace-request-sys`'s own doc carries the same imprecision, and the question of whether a genuinely lexical canonicalizer (`PathCchCanonicalizeEx`) should replace the call belongs there. The expected answer is no -- resolving against the CWD at submission is the property the namespace design buys -- but it should be recorded rather than re-derived. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
|
Two corrections from review, in 1. The reference figures were measured on a Snapdragon X2 (ARM64), and did not say so. The constants and everything derived from them said "the development machine". That is a
So "which half is larger" inverts between the two, and the short-circuit reading of the Worth noting for anyone auditing this: both probes already emitted an 2. My earlier correction in this PR was itself wrong, and I want that on the record. I replaced "a syscall cost" with "lexical" for What the run established is the cost. The reports now say that, add that it touches no This leaves a real question one layer down, queued as M2.6 rather than taken here: |
There was a problem hiding this comment.
🔵 Needs a closer look
The probe logic has a couple of correctness/convention issues (unvalidated WaitForSingleObject result, reintroduced C: assumption, and probe mains not using emit_report) that should be fixed before approval.
Review details
Suppressed comments (4)
Previously missed (3) — in code that hasn't changed since the last review.
crates/windows-platform-probes/src/doorbell_cost.rs:187
wait_zero_signalledis documented asWaitForSingleObject(handle, 0)on a signalled event, but the return value is currently ignored. If the wait fails (WAIT_FAILED) or times out, the probe will still record a plausible timing under a label that claims the satisfied path, which can mislead both the prose and NDJSON consumers.
crates/windows-platform-probes/src/bin/doorbell_cost.rs:26- This probe calls
emit(&mut Stdout, &render(...)), which means a panic insiderender(e.g., from assertions in the measured loops) would drop the partially composed report on the floor. The crate convention is to usereport::emit_report/emit_report_toso partial output is still printed on unwinding panics (documented incrates/windows-platform-probes/src/report.rs:112-170). Using that here would keep probe failures diagnosable in CI logs.
crates/windows-platform-probes/src/bin/request_cost.rs:48 - This probe uses
emit(&mut Stdout, &render()), which loses all already-composed output ifrender()panics before returning. The crate’s established pattern is to wrap probe rendering inreport::emit_report/emit_report_toso partial output is still emitted on unwinding panics (seecrates/windows-platform-probes/src/report.rs:112-170). Aligning this probe with that convention keeps failures diagnosable.
crates/windows-platform-probes/src/request_cost.rs:189
- This probe goes out of its way to avoid assuming Windows is installed on
C:(by queryingGetSystemDirectoryW), but the long-path test case reintroduces a hard-codedC:\...absolute path.prepare()ultimately callsGetFullPathNameWand returns an error on resolution failure, so this can make the probe panic on machines whereC:is absent/unmapped, defeating the earlier portability fix.
let long_text = format!(r"C:\{}\file.txt", vec!["directory"; 24].join("\\"));
let long = Wtf16String::from(long_text.as_str());
- Files reviewed: 9/10 changed files
- Comments generated: 0 new
- Review effort level: Lite
…he drive-letter claim Two findings from Copilot review on #83. The review ran against `fabda22`, two commits back, so its third finding (a stray `//!`) was already fixed in `ec4e2b5`; these two are live at HEAD and both land. Finish the rename. `ec4e2b5` renamed the serialized field to `doorbell_over_empty_submit` because "share of a submit" promises a meaningful fraction where the denominator is an empty ring, and left the accessor as `doorbell_share_of_submit`. The comment making that argument sat six lines from the sibling it did not reach, and the reviewer duly read the method as promising a share. A rename for precision is not finished until every name for the quantity moves; the field and the method are one fact with two spellings. Measure the drive-letter claim rather than arguing it. Two review passes read the hard-coded `C:` in the long-path sample as the portability bug that `system_directory()` fixes for the path that is really opened. It is not: `GetFullPathNameW` normalizes a fully-qualified path without consulting a device, so no volume is needed behind the letter -- verified by preparing a 24-component path on a drive letter with nothing mounted, which succeeds. That fact is now a test rather than a comment, and it earns its place twice over. It answers the review permanently, and it is the first thing here to pin "touches no filesystem" -- the claim the probe's whole account of where its nanoseconds go rests on, corrected only one commit ago and until now supported by nothing executable. The sample stays hard-coded, because the two paths have opposite requirements and making them match would hide that. `short` is really opened, so it must exist and is resolved; the long sample is only normalized, so it must not need to exist, and a fixed 24 components keep its length identical across the hosts the report asks a reader to compare. The comment now says so. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
|
Addressed in 1.
2. The hard-coded The stated failure mode isn't there. But two passes flagged the same line, which is its own evidence — the code was communicating a portability bug it doesn't have. So rather than reply with the argument, it's a test: fn preparing_a_path_needs_no_volume_behind_its_drive_letter()That earns its place twice over. It answers this permanently, and it's the first thing in the crate to pin "touches no filesystem" — which is the claim the probe's entire account of where its nanoseconds go rests on, corrected only one commit ago (in On why the sample stays hard-coded rather than taking the resolved drive: the two paths have opposite requirements, and making them match would hide that.
Building the second off the first would imply it needs a real volume when the opposite is the requirement. The comment now states that, and points at the test. |
There was a problem hiding this comment.
🟡 Changes recommended
Several timing loops and a new test can silently produce misleading measurements or introduce CI flakiness without small correctness checks (Win32 return-value validation and drive-letter selection via GetLogicalDrives).
Once you've addressed the issues Copilot identified, you can request another Copilot review.
Review details
Suppressed comments (5)
Previously missed (3) — in code that hasn't changed since the last review.
crates/windows-platform-probes/src/doorbell_cost.rs:177
- The event-manipulation timings don't check
SetEvent's return value. IfSetEventever fails (e.g., invalid handle), the probe will still record a plausible timing while no longer measuring the labeled operation, which is exactly the failure mode this probe warns about forSubmitIoRing. Consider asserting success here too (and inside the timed loop, since the check cost is negligible compared to losing measurement validity).
This issue also appears in the following locations of the same file:
- line 179
- line 185
crates/windows-platform-probes/src/request_cost.rs:187
system_directory()is obtained as a Windows path, but it's converted to UTF-8 withto_str().expect(...)before building aWtf16String. That reintroduces an avoidable panic (and can lose unpaired surrogates), even thoughWtf16Stringalready has a lossless WindowsOsStrbridge.
crates/windows-platform-probes/src/request_cost.rs:304system_directory()falls back toC:\Windows\System32when the fixed buffer is too small (the documented Win32 "required size" return), which can silently point the probe at the wrong file on unusual installs. Also, the doc comment says the failure is kept visible, but the fallback currently leaves no signal. Retrying with a dynamically sized buffer avoids the silent wrong-path case.
crates/windows-platform-probes/src/doorbell_cost.rs:183
set_reset_eventis intended to time a real SetEvent/ResetEvent cycle, but the code doesn't validate either call succeeded. A failing BOOL return would silently turn this into a timing of a failing syscall path (still a measurable transition), which would make the report misleading.
unsafe { ResetEvent(event) };
timings.push(time_loop("set_reset_event", ITERATIONS, || unsafe {
SetEvent(event);
ResetEvent(event);
}));
crates/windows-platform-probes/src/doorbell_cost.rs:188
wait_zero_signalledis documented as a satisfied wait (WAIT_OBJECT_0), but the timing loop ignores the return value. If the wait ever returnsWAIT_FAILED/WAIT_TIMEOUT, the probe would still emit a plausible timing under the satisfied-wait label.
unsafe { SetEvent(event) };
timings.push(time_loop("wait_zero_signalled", ITERATIONS, || {
unsafe { WaitForSingleObject(event, 0) };
}));
- Files reviewed: 9/10 changed files
- Comments generated: 1
- Review effort level: Lite
… guessing a path Six findings from review, all accepted. The rule adopted is flat: every call that returns a status has that status checked, inside the timed region, with no per-call-site argument about whether this one is worth it. The three event loops discarded their `BOOL`s while the `SubmitIoRing` loop twelve lines below them carried a careful note explaining why doing that lets a failing call report a plausible time for an operation that never happened. The argument was right and had been applied only to the call that looked expensive enough to deserve it. That is how the cheap calls get missed: "trivial enough not to check" is a fact about how hard anyone looked, not about the call. Both halves of the case are now measured rather than argued, by running the probe against a deliberately invalid handle so every event call fails. The unchecked version reported 212.7 ns for a redundant `SetEvent` (true 205), 422.9 for the set/reset cycle (true 531), and 231.5 for a satisfied wait (true 280). Not one looks wrong -- the first is within 4% -- so the whole doorbell-versus-build conclusion would have been drawn from a run in which no event operation succeeded. A failing syscall is not cheap enough to be conspicuous, which is the entire hazard. With the checks in place the same host reports 204-206, 528-534 and 280.3-280.7 across three runs: run-to-run spread, not a shift. The figures are recorded at the site so a future tuning decision starts from data. `CloseHandle` is checked as a post-condition, and earns it: in that bad-handle run, with every other check stripped, it was the one that caught it. Also in `request_cost`: - `system_directory` retries at the size the call asks for instead of giving up on a short buffer, and now panics rather than substituting `C:\Windows\ System32` when the system will not answer. The old fallback sat under a doc comment claiming it kept the failure visible while doing the opposite -- substituting a guess for an answer never obtained, which is the same defect as discarding a status. - The path stays wide from the Win32 call to `Wtf16String`. It went out through `from_utf16_lossy` and back in through `to_str().expect(...)`, which could panic on a non-UTF-8 path and silently replaced unpaired surrogates before that. `Wtf16String::from_os_str` exists for exactly this. And the new drive-letter test uses `GetLogicalDrives` instead of probing 23 roots with `Path::exists()`, which touches real devices: an offline mapped network drive would block it until the redirector times out, on exactly the CI machine most likely to have one. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
|
All six addressed in The three event loops (comments 1–3)Right, and the miss is instructive. Twelve lines below those loops, the Rather than assert that, I measured it — ran the probe against a deliberately invalid handle so every event call fails, with and without the checks:
Not one of those looks wrong. The redundant- Cost of the fix, same host, three runs with checks in place: 204–206, 528–534, 280.3–280.7. That's run-to-run spread, not a shift. One extra: I also check
|
There was a problem hiding this comment.
🟡 Changes recommended
There are correctness/robustness issues around unchecked Win32 return values in the handshake and JSON-invalid NaN defaults in NDJSON emission (plus a test robustness tweak needed).
Once you've addressed the issues Copilot identified, you can request another Copilot review.
Review details
Suppressed comments (3)
Previously missed (1) — in code that hasn't changed since the last review.
crates/windows-platform-probes/src/bin/doorbell_cost.rs:248
- The NDJSON line uses
unwrap_or(f64::NAN)for required timings, but JSON does not permit NaN/Infinity literals. If any label lookup ever fails (e.g., a renamed label), the probe would emit invalid JSON rather than a clear failure, undermining the “NDJSON parses” guarantee.
crates/windows-platform-probes/src/doorbell_cost.rs:357
measure_park_and_wakeignores the return value ofSetEvent(ping)in the timed loop. If it ever fails, the following wait will time out and the failure will look like a liveness issue rather than a failed wake attempt.
for _ in 0..rounds {
// SAFETY: both handles are live for the whole loop.
unsafe { SetEvent(ping) };
if unsafe { WaitForSingleObject(pong, WAIT_TIMEOUT_MS) } != WAIT_OBJECT_0 {
ok = false;
crates/windows-platform-probes/src/doorbell_cost.rs:368
measure_park_and_wakeclosesping/pongwithout checkingCloseHandle's return value. A close failure would leak handles across test runs and also suggests the handles were invalid, which would undermine the timing result.
// SAFETY: the peer has been joined, so nothing else holds these.
unsafe {
CloseHandle(ping);
CloseHandle(pong);
}
- Files reviewed: 9/10 changed files
- Comments generated: 2
- Review effort level: Lite
`measure_park_and_wake` discarded four statuses -- `SetEvent` on both sides of the handshake and both `CloseHandle`s -- in the same file, and the same commit, that declared the rule flat and wrote "no exceptions". Fixing `measure` and leaving its sibling one function below is precisely the failure the flat rule exists to prevent, committed while stating it. Found by `rustc`'s `unused_results` lint rather than by re-reading the file. The regex sweep that sized the workspace backlog had counted these sites; it reported a per-crate total, and a total is not a list, so the four in a file already believed fixed went unlooked-at. The lint names sites. The two `SetEvent`s are reported rather than asserted, because these run either side of a thread boundary. The peer returns `false`, since a panic there would be flattened into the same `false` by `join` with the message lost; the main loop sets `ok = false` and breaks, matching how the existing wait failure is handled. Neither changes the answer -- a failing `SetEvent` was already caught by the peer's wait timing out -- but it changes a five-second timeout per round diagnosed as "the peer never woke" into an immediate failure at the cause. The two `CloseHandle`s assert, matching `measure`. Verified with `RUSTFLAGS=-W unused_results`: the only remaining hits in this PR's two files are `fetch_add` and `black_box`, neither of which is a failable call. 26 unique sites elsewhere in the crate are pre-existing and belong to the workspace audit queued as M22.1. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
There was a problem hiding this comment.
🔵 Needs a closer look
It contains a real handle-leak / incorrect-error-reporting edge case in measure_park_and_wake and a new test that can fail spuriously on hosts with all drive letters mapped.
Review details
Suppressed comments (2)
Previously missed (1) — in code that hasn't changed since the last review.
crates/windows-platform-probes/src/doorbell_cost.rs:376
- In
measure_park_and_wake, the combinedassert!(!ping.is_null() && !pong.is_null(), ...)can leak a handle (if oneCreateEventWsucceeds and the other fails) and can also report the wronglast_os_error()(the last call may have succeeded, making the error code stale/undefined). Handle each creation separately, and closepingif creatingpongfails.
crates/windows-platform-probes/src/tests.rs:4395
- This test hard-fails if every drive letter A-Z is in use (e.g., many mapped network drives). That environment condition is unrelated to the behavior under test (that
preparedoesn’t consult the filesystem), so it can make the suite fail spuriously. Consider skipping the test when no unused letter is available.
let absent = (b'A'..=b'Z')
.find(|&byte| used & (1 << u32::from(byte - b'A')) == 0)
.map(char::from)
.expect("every drive letter A-Z is in use, so this test cannot run here");
- Files reviewed: 10/11 changed files
- Comments generated: 0 new
- Review effort level: Lite
…d why the drive test fails loudly Two findings from Copilot review on #83. One is a real leak-and-misreport that the previous commit introduced; the other is declined, with the reasoning recorded at the site so it is not re-raised a fourth time. `measure_park_and_wake` created both events and checked them with a single combined assertion. That leaked: with `ping` created and `pong` failing, it panicked without closing `ping`. It also misreported, and the misreport is the more interesting half -- `last_os_error()` was read after *both* calls, so a failure of the first was overwritten by the success of the second and the message could read "CreateEventW failed: The operation completed successfully". That defect was introduced by `bdc45cc`, which added `last_os_error()` to every assertion in the file precisely to improve diagnosis. Attaching an error code to a condition spanning two calls does not improve a diagnosis, it fabricates one: the code belongs to whichever call ran last, not to whichever failed. The rule this yields is now stated at the site -- an error code is only meaningful read immediately after the single call whose failure is being reported -- and the close in the new failure path reads its own rather than reusing an earlier one. Swept the file for the same shape. One more, milder, in `request_cost`: `GetSystemDirectoryW`'s retry asserted `retried != 0 && retried < heap.len()` with one error code, where only the first half is an OS failure and the second means the directory grew between calls. Split into two assertions so neither carries an error code that would be fiction. Declined: skipping the drive-letter test when no letter is free. libtest has no runtime skip, so a test that skips is a test that PASSES -- and this is the test pinning the claim that `prepare` needs no volume, which makes a pass that established nothing the one outcome worth avoiding. It is the same vacuous-green hazard as the unchecked `GetLogicalDrives` fixed in `c0a0021`. It also matches what this crate already does with the identical condition: two device-map tests `panic!("no free drive letter on this host, so the probe cannot run")` while searching only `H..=Z`, where this searches all 26 and so fails strictly less often. The condition needs `A` and `B` mounted too, which is essentially unreachable. The message now says plainly that it is an environment limitation rather than a failure of the behaviour under test. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
|
Addressed in Taken: the combined
|
There was a problem hiding this comment.
🔵 Needs a closer look
Two concrete issues were found that impact CI diagnosability/behavior (missing OS error details for CreateEventW failure, and the new probe steps may be skipped after earlier job failures unless guarded like the topology probe).
Review details
Suppressed comments (2)
Previously missed (2) — in code that hasn't changed since the last review.
.github/workflows/ci.yml:293
- These two probes are described as running in CI for the nanosecond comparisons, but without an explicit
if: '!cancelled()'they will be skipped whenever an earlier step in the job fails (defaultif: success()). Topology is already guarded to still run on failed jobs for exactly this reason; consider doing the same here so the probe output is still available when a failure elsewhere makes the log more valuable.
crates/windows-platform-probes/src/doorbell_cost.rs:172 - The CreateEventW failure path drops the OS error code, even though the surrounding assertions consistently include last_os_error() for diagnosability in CI logs. Including the error here makes failures actionable (e.g., invalid parameter vs handle quota) without needing a debugger.
- Files reviewed: 10/11 changed files
- Comments generated: 0 new
- Review effort level: Lite
…and guard the new CI steps Both findings from Copilot review on #83, and both were siblings walked past rather than new ground. `measure`'s `CreateEventW` assertion was the one site in this file still saying only "CreateEventW failed". `bdc45cc` added `last_os_error()` to every other assertion here and `c6684cf` fixed the creation pair in `measure_park_and_wake`; this one sat between the two sweeps and neither reached it. The CI steps for both new probes lacked `if: '!cancelled()'`, so Actions' default `if: success()` skips them whenever an earlier step in the job fails. The reason that is wrong is already written four lines above them, at the topology step: a probe step exists to emit diagnostics, so skipping it on failure suppresses it in exactly the run that wanted it. The argument was general and had been applied to one step. It matters more for these two than for a pass/fail probe. Both now assert every status they take and panic on a missing NDJSON label, and both route through `emit_report`, which prints what was composed before a panic -- machinery that only reaches a reader if the step runs at all. This branch spent several commits making these probes fail loudly and then left them in the one configuration where the failure is not printed. Two sweeps are queued rather than taken, because both reach beyond this peel: M2.7 -- nine of the twelve probe steps in CI are still unguarded, including the long-path pair, whose own comment says either half alone "says nothing" since the finding is the difference between two executables. Queued rather than changed because `!cancelled()` also runs a step when the *build* failed, turning skipped into failed, which trades quieter broken-build output for better broken-test output. The topology step took that trade; whether all twelve should is an operational judgement about how the log is read. M2.8 -- four assertion messages in probes this peel did not touch still name what was attempted without saying why it failed. The item carries the two rules this peel learned expensively: read the error immediately after the single call whose failure is reported, and attach one only where the condition is genuinely an OS failure. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
|
Both taken in The bare
|
There was a problem hiding this comment.
🟢 Approval recommended
The changes are cohesive and low-risk (probe-only additions plus CI wiring), and the only finding is a minor comment/doc mismatch noted inline.
Review details
Suppressed comments (1)
Previously missed (1) — in code that hasn't changed since the last review.
crates/windows-platform-probes/src/tests.rs:4333
- The comment says a machine under load may make each round “arbitrarily slow” without making it wrong, but the handshake implementation uses a 5-second timeout per wait and returns None on timeout. If a round takes >5s (e.g., extreme load/suspension), this test will fail, so the comment should reflect the bounded nature of the measurement.
- Files reviewed: 10/11 changed files
- Comments generated: 0 new
- Review effort level: Lite
…of claiming none The liveness test said a loaded machine "may make each round arbitrarily slow without making it wrong". It may not. Each wait inside `measure_park_and_wake` carries a 5-second timeout, and a round that exceeds it makes the handshake return `None` and fails the `expect` on the following line. The claim was contradicted twice within a few lines of being made: the section header directly above states the 5-second bound correctly and gives the reason for it, and the `expect` message on the next line reads "a bounded handshake of 64 rounds must complete rather than time out". Both were right; only this sentence was wrong, which is why nothing caught it -- each statement is locally true and only the pair is contradictory. Reworded to say what the code does, and to keep the reason the bound exists: a test that can hang takes the whole suite with it, which is worse than the defect it guards. The margin is enormous -- a sub-microsecond round trip against a 5-second ceiling -- so a failure means the machine stalled for seconds, which is worth a red test rather than a silently slow pass. Swept the crate for other unbounded-tolerance claims; this was the only one. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
|
Fixed in
What makes it worth more than a wording tweak is where the correct statement already was. Two places, both within a few lines:
So the file said "bounded" twice and "unbounded" once, in adjacent lines. Nothing caught it because each statement is locally true and only the pair is contradictory — the exact failure mode that per-artifact review is structurally blind to, which is why it took an outside reader to spot. Reworded to state the real bound and keep the reason it exists: a test that can hang takes the whole suite with it, which is worse than the defect it guards. The margin is enormous — sub-microsecond round trip against a 5-second ceiling — so a failure means the machine stalled for seconds, which deserves a red test rather than a silently slow pass. Swept the crate for other unbounded-tolerance claims ( Gate: 151 tests, clippy, encoding clean. |
There was a problem hiding this comment.
🔵 Needs a closer look
doorbell_cost reports last_os_error() for a WAIT_TIMEOUT wait result where GetLastError is undefined/stale, producing misleading diagnostics.
Review details
Suppressed comments (1)
Previously missed (1) — in code that hasn't changed since the last review.
crates/windows-platform-probes/src/doorbell_cost.rs:277
WaitForSingleObjectonly setsGetLastErrorwhen it returnsWAIT_FAILED; if it returnsWAIT_TIMEOUT(a successful return),last_os_error()is stale and can mislead the diagnosis. Capture the return value and only includelast_os_error()for theWAIT_FAILEDcase, otherwise report the wait code itself.
- Files reviewed: 10/11 changed files
- Comments generated: 0 new
- Review effort level: Lite
…s not describe it `WaitForSingleObject` is the one call in this file that returns a status code rather than a `BOOL`, and only `WAIT_FAILED` sets the last-error value. `WAIT_TIMEOUT` and `WAIT_ABANDONED` are *successful* returns, so the `last_os_error()` printed beside them belonged to whatever call ran previously and described something else. This is the rule the last two commits arrived at -- attach an error only where the condition is genuinely an OS failure -- broken in the same file that states it. The sweep that added `last_os_error()` to every assertion here treated a status-returning call like the `BOOL`-returning ones around it, which is how a rule about reporting failures became a way to report a failure that did not happen. That is the second time on this branch that a diagnosability sweep introduced a wrong diagnosis, after the combined `CreateEventW` assertion. The wait result now goes through `describe_wait`, which names the code and attaches `last_os_error()` only for `WAIT_FAILED`. Swept the crate: the other two `WaitForSingleObject` calls here compare and return without claiming an error, and `pool_growth`'s discards its result entirely, which belongs to the workspace audit rather than this peel. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…n in CI A CI run of PR #83 reported 96 failures in this crate. Recorded per the repository's test-tracking rule rather than left in a log, so the next occurrence is recognised instead of re-investigated. The 96 are one defect: `close::tests::a_caller_supplied_routine_is_carried` failed `assertion failed: !was_still_open(raw)` while holding the write guard on `handle_allocation()`, and the other 95 are `PoisonError` collateral from that guard. Established as intermittent rather than assumed: the same commit was re-run unchanged and passed, the crate passes 5/5 locally, and the two commits before it passed the identical job. It is also not attributable to the branch that observed it, which touches only `crates/windows-platform-probes/`, the workflow and `Cargo.lock`, and adds this crate as a dependency without changing its code. The mechanism is measured, not guessed. `was_still_open` probes a raw value by attempting to close it, and its own comment states the precondition -- that these tests hold the allocation lock. **218 tests in this crate, and 11 of them open handles without taking it.** `cargo test` runs tests as threads in one process, so a freed handle value can be reallocated by one of those 11 running concurrently, and the probe then finds it open. The entry also records what is worse than the visible assertion: `was_still_open` CLOSES the handle when the probe succeeds, so in the losing interleaving this test closes a live handle belonging to another test. The failed assertion is the benign outcome; a stranger failure elsewhere is the other one. Three directions are written down, with the observation that taking the lock in those 11 tests stops the bleeding while leaving the invariant resting on every future author remembering -- and that removing the need for the lock, by asking the close routine's own observation static rather than the OS, cannot race at all. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
|
Both handled. The CI failure was not this PR, and that is established rather than argued96 failures, all in Evidence it is intermittent:
The mechanism is measured. Worth flagging: the failed assertion is the benign outcome. Recorded in a new crates/windows-namespace-request-sys/UNRESOLVED-TEST-FAILURES.md per the repo's test-tracking rule, with three directions. Not fixed here: it is another component, and it wants a real decision (lock the 11, or remove the need for the lock by asking the close routine's own observation static instead of the OS — the latter cannot race at all). The wait-result findingRight, and it breaks a rule stated in the same file. The sweep in Now goes through Gate: 151 tests, clippy, encoding clean. |
There was a problem hiding this comment.
🟡 Changes recommended
A factual statement in the newly added UNRESOLVED-TEST-FAILURES.md contradicts the PR’s actual touched paths and should be corrected to keep the diagnostic record accurate.
Once you've addressed the issues Copilot identified, you can request another Copilot review.
Review details
- Files reviewed: 11/12 changed files
- Comments generated: 1
- Review effort level: Lite
The record is meant to save whoever picks this up from re-deriving anything, so a claim in it that does not survive checking is worse than a gap. Two did not. **Which paths the observing branch touches.** It said "touches only `crates/windows-platform-probes/`, `.github/workflows/ci.yml` and `Cargo.lock`", which was untrue the moment it was written, because the file saying it lives under `crates/windows-namespace-request-sys/`. The claim that holds, and the one actually meant, is that the branch makes no *source* change to this crate -- verified against the diff: its only file here is this record. **That the close routines have observation statics.** They do not. That is `windows-threadpool-sys`'s pattern for its wait targets, imported here by mistake. This crate calls the real `CloseHandle` and `FindCloseChangeNotification` directly with no shim, which `src/close.rs` records as deliberate, so there is nothing currently observable to ask and `was_still_open` -- at seven sites in `close/tests.rs` -- is the only mechanism the crate has. That makes the "ask the routine, not the OS" direction substantially larger than the entry implied, since it needs a test-only routine introduced first. The directions are reordered accordingly: make the hazard structural by having the fixture helpers take the lock, so omitting it is not something a test can do, is now second and is the smallest change that stops the flake recurring. Both corrections are recorded in place rather than silently rewritten, since the entry is a diagnostic record and a reader should be able to see what it once claimed. Also adds the `RESOLVED-TEST-FAILURES.md` sibling the record links to, which did not exist -- a link broken on creation, which the repository's own cross-reference rule forbids. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
|
Fixed in The one you foundCorrect, and neatly self-referential: the paragraph said the branch "touches only The one I found while checking the restYour comment made me re-verify every checkable claim in the entry rather than just the reported one. Most held — the 96/95 split, the identical-SHA rerun, 5/5 locally, 218 tests with 11 unlocked, the root DESIGN-NOTES citation. One did not. The entry proposed, as the most promising fix, that They do not. That is That mattered more than the path error: I had recommended it as the direction, so the next person would have started on a change substantially larger than advertised, requiring a test-only routine be introduced first. The directions are reordered — having Both corrections are recorded in place rather than silently rewritten. A diagnostic record's value depends on a reader being able to trust it, and seeing what it once claimed is part of that. Also added the Worth noting the shape: a record written to prevent someone re-deriving things contained two assertions that would have cost them time. The rule I keep relearning on this branch is that a claim is only worth writing down if it was checked, and prose about code gets no automatic verification the way code does. |
There was a problem hiding this comment.
🟢 Approval recommended
The changes are self-contained, well-defended with tests/documentation where behavior is subtle, and the CI wiring is consistent with the probes’ measurement goals.
Review details
- Files reviewed: 12/13 changed files
- Comments generated: 0 new
- Review effort level: Lite
…nst merged main Merges `origin/main` (PR #83) and reconciles this branch's measured claims with what landed, since every figure here describes code that just changed. The measurement table's third column was headed "true", which does not say what it is true *of*. Both columns are now named for the run they came from -- what the unchecked probe reported when every call failed, against what it reports when they succeed. Re-measured against `dc2b463` rather than left as written: - 121 bare `unsafe { Call(...) };` statements became 119, and `windows-platform-probes` 25 became 23, because #83 fixed sites inside it. - `SetEvent` discards fell from 4 to 2 for the same reason. - The `unused_results` count on that crate fell from 112 to 104. - "Two thirds are ordinary Rust" was an overstatement: it is 62 of 104, about sixty percent, with the remaining 42 naming a raw Win32 call -- and not even all of those are violations, since several of the calls return `void`. The failable set is now a table of six calls totalling 30 sites, each confirmed against its `windows-sys` signature rather than assumed. `CancelIoEx`, `RevertToSelf`, `SetCurrentDirectoryW` and `FindCloseChangeNotification` were checked and all return `BOOL`; `CloseIoRing` returns an `HRESULT`. The entry now says plainly that every number in it is stale on arrival and the lint should be re-run instead. That is the honest reading of figures that moved twice in one afternoon from an unrelated merge, and it keeps the item a description of the work's shape rather than an inventory somebody ticks off against a tree that has moved underneath it. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Fifth slice peeled off
mikegrier/deferred-namespace-ops(#56), after #79, #80, #81 and #82.Two probes, peeled together because neither answers its question alone.
The decision they inform
The two-layer ring has a client thread push a descriptor onto a bounded MPSC queue and then, sometimes, signal an event so the domain thread wakes. The design assumes that signal is expensive enough to be worth avoiding, and proposes an eventcount -- publish intent to park, re-check the queue, then wait -- so a producer rings the doorbell only on the empty-to-non-empty edge. Publish-recheck-park is exactly where lost wakeups live, so building it because the cost was assumed would be taking on the highest-risk part of the design without evidence.
probe-doorbell-costsupplies the evidence:SetEventagainst an already-signalled event, a full set/reset cycle, a satisfied wait, and an emptySubmitIoRing, each against an uncontendedfetch_addfloor, plus a real park-and-wake round trip.probe-request-costasks the other half. The submission queue was specified to carry POD descriptors with no allocation on push; a deferredCreateFileWcarries a path, which is neither.windows-namespace-request-sysmakes anOpenFilean owned,Sendparameter set, so the queue can carry a request by value -- what remains is what building one costs against the doorbell that would carry it.Both run
--releasein CI, and only these two do. Every other probe measures behaviour, which does not change with the optimisation level. These compare operations tens of nanoseconds apart, where the loop and closure indirection carry overhead that does not shrink uniformly.What it found
On an x86_64 16p/8c host: a doorbell cycle costs 255x an uncontended atomic, a real park-and-wake round trip 43x that again, and building a pathed request ~210 ns.
The batching arithmetic is the finding the design can actually use: at a batch of about 216, the doorbell costs less per operation than the atomic push it accompanies. The skip-when-busy rule is a refinement, not a prerequisite -- batching alone drives the doorbell below the push, so a first implementation can always-signal and stay honest, and the eventcount's lost-wakeup risk can wait for a measurement against real work.
Reviewed before pushing, and it mattered
Two independent reviews found six defects, five of the class this component keeps hitting: a statement true of the machine it was written on, printed as fact everywhere else. All are fixed in
fabda22; the notable ones:SubmitIoRingis not a fair denominator and that any figure from it is a confident wrong answer -- then emitted"doorbell_share_of_submit"unqualified. Renameddoorbell_over_empty_submit, so the field states its own denominator.SetEvent, making the claim false. It is now decided against the probe's own measured syscalls.One finding is documented rather than settled, and is for the engineer:
time_loopdrops each returned value inside the timed region, so the three heap-owning timings are construct-and-destroy cycles reported as construction. The obvious fix is not obviously better -- retaining every value would hold 100_000 live allocations and measure an allocator that never reuses a block, and a bounded queue is neither regime. The captured-handle loop is restructured, because dropping one callsCloseHandleand that is a second kernel transition rather than a free.Verification
cargo check --all-targetsclean across the workspace; clippy,cargo fmt --checkandcargo docclean--releaseand exit 0; the emitted NDJSON parsesAlso adds the two probe-table rows in
lib.rsthat the source branch never had -- the table is headed "what each probe establishes" and lists binary-only probes, so omitting these made it wrong about its own scope. The rows were themselves over-claimed on the first attempt and are corrected infabda22.