From dafda65cbe95abcd4e486bb8bcac885c7d61d0b4 Mon Sep 17 00:00:00 2001 From: Stefan Ceriu Date: Fri, 2 Oct 2026 17:31:46 +0300 Subject: [PATCH] Start the app coordinator from background tasks A background launch doesn't always connect a scene, so nothing started the session restore and both background app refresh and search backfill gave up without a session. Store optimizations are skipped while in the background, as vacuuming can outlast the task and get the app killed before the remaining migrations run. --- .../Sources/Application/AppCoordinator.swift | 20 ++++++++++++++----- 1 file changed, 15 insertions(+), 5 deletions(-) diff --git a/ElementX/Sources/Application/AppCoordinator.swift b/ElementX/Sources/Application/AppCoordinator.swift index bb9d116b8..3b9820f78 100644 --- a/ElementX/Sources/Application/AppCoordinator.swift +++ b/ElementX/Sources/Application/AppCoordinator.swift @@ -213,7 +213,7 @@ class AppCoordinator: AppCoordinatorProtocol, AuthenticationFlowCoordinatorDeleg func start() { guard stateMachine.state == .initial else { - MXLog.error("Received a start request when already started") + MXLog.info("Received a start request when already started") return } @@ -486,9 +486,12 @@ class AppCoordinator: AppCoordinatorProtocol, AuthenticationFlowCoordinatorDeleg MXLog.info("Migrating user session from \(oldVersion)") - MXLog.info("Performing client store optimizations.") - await userSession.clientProxy.optimizeStores() - MXLog.info("Finished optimizing client stores.") + // Vacuuming can outlast a background task, getting the app killed before the migrations below run. + if appMediator.appState != .background { + MXLog.info("Performing client store optimizations.") + await userSession.clientProxy.optimizeStores() + MXLog.info("Finished optimizing client stores.") + } if oldVersion < Version(1, 6, 0) { MXLog.info("Migrating to v1.6.0, marking identity confirmation onboarding as ran.") @@ -1348,8 +1351,13 @@ private extension AppCoordinator { } } - /// When iOS relaunches a terminated app for a background task, the session is still being restored asynchronously. + /// When iOS relaunches a terminated app for a background task, the session needs restoring before the task can run. private func waitForSessionRestore() async { + // A background launch doesn't necessarily connect a scene, which is what normally calls `start()`. + if stateMachine.state == .initial, userSessionStore.hasSessions { + start() + } + for await isSessionRestored in isSessionRestoredSubject.timeout(.seconds(5), scheduler: DispatchQueue.main).values where isSessionRestored { return } @@ -1394,6 +1402,8 @@ private extension AppCoordinator { func handleSearchBackfill(_ task: BGProcessingTask) async { MXLog.info("Started search backfill task") + await waitForSessionRestore() + guard let clientProxy = userSession?.clientProxy else { task.setTaskCompleted(success: false) return