fix(mobile): run iOS bg task phases in parallel#28293
Conversation
onIosUpload runs sync local, sync remote, hash and handle backup sequentially. on the bg refresh task path that's a 20s budget from iOS, and sync + hash usually eat all of it before backup gets a turn to enqueue any candidates. these phases don't actually depend on each other. local + remote sync touch different tables. hash works off whatever's already in drift. handle backup reads candidates and just enqueues to URLSession bg. anything one phase produces in this fire shows up to the others on the next fire, and server-side dedup catches the rare race where backup enqueues something sync remote was about to mark as already uploaded. so this runs all four concurrently via Future.wait, with hash getting the full maxSeconds-1 budget instead of a fixed 5s. outer budget timeout still caps everything before iOS expires. second small change: getAssetsToHash orders by createdAt DESC instead of id ASC to match getCandidates. when hash runs inside a refresh fire it processes recent photos first.
There was a problem hiding this comment.
Pull request overview
This PR updates the mobile background upload flow on iOS to better utilize the limited BGAppRefresh execution window by running formerly sequential “phases” concurrently, and tweaks hashing order to prioritize recent assets.
Changes:
- Run iOS background task phases (sync local, sync remote, hashing, backup enqueue) in parallel via
Future.wait, with budget-based timeouts. - Increase refresh-mode hash time budget to roughly the full iOS refresh window instead of a fixed 5s.
- Change
getAssetsToHashordering tocreatedAt DESCto process newer assets first (aligning with backup candidate ordering).
Reviewed changes
Copilot reviewed 2 out of 2 changed files in this pull request and generated 1 comment.
| File | Description |
|---|---|
| mobile/lib/infrastructure/repositories/local_album.repository.dart | Hash-queue query now orders un-hashed album assets by createdAt DESC. |
| mobile/lib/domain/services/background_worker.service.dart | iOS background upload now runs sync/hash/backup concurrently under a shared time budget. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| Future<List<LocalAsset>> getAssetsToHash(String albumId) { | ||
| final query = | ||
| _db.localAlbumAssetEntity.select().join([ | ||
| innerJoin(_db.localAssetEntity, _db.localAlbumAssetEntity.assetId.equalsExp(_db.localAssetEntity.id)), | ||
| ]) | ||
| ..where(_db.localAlbumAssetEntity.albumId.equals(albumId) & _db.localAssetEntity.checksum.isNull()) | ||
| ..orderBy([OrderingTerm.asc(_db.localAssetEntity.id)]); | ||
| ..orderBy([OrderingTerm.desc(_db.localAssetEntity.createdAt)]); | ||
|
|
||
| return query.map((row) => row.readTable(_db.localAssetEntity).toDto()).get(); |
|
IIRC, we switched to this specific synchronous mechanism because when it was running in parallel, the database locked up very often in the background and caused the foreground to also lock up. Am I remembering this correctly? We will need to spend sometimes running this PR to make sure it doesn't happen |
That is how I remember it as well, yes. But we've bumped our dependencies and things have changed a lot so it might not be bad idea to revisit this now |
|
4 days running on my iphone, last 2 days i didnt even open the app. around 7k photos in the server from 180 fires. no fg wedge when i do open it. phases were already in their own isolates via |
Description
onIosUploadruns sync local → sync remote → hash → handle backup sequentially. on the bg refresh task path that's a 20s budget from iOS, and sync + hash usually eat all of it before backup gets a turn to enqueue any candidates. so the fire ends withsetTaskCompleted(success: false)and zero new uploads.the four phases don't actually depend on each other. local + remote sync touch different tables. hash works off whatever's already in drift. handle backup reads candidates from drift and just enqueues to URLSession bg. anything one phase produces in this fire shows up to the others on the next fire, and server-side dedup catches the rare race where backup enqueues something sync remote was about to mark as already-uploaded.
so this runs all four concurrently via
Future.wait, with hash getting the fullmaxSeconds-1budget instead of a fixed 5s. outer budget timeout still caps everything before iOS expires.second small change:
getAssetsToHashorders bycreatedAt DESCinstead ofid ASCto matchgetCandidates. when hash runs inside a refresh fire it processes recent photos first, so the next fire's queue gets what the user just took rather than random old assets.tested over ~3 hours on iphone 15 pro with iOS 26.5 against a fresh server. 12 of 14 refresh fires reached the 20s budget cleanly, vs essentially zero useful enqueues before. server side: 330 new asset rows created from bg-only activity in those 3 hours, phone wasn't in foreground.
Please describe to which degree, if any, an LLM was used in creating this pull request.
None.