refactor: background work reads app state in its own loop - #29660
Open
chrisnojima wants to merge 3 commits into
Open
chrisnojima wants to merge 3 commits into
chrisnojima wants to merge 3 commits into
Conversation
Each background subsystem now watches MobileAppState in a loop it owns instead of piggybacking on a shared notification path, so pauses and resumes are ordered against that subsystem's own start/stop. - bgticker: Stop() closes a done channel, so the tick goroutine can no longer block forever sending to a reader that has gone away. Other suites' goroutine-leak assertions depend on this. - avatars: the background flusher arms its first wake-up before its goroutine runs, so the seed state can't be missed. - ephemeral: the keygen loop seeds from the current state and reacts to transitions itself. - chat convloader: suspension is tracked per run; app-state suspends carry across runs and stale wake-ups are dropped. - chat archive registry: resumes launch a job once, ended runs touch nothing, and pauses flush. - chat search indexer: the sync loop only syncs in FOREGROUND and cancels an active sync on background. - kbfs: paused loops (folder_block_manager, search indexer) exit on shutdown, and the prefetcher does a single pause-wait so its pause reasons no longer undo each other.
Collaborator
I'd prefer if there were independent PRs for the bugfixes and the arch change after that |
- search indexer: a sync skipped outside FOREGROUND (including the one-shot start sync) runs on the next change into FOREGROUND instead of being lost. - convloader: an app-state change that doesn't suspend keeps waiting out the load delay, so a retry keeps its full backoff. - archive registry: Set returns an error when it pauses a job registering outside the foreground, so ArchiveChat stops at once. Stop drops the run's in-memory history so the next Start, possibly for another user, reads its own. - kbfs: note that forced quota reclamations block while QR is paused.
A job launched before a logout could register or report progress after the next user's Start and land in that user's history. Set now takes the uid the job runs as, and refuses (and stops) a job for any user but the current one.
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.
Split out of #29637. Based on
master, independent of the app-state stack.Why
Six subsystems run background loops that are supposed to pause when the app is not in the foreground. Each implemented that independently, and each got some part of it wrong in the same way: they assume the app starts in FOREGROUND, and they read the app state at a moment that can miss a transition. Gathering them into one change makes the shared shape reviewable once instead of six times.
The shape, applied per subsystem: seed from the current app state instead of assuming FOREGROUND, arm
NextUpdatebefore doing work so a transition landing mid-flight is not lost, and re-read the state on wake rather than assuming what it changed to.Master bugs this fixes
BgTickerleaks its goroutine.Stop()only stops the ticker.tick()still sits infor c := range t.ticker.C— which never ends, becauseStop()does not close the channel — and blocks forever ont.c <- conce the 1-slot buffer fills and no one is reading. The goroutine-leak assertions in the suites below depend on this being fixed, which is why it is here.NextUpdateis armed inside the goroutine, so a change betweenstart()and the goroutine's first scheduling is missed entirely.convloader.go:178andleveldb_cleaner.go:134both hardcodestate := MobileAppState_FOREGROUNDas the seed.Indexer.Shutdown.Stop.Stopkeeps the in-memory job history andinited, so the next user'sStartresumes the previous user's paused jobs under the new uid and writes that history to the new user's db key. A job launched before the switch can also register or report progress into the new user's history.What this changes
Per subsystem:
BgTicker.Stop()closes adonechannel; the avatars flusher is armed before its goroutine runs; the ephemeral keygen loop is seeded from the current state and made testable; the conv loader reads app state inside its loop,Startwaits for the previous run, andsuspendInAppStatebecomesstate == BACKGROUNDso INACTIVE keeps loading; the archive registry runs from one loop and pauses by app-state level; the chat search indexer starts no sync outside FOREGROUND; and the KBFS loops watch shutdown with the prefetcher's two waits collapsed into one.Follow-ups within those:
Setreturns an error when it pauses a job registering outside the foreground, soArchiveChatstops at once instead of carrying on with a canceled context.Stopdrops the run's history once flushed, so the nextStartreads its own user's.Settakes the job's uid and refuses (and stops) a job for any user but the current one.Ordering
Independent of the app-state stack at compile level — all of it uses only
MobileAppState.State()/NextUpdate(), unchanged on master. No file overlap with the stack at all.But it should land before or with #29651, which makes Android boot in BACKGROUND. Until these loops seed from the live state, they wake at boot, read BACKGROUND and suspend — on Android the conv loader would come up suspended.
Verification
go build,go vet,gofmtclean,golangci-lint --new-from-rev master0 issues. All new tests pass under-race, and each follow-up's regression test fails with its fix reverted.Not verified here: tests calling
kbtestsignup need kbweb onlocalhost:3000, unavailable in this environment — 5 inconvloader_test.go,TestUploadAvatar,TestKeygenIfNeeded. They are pre-existing and untouched.TestArchiveScenarioReplayandTestSyncLoopScenarioReplayare deliberately omitted — they needlifecycle/lifecycletest, which lands in #29651. Add them once both are in.