Skip to content

fix: prevent main app from blocking notification extension (take 4) - WPB-23511#4880

Open
samwyndham wants to merge 27 commits into
developfrom
fix/nse-stuck-and-expiring-WPB-23511
Open

fix: prevent main app from blocking notification extension (take 4) - WPB-23511#4880
samwyndham wants to merge 27 commits into
developfrom
fix/nse-stuck-and-expiring-WPB-23511

Conversation

@samwyndham

@samwyndham samwyndham commented Jun 22, 2026

Copy link
Copy Markdown
Contributor
BugWPB-23511 [iOS] NSE is often getting stuck and expiring

Issue

This is yet another attempt to fix issues with expiring activity. The key decisions this time are:

  • From the main app (where UIApplication is available) always use the beginBackgroundTask(expirationHandler:) API instead of the performExpiringActivity(withReason:using:) API. This requires no semaphore and in dummy app found that this worked perfectly fine with 1000 simultaneous tasks vs the 64 tasks of performExpiringActivity(withReason:using:).
  • Deleted the current implementation of withExpiringActivity. I replaced it with a similar method, withBackgroundTask that takes a BackgroundTaskExecuter which is a protocol. In the main app we pass in a AppBackgroundTaskExecuter which uses the beginBackgroundTask(expirationHandler:) API. From the app extensions we currently pass in a PassthroughTaskExecuter which does nothing but perform the task (but could be changed.) The idea is to allow different implementations per target.
  • From tests I found that we must show the notification within a very short time after UNNotificationServiceExtension.serviceExtensionTimeWillExpire is called. Extending background time doesn't help here. However using performExpiringActivity(withReason:using: does appear to give us more time to allow for clean up. So in this implementation once UNNotificationServiceExtension.serviceExtensionTimeWillExpire is called I cancel the ongoing task using performExpiringActivity(withReason:using: to give us extra time for the cancellation to succeed.
  • From the share extension I do something similar which is triggered when the the extension enters the background or the user hits cancel - basically give extra time for the cancellation to succeed.
  • I intentionally didn't abstract cancellation using performExpiringActivity because 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

  • Title contains a reference JIRA issue number like [WPB-XXX].
  • Description is filled and free of optional paragraphs.
  • Adds/updates automated tests.

block: block
)
} else {
try await withBackgroundTask(

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is now always performed but will use a PassthroughTaskExecuter when called from an app extension.

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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 uses PassthroughTaskExecuter for 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.

Comment thread wire-ios-system/Source/BackgroundTasks.swift Outdated
Comment thread wire-ios/Wire-iOS/Sources/AppBackgroundTaskExecuter.swift Outdated
Comment thread wire-ios/Wire-iOS Share Extension/Sources/Content/PostContent.swift Outdated
Comment thread wire-ios-system/Source/BackgroundTasks.swift
@github-actions

github-actions Bot commented Jun 22, 2026

Copy link
Copy Markdown
Contributor

Test Results – WireShareEngine

15 tests   15 ✅  0s ⏱️
 4 suites   0 💤
 1 files     0 ❌

Results for commit 0dd793c.

♻️ This comment has been updated with latest results.

@github-actions

github-actions Bot commented Jun 22, 2026

Copy link
Copy Markdown
Contributor

Test Results – WireDomain

  1 files  141 suites   15s ⏱️
564 tests 564 ✅ 0 💤 0 ❌
565 runs  565 ✅ 0 💤 0 ❌

Results for commit 0dd793c.

♻️ This comment has been updated with latest results.

@github-actions

github-actions Bot commented Jun 22, 2026

Copy link
Copy Markdown
Contributor

Test Results – WireRequestStrategy

968 tests   968 ✅  46s ⏱️
108 suites    0 💤
  1 files      0 ❌

Results for commit 0dd793c.

♻️ This comment has been updated with latest results.

@github-actions

github-actions Bot commented Jun 22, 2026

Copy link
Copy Markdown
Contributor

Test Results – WireDataModel

2 378 tests   2 378 ✅  4m 25s ⏱️
  245 suites      0 💤
    1 files        0 ❌

Results for commit 0dd793c.

♻️ This comment has been updated with latest results.

@github-actions

github-actions Bot commented Jun 22, 2026

Copy link
Copy Markdown
Contributor

Test Results – Wire-iOS

1 908 tests   1 881 ✅  2m 34s ⏱️
  302 suites     27 💤
    1 files        0 ❌

Results for commit 0dd793c.

♻️ This comment has been updated with latest results.

@github-actions

github-actions Bot commented Jun 22, 2026

Copy link
Copy Markdown
Contributor

Test Results – WireSystem

80 tests   77 ✅  1s ⏱️
12 suites   3 💤
 1 files     0 ❌

Results for commit 0dd793c.

♻️ This comment has been updated with latest results.

@github-actions

github-actions Bot commented Jun 22, 2026

Copy link
Copy Markdown
Contributor

Test Results – WireSyncEngine

1 135 tests   1 135 ✅  1m 17s ⏱️
  135 suites      0 💤
    1 files        0 ❌

Results for commit 0dd793c.

♻️ This comment has been updated with latest results.

@sonarqubecloud

Copy link
Copy Markdown

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants