Skip to content

The one surviving fixed-wall-clock poll (test_ledger_reports.cpp) should use a MORPH_LADDER_DEADLINE_MS-scaled deadline, not a hand-picked 100x10ms budget #223

Description

@Yaraslaut

Update: premise was true when filed, but is now stale

This issue was filed 2026-08-23T21:13:36Z. PR #222 merged 2026-08-24T05:43:53Z
— about 8.5 hours later — and it already rewrote the exact
pollUntilSettled doc comment this issue quotes and disputes.

On origin/master today, the doc comment no longer says "far inside that
budget." It reads:

A bounded retry loop with a hard cap (100 x 10ms = 1s). Used by exactly ONE
test case below — the one that deliberately keeps a real
ThreadPoolExecutor underneath the model (see its own comment). Every other
case in this file injects a StepExecutor and drives the worker by hand, so
it neither sleeps nor guesses at a budget. Do not reach for this helper for
a new case without the same explicit justification.

and the one retained call site's own comment now ends with:

The cost is the retry loop pollUntilSettled still carries. Paying it once,
for the properties only a real thread can show, beats paying it five times.

That is already an honest statement of a deliberate tradeoff, not the "far
inside that budget" framing this issue originally rebutted. #222 also cut the
blast radius from the 4 call sites this issue originally counted down to
exactly 1, by converting 3 of this file's 4 report-job tests to a
deterministic, hand-driven StepExecutor that removes polling entirely.

The bookmarks/pastebin claim is refuted

The original "why it survives #222" section below claimed
examples/bookmarks/tests/test_app.cpp and
examples/pastebin/tests/test_paste_model.cpp "use the same fixed-budget
shape." Against current master this is factually wrong:

$ git grep -n "for (int i = 0; i < 100" origin/master -- examples
origin/master:examples/ledger/tests/test_ledger_reports.cpp:57:
  • bookmarks/tests/test_app.cpp has no sleep_for at all — it uses
    pumpUntil, which is already MORPH_LADDER_DEADLINE_MS-scaled.
  • pastebin/tests/test_paste_model.cpp's only mention of sleep_for is a
    comment explaining it deliberately avoids one, in favor of a
    condition-variable-based SqlLogger hook.

A repo-wide grep confirms the fixed 100x10ms loop shape exists in exactly
one place today, not four.

The timing concern itself still holds, narrower

Rebuilt and ran ladder_ledger_tests [reports] 10x on an idle machine:

0.218 s  0.219 s  0.219 s  0.220 s  0.167 s
0.219 s  0.216 s  0.235 s  0.237 s  0.219 s

167–237 ms against the 1000 ms cap: 4.2x–6.0x margin — the same
single-digit-x order of magnitude as this issue's original macOS measurement
(6.7x at its worst observed), not "far" in any open-ended sense. Under
synthetic full-core CPU contention, six runs came back at 237–326 ms
(margin 3.1x–4.2x) — a real, measurable degradation under load, confirming
the qualitative direction of the original concern even though the specific
numbers and file scope were stale. The cap has not been observed exhausted on
any machine or in CI history for this test.

Invariant 7 applies, but narrower than originally framed

"Would this still pass if the feature did nothing?"pollUntilSettled is
not a control that measures nothing: it correctly fails the test
(REQUIRE(status.status == Done)) if the cap is exhausted while the job is
still Pending. The real, still-live gap is narrower: the cap itself doesn't
scale with MORPH_LADDER_DEADLINE_MS the way every Qt-side wait in this
codebase does via pump.hpp's pumpUntil, so on a slow or loaded runner the
margin measured above shrinks without any of the usual dials moving.

Corrected scope

Add a non-Qt waitUntil(pred, deadline) helper that shares pump.hpp's
MORPH_LADDER_DEADLINE_MS scale factor, and have the one remaining real-pool
poll in examples/ledger/tests/test_ledger_reports.cpp use it instead of its
hand-picked 100x10ms wall-clock budget. Whether this helper belongs inside
pump.hpp itself (whose current contract is "the Qt loop is the pump") or
beside it is a design call for whoever picks this up, not a mechanical one.

This is now a narrow test-infra robustness improvement affecting one call
site, not a multi-file defect in shipped behavior.


Original report (superseded above; kept for the measurement detail)

What was measured

examples/ledger/tests/test_ledger_reports.cpp::pollUntilSettled polls a submitted report job with a hard iteration cap:

for (int i = 0; i < 100; ++i) {
    status = model.execute(ledger::GetReportStatus{.jobId = jobId});
    if (status.status != ledger::ReportStatus::Pending) { break; }
    std::this_thread::sleep_for(std::chrono::milliseconds(10));
}

Its own doc comment (before #222 rewrote it) claimed "A single report job over a tiny test ledger completes far inside that budget; exhausting the cap means a real stall … not a slow machine."

I instrumented the loop and counted the iterations it actually spends, on macOS 15.6 / arm64, Debug, /opt/homebrew/opt/llvm/bin/clang++, -DMORPH_LADDER_RUNGS=ledger, at 93a6168. Five consecutive runs of the retained real-pool case:

PROBE pollUntilSettled slept=15
PROBE pollUntilSettled slept=7
PROBE pollUntilSettled slept=9
PROBE pollUntilSettled slept=7
PROBE pollUntilSettled slept=12

So the job takes 70–150 ms, and varies by better than 2x run to run on an idle machine. Against a 1000 ms cap that is a margin of 6.7x at worst observed, not "far inside".

The other half of the measurement is what contention does to the same suite. Timing [reports] while another compile was running on the same machine, against the steady-state figure:

contended: 3.48 s, 6.58 s, 2.39 s
idle:      1.28 s, 1.29 s, 1.29 s

A 2.7–5.1x whole-suite slowdown. Applied to the worst observed 15 iterations, that is 40–77 iterations — and a 6.7x slowdown, inside the range already observed, puts it at 100.

Verification status

  • Reproduced: the iteration counts and the contended/idle wall-clock figures above are measured output from this machine, not inferred.
  • Not reproduced: I never actually saw the cap exhausted. The 6.7x figure is arithmetic over two separately measured quantities (per-job iterations, whole-suite slowdown under load), not an observed failure. Treat it as a margin estimate.
  • Not measured on Linux/CI hardware at all.

This is the same shape as #147 — a budget that is fine on the machine it was written on.

What would change the verdict

  • Close if the remaining real-pool polls move to a deadline scaled by MORPH_LADDER_DEADLINE_MS, or if measurement on CI hardware shows the margin is comfortable there and the macOS figures are an outlier worth ignoring.
  • Escalate if the cap is ever observed exhausted in CI or locally — that turns the estimate above into a reproduction.

Metadata

Metadata

Assignees

No one assigned

    Labels

    area: ladderSubsystem: laddertriage: validWell-framed; implement as written

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions