fix(rescore): pace a backgrounded pass in 10-second quanta so it isn't suspended after every night - #2318
Merged
Conversation
…hen a trigger queues behind a running pass
5 tasks
4 tasks
ryanbr
pushed a commit
that referenced
this pull request
Sep 19, 2026
A backgrounded offload that finds a re-score already owed, with nothing running, now defers only while the last pass STARTED less than 30 minutes ago. After that it runs, paced as usual. The owed rule dates from #1538, when a backgrounded pass was killed by the CPU limit every time and retrying on each offload was the livelock. Pacing (#2296, #2318) keeps a background pass under that limit now, but the rule still deferred on any unfinished debt with no end, until a BGProcessingTask or the next foreground. On one phone that left a pass unfinished at 11:25 deferring every offload until the app was opened at 09:26 the next morning: 19 hours, and the night in between was never scored. The start time is recorded only by a pass that is about to work. The deferral path re-marks the debt without touching it, so repeated deferrals cannot keep an old unfinished pass looking recent. Nothing to port: Android has no equivalent deferral or background suspension. By @Iskrata.
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.
What this PR does
Follow-up to #2296 (the pacing from #2280). A backgrounded pass now does 10 seconds of work between rests (
backgroundWorkQuantumSeconds) instead of resting after every night. It rests as long as it worked, still capped at 30 s, so it holds the same ~50% ceiling under iOS's 80%-over-60 s limit.It also logs, once per running pass, when a forced re-score is queued behind it:
Why
A rest is a
Task.sleep, and a backgrounded process that is only sleeping is exactly what iOS suspends, until the next bluetooth wake about ten minutes later. Each night of a pass is milliseconds to a few seconds of CPU. So resting after every night meant a backgrounded pass advanced about one night per wake, and only the CPU limit was being respected.On one phone (Release build, #2293's cache fixes applied):
runEffortRescoreIfNeeded,maxDays4000) took the lock at a 21:34 relaunch and still held it at 11:05 the next morning.guard !computingwithout a word: 26Backfill: refreshing dashboard cachelines and nore-score:line at all.The earlier 21-night passes of 2 h 27 min and 39 min reported on #2280 have the same shape.
The second change is why this took a database pull to find. The log said nothing between "sync done" and "no score". The queued line names the pass that is holding the lock and how long it has held it.
Type of change
How it was tested
RescoreBackgroundPolicyTests: work under a quantum rests zero, a quantum rests its own length, the cap still holds, and the foreground never rests.RescoreBackgroundSchedulerTestspass (34 tests across the two suites).doc_comment_lint.pyis clean.parity_ratchet.py --base upstream/main --offlinereports one error,base authority cannot be reproduced with the current derivation. It reports the same on an unmodifiedupstream/maincheckout, so it isn't from this change.Android: nothing to port. Android has no equivalent background suspension, and there is no Kotlin twin of the pacing.
Checklist
docs/CONTRIBUTING.mdStrand.xcodeproj/) or any secrets/keystores