Raise backfill auto-continue cap so a deep backlog drains in one connection (#533) - #594
Merged
Merged
Conversation
ryanbr
force-pushed
the
fix/backfill-auto-continue-cap-533
branch
from
July 18, 2026 23:47
77cd00d to
25cca0b
Compare
…ection (#533) The per-connection consecutive auto-continue cap (MAX_AUTO_CONTINUES / defaultMaxAutoContinues) was 6. Guards 1-3 in shouldAutoContinue (healthy link, genuine backlog, advancing trim, plus the #928/#1012 future-clock exclusion) already stop the pathological cases, so this cap is only the backstop against a strap that advances its trim but never advances OUR frontier. #533 field data (21k rows, ~9s productive bursts 15-20 min apart, ~95% idle) showed a well-behaved deep backlog hitting the cap at 6 and being throttled to the 15-min periodic floor mid-drain, so recent nights landed hours after waking (surfacing as the false sleep-detection symptom in #515). Raise the cap to 24 so a typical deep backlog drains in a single connection (~24 productive passes ≈ a few minutes back-to-back); the backstop window only ever bites the rare data-shape spin. Value is tunable and flagged for on-strap validation. Both platforms bumped in lockstep (parity contract). Tests (both platforms in lockstep): the multi-pass-drain test now uses a cap-relative backlog (cap + 5 days) so it pins the cap-bounds-a-deep-drain invariant independent of the value (the old hardcoded 7-day backlog was > 6 but < 24, so it now correctly drains to caught-up); add a deep-drain regression test pinning that a genuine backlog past the old cap of 6 still continues.
ryanbr
force-pushed
the
fix/backfill-auto-continue-cap-533
branch
from
July 18, 2026 23:50
25cca0b to
99dfcc4
Compare
This was referenced Jul 19, 2026
ryanbr
added a commit
that referenced
this pull request
Jul 19, 2026
…licker it (#587 follow-up) (#608) Follow-up to #590. That change tied the pull-to-sync vessel + "Syncing…" label to `live.backfilling`, but that flag toggles false→true between EVERY offload chunk (exitBackfilling at each HISTORY_END → auto-continue re-kick → beginBackfill), with a real BLE gap in between. A deep backlog is now up to ~24 chunks in one connection (#594 raised the auto-continue cap 6→24), so the vessel strobed in/out on every chunk boundary — the exact per-chunk toggle the MenuBar header already pins a constant height against. Debounce the visibility: a new @State `syncing` goes true instantly on syncingRaw, but goes false only after riding out a 3s quiet window with no new chunk (cancelled the moment the next chunk arrives). So the vessel stays steady through a multi-chunk drain and only drops when the sync is genuinely done. iOS/macOS shared (LiquidTodayView), one file. swiftc -parse clean; i18n audit exit 0 (reuses the existing "Syncing…" key). App-target compile to be confirmed by app-build.
DX23876
pushed a commit
to DX23876/noop
that referenced
this pull request
Jul 22, 2026
…ection (ryanbr#533) (ryanbr#594) The per-connection consecutive auto-continue cap (MAX_AUTO_CONTINUES / defaultMaxAutoContinues) was 6. Guards 1-3 in shouldAutoContinue (healthy link, genuine backlog, advancing trim, plus the ryanbr#928/ryanbr#1012 future-clock exclusion) already stop the pathological cases, so this cap is only the backstop against a strap that advances its trim but never advances OUR frontier. ryanbr#533 field data (21k rows, ~9s productive bursts 15-20 min apart, ~95% idle) showed a well-behaved deep backlog hitting the cap at 6 and being throttled to the 15-min periodic floor mid-drain, so recent nights landed hours after waking (surfacing as the false sleep-detection symptom in ryanbr#515). Raise the cap to 24 so a typical deep backlog drains in a single connection (~24 productive passes ≈ a few minutes back-to-back); the backstop window only ever bites the rare data-shape spin. Value is tunable and flagged for on-strap validation. Both platforms bumped in lockstep (parity contract). Tests (both platforms in lockstep): the multi-pass-drain test now uses a cap-relative backlog (cap + 5 days) so it pins the cap-bounds-a-deep-drain invariant independent of the value (the old hardcoded 7-day backlog was > 6 but < 24, so it now correctly drains to caught-up); add a deep-drain regression test pinning that a genuine backlog past the old cap of 6 still continues.
DX23876
pushed a commit
to DX23876/noop
that referenced
this pull request
Jul 22, 2026
…licker it (ryanbr#587 follow-up) (ryanbr#608) Follow-up to ryanbr#590. That change tied the pull-to-sync vessel + "Syncing…" label to `live.backfilling`, but that flag toggles false→true between EVERY offload chunk (exitBackfilling at each HISTORY_END → auto-continue re-kick → beginBackfill), with a real BLE gap in between. A deep backlog is now up to ~24 chunks in one connection (ryanbr#594 raised the auto-continue cap 6→24), so the vessel strobed in/out on every chunk boundary — the exact per-chunk toggle the MenuBar header already pins a constant height against. Debounce the visibility: a new @State `syncing` goes true instantly on syncingRaw, but goes false only after riding out a 3s quiet window with no new chunk (cancelled the moment the next chunk arrives). So the vessel stays steady through a multi-chunk drain and only drops when the sync is genuinely done. iOS/macOS shared (LiquidTodayView), one file. swiftc -parse clean; i18n audit exit 0 (reuses the existing "Syncing…" key). App-target compile to be confirmed by app-build.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Closes #533.
The bottleneck
The per-connection consecutive auto-continue cap —
WhoopBleClient.MAX_AUTO_CONTINUES(Kotlin) /BackfillContinuation.defaultMaxAutoContinues(Swift) — was 6. When the strap has a deep backlog, NOOP drains it in immediate back-to-back offload passes; once it hits the cap it falls back to the 900 s (15-min) periodic timer.#533field data (≈21k rows, ~9 s productive bursts 15–20 min apart, ~95 % of wall-time idle) shows a well-behaved deep backlog hitting the cap at 6 and then being throttled to the 15-min floor mid-drain — so recent nights landed hours after waking. That's the same late-arrival that surfaced as the false sleep-detection symptom in #515.Guards 1–3 in
shouldAutoContinue(healthy link, genuine backlognewest − frontier > behindGapSeconds, advancing trim, plus the #928/#1012 future-clock exclusion) already stop the pathological cases. The cap is only the backstop against a strap that advances its trim but never advances our frontier (a data-shape spin). So raising it doesn't weaken any real safety guard.The change
Bump the cap 6 → 24 on both platforms (parity contract — byte-identical scheduler behavior). A typical deep backlog now drains in one connection (~24 productive passes ≈ a few minutes back-to-back) instead of stretching across several 15-min windows.
android/.../ble/WhoopBleClient.kt—MAX_AUTO_CONTINUESStrand/BLE/BLEManager.swift—defaultMaxAutoContinuesThe worst-case backstop window widens from ~6 min to ~24 min of back-to-back radio use only in the rare true-spin shape (trim advances, frontier doesn't) — and guards 1–3 already catch the common variants of that before the cap is even consulted.
Tests (both platforms, in lockstep)
BackfillContinuationTest.kt+BackfillContinuationTests.swift:cap + 5days) so it pins the cap-bounds-a-deep-drain invariant independent of the value. The old hardcoded 7-day backlog was> 6but< 24, so with the new cap it now correctly drains to caught-up rather than capping (the fix working as intended) — which is why the hardcoded assertion had to become cap-relative.continues_pastOldSixCap_onDeepBacklog— new; pins that a genuine backlog atconsecutiveCount = 10(past the old cap of 6) still continues../gradlew testFullDebugUnitTest --tests "com.noop.ble.BackfillContinuationTest"→ 22 passed, 0 failed. The Swift twin mirrors the same two changes (see caveat below re: running it).Verification / caveats
24is TUNABLE and needs on-strap validation before merge — BLE offload behavior cannot be CI- or Linux-tested. It's sized to drain the observed field backlog in one connection; a real deep-backlog drain on hardware should confirm it caught up in a single session without the radio being monopolised.Strand/BLE/BLEManager.swift+StrandTests/BackfillContinuationTests.swift) — not covered byswift-packagesCI (app-build.ymlis disabled), so it needs anxcodebuild/app-build.ymlrun to confirm it compiles and the two updated tests pass. The Kotlin half compiled + tested clean on Linux.Alternative considered (not taken)
A more principled fix is to reset the consecutive counter on every productive pass (only count unproductive re-kicks toward the cap), which removes the "well-behaved deep backlog" from the cap's scope entirely and keeps the backstop tight. It was left out here because it touches the carefully-guarded
shouldAutoContinuepredicate (with its #928/#1012/#451 interactions) and is riskier to land; the constant bump is minimal and reviewable. Happy to follow up with the reset-on-progress version if preferred.