fix: prevent main app from blocking notification extension (take 4) - WPB-23511#4880
fix: prevent main app from blocking notification extension (take 4) - WPB-23511#4880samwyndham wants to merge 27 commits into
Conversation
…(eventID:contentHandler:)` is cancelled
…piringActivity(reason:block:)`
…meout:processInfo:)`" This reverts commit 891ff0b.
| block: block | ||
| ) | ||
| } else { | ||
| try await withBackgroundTask( |
There was a problem hiding this comment.
This is now always performed but will use a PassthroughTaskExecuter when called from an app extension.
There was a problem hiding this comment.
Pull request overview
This PR refactors background-time handling across the main app and app extensions to prevent the main app from blocking the Notification Service Extension (NSE), primarily by replacing performExpiringActivity usage in the main app with UIApplication.beginBackgroundTask(...) via an injectable background-task executor.
Changes:
- Introduces
BackgroundTaskExecuter+withBackgroundTask(...), and threads the executor through sync/crypto/session construction. - Adds
AppBackgroundTaskExecuter(main app) and usesPassthroughTaskExecuterfor extensions/tests. - Updates NSE and Share Extension cancellation paths to attempt cleanup under
performExpiringActivity(...).
Reviewed changes
Copilot reviewed 51 out of 51 changed files in this pull request and generated 4 comments.
Show a summary per file
| File | Description |
|---|---|
| WireDomain/Tests/WireDomainTests/Synchronization/PullPendingUpdateEventsSyncV2Tests.swift | Updates SafeCoreCrypto construction for new background task executor dependency. |
| WireDomain/Tests/WireDomainTests/Synchronization/IncrementalSyncV2Tests.swift | Injects PassthroughTaskExecuter into SafeCoreCrypto and IncrementalSyncV2. |
| WireDomain/Tests/WireDomainTests/Synchronization/IncrementalSyncTests.swift | Injects PassthroughTaskExecuter into IncrementalSync. |
| WireDomain/Sources/WireDomain/Synchronization/IncrementalSyncV2.swift | Wraps live sync processing in withBackgroundTask(...) using injected executor. |
| WireDomain/Sources/WireDomain/Synchronization/IncrementalSync.swift | Wraps live sync processing in withBackgroundTask(...) using injected executor. |
| WireDomain/Sources/WireDomain/Notifications/Protocols/NotificationServiceProtocol.swift | Removes protocol (NSE wiring now uses concrete type). |
| WireDomain/Sources/WireDomain/Notifications/NotificationServiceExtension.swift | Removes protocol conformance; adds hasOnGoingTask + async cancel(). |
| WireDomain/Sources/WireDomain/Notifications/Components/NSEUserScope.swift | Threads background task executor into user/client scopes; simplifies processPayload call. |
| WireDomain/Sources/WireDomain/Notifications/Components/NSEFlow.swift | Uses a shared PassthroughTaskExecuter for NSE-scoped components. |
| WireDomain/Sources/WireDomain/Notifications/Components/NSEClientScope.swift | Refactors payload processing tasks/monitoring and injects background task executor into crypto provider. |
| WireDomain/Sources/WireDomain/Components/UserSessionComponent.swift | Adds background task executor dependency and passes it to child components. |
| WireDomain/Sources/WireDomain/Components/ClientSessionComponent.swift | Adds background task executor dependency and injects into sync implementations. |
| wire-ios/Wire-iOS/Sources/AppRootRouter.swift | Adds background task executor dependency for app root construction. |
| wire-ios/Wire-iOS/Sources/AppDelegate.swift | Constructs AppBackgroundTaskExecuter and passes it into session/app wiring. |
| wire-ios/Wire-iOS/Sources/AppBackgroundTaskExecuter.swift | Adds main-app executor using UIApplication.beginBackgroundTask(...). |
| wire-ios/Wire-iOS Share Extension/Sources/Content/PostContent.swift | Adds logging and uses performExpiringActivity(...) to allow cancellation cleanup. |
| wire-ios/Wire Notification Service Extension/NotificationService.swift | Switches to concrete NotificationServiceExtension; cancels ongoing work on expiry with expiring activity + timeout. |
| wire-ios-system/Tests/ExpiringActivityTests.swift | Removes expiring-activity tests (old mechanism removed). |
| wire-ios-system/Source/ExpiringActivity.swift | Removes withExpiringActivity(...) implementation. |
| wire-ios-system/Source/BackgroundTasks.swift | Introduces executor protocol, passthrough executor, and helper withBackgroundTask(...). |
| wire-ios-sync-engine/Tests/Source/UserSession/ZMUserSessionTestsBase.swift | Updates SafeCoreCrypto construction for executor dependency. |
| wire-ios-sync-engine/Tests/Source/UserSession/ZMUserSessionTests_NetworkState.swift | Updates SafeCoreCrypto construction for executor dependency. |
| wire-ios-sync-engine/Tests/Source/Use cases/CheckOneOnOneConversationIsReadyUseCaseTests.swift | Updates SafeCoreCrypto construction for executor dependency. |
| wire-ios-sync-engine/Tests/Source/Synchronization/SyncAgentTests.swift | Injects PassthroughTaskExecuter into SyncAgent. |
| wire-ios-sync-engine/Tests/Source/E2EI/CertificateRevocationListsCheckerTests.swift | Updates SafeCoreCrypto construction for executor dependency. |
| wire-ios-sync-engine/Source/UserSession/ZMUserSession/ZMUserSessionBuilder.swift | Supplies a PassthroughTaskExecuter for builder-created sessions. |
| wire-ios-sync-engine/Source/UserSession/ZMUserSession/ZMUserSession.swift | Threads executor through session/component/sync strategy construction. |
| wire-ios-sync-engine/Source/Synchronization/SyncAgent.swift | Replaces withExpiringActivity(...) with withBackgroundTask(...). |
| wire-ios-sync-engine/Source/Synchronization/StrategyDirectory.swift | Adds executor parameter and passes it into strategy factory. |
| wire-ios-sync-engine/Source/SessionManager/UserSessionLoader.swift | Replaces direct UIApplication.shared background-task manager usage with injected executor. |
| wire-ios-sync-engine/Source/SessionManager/SessionManager.swift | Stores executor and passes it into loaders and related wiring. |
| wire-ios-share-engine/WireShareEngineTests/BaseSharingSessionTests.swift | Injects PassthroughTaskExecuter into strategy directory. |
| wire-ios-share-engine/Sources/StrategyFactory.swift | Adds executor dependency to strategy wiring. |
| wire-ios-share-engine/Sources/SharingSessionLoader.swift | Uses a PassthroughTaskExecuter and injects into crypto + strategies. |
| wire-ios-share-engine/Sources/SharingSession.swift | Uses a PassthroughTaskExecuter and injects into crypto + strategies. |
| wire-ios-request-strategy/Tests/Helpers/ProteusClientSimulator.swift | Updates CoreCryptoProvider init for executor dependency. |
| wire-ios-request-strategy/Tests/Helpers/MessagingTestBase.swift | Updates CoreCryptoProvider init for executor dependency. |
| wire-ios-request-strategy/Sources/Message Sending/MessageSenderTests.swift | Injects PassthroughTaskExecuter into MessageSender construction in tests. |
| wire-ios-request-strategy/Sources/Message Sending/MessageSender.swift | Wraps send/broadcast operations with withBackgroundTask(...) using injected executor. |
| wire-ios-request-strategy/Sources/E2EIdentity/E2EIKeyPackageRotatorTests.swift | Updates SafeCoreCrypto construction for executor dependency. |
| wire-ios-data-model/Tests/UseCases/IsUserE2EICertifiedUseCaseTests.swift | Updates SafeCoreCrypto construction for executor dependency. |
| wire-ios-data-model/Tests/Proteus/ProteusServiceTests.swift | Updates SafeCoreCrypto construction for executor dependency. |
| wire-ios-data-model/Tests/MLS/MLSServiceTests.swift | Updates SafeCoreCrypto construction for executor dependency. |
| wire-ios-data-model/Tests/MLS/MLSEncryptionServiceTests.swift | Updates SafeCoreCrypto construction for executor dependency. |
| wire-ios-data-model/Tests/MLS/MLSActionExecutorTests.swift | Updates SafeCoreCrypto construction for executor dependency. |
| wire-ios-data-model/Tests/E2EIdentity/E2EIVerificationStatusServiceTests.swift | Updates SafeCoreCrypto construction for executor dependency. |
| wire-ios-data-model/Tests/E2EIdentity/E2EIServiceTests.swift | Updates SafeCoreCrypto construction for executor dependency. |
| wire-ios-data-model/Support/Sources/CoreCryptoMocksEnvelope.swift | Updates SafeCoreCrypto construction for executor dependency. |
| wire-ios-data-model/Source/Utilis/BackgroundTaskManager.swift | Removes old UIApplication background task abstraction. |
| wire-ios-data-model/Source/Core Crypto/SafeCoreCrypto.swift | Moves transaction wrapping to withBackgroundTask(...). |
| wire-ios-data-model/Source/Core Crypto/CoreCryptoProvider.swift | Replaces optional background task manager with required executor injection. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
Test Results – WireShareEngine15 tests 15 ✅ 0s ⏱️ Results for commit 0dd793c. ♻️ This comment has been updated with latest results. |
Test Results – WireDomain 1 files 141 suites 15s ⏱️ Results for commit 0dd793c. ♻️ This comment has been updated with latest results. |
Test Results – WireRequestStrategy968 tests 968 ✅ 46s ⏱️ Results for commit 0dd793c. ♻️ This comment has been updated with latest results. |
Test Results – WireDataModel2 378 tests 2 378 ✅ 4m 25s ⏱️ Results for commit 0dd793c. ♻️ This comment has been updated with latest results. |
Test Results – Wire-iOS1 908 tests 1 881 ✅ 2m 34s ⏱️ Results for commit 0dd793c. ♻️ This comment has been updated with latest results. |
Test Results – WireSystem80 tests 77 ✅ 1s ⏱️ Results for commit 0dd793c. ♻️ This comment has been updated with latest results. |
Test Results – WireSyncEngine1 135 tests 1 135 ✅ 1m 17s ⏱️ Results for commit 0dd793c. ♻️ This comment has been updated with latest results. |
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
|



Issue
This is yet another attempt to fix issues with expiring activity. The key decisions this time are:
performExpiringActivity(withReason:using:).withExpiringActivity. I replaced it with a similar method,withBackgroundTaskthat takes aBackgroundTaskExecuterwhich is a protocol. In the main app we pass in aAppBackgroundTaskExecuterwhich usesthe beginBackgroundTask(expirationHandler:)API. From the app extensions we currently pass in aPassthroughTaskExecuterwhich does nothing but perform the task (but could be changed.) The idea is to allow different implementations per target.UNNotificationServiceExtension.serviceExtensionTimeWillExpireis called. Extending background time doesn't help here. However usingperformExpiringActivity(withReason:using:does appear to give us more time to allow for clean up. So in this implementation onceUNNotificationServiceExtension.serviceExtensionTimeWillExpireis called I cancel the ongoing task usingperformExpiringActivity(withReason:using:to give us extra time for the cancellation to succeed.performExpiringActivitybecause I feel the necessary Semaphore usage is a hack so we shouldn't encourage it.Testing
@johnxnguyen Any ideas here? The only thing I can think of is to monitor logging while the app is being used.
Checklist
[WPB-XXX].