Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix crash in dispatch queue assertion with background tasks #7510

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -84,7 +84,7 @@ final class AddressCacheTracker: @unchecked Sendable {
timer = nil
}

func updateEndpoints(completionHandler: (@Sendable (Result<Bool, Error>) -> Void)? = nil) -> Cancellable {
func updateEndpoints(completionHandler: ((sending Result<Bool, Error>) -> Void)? = nil) -> Cancellable {
let operation = ResultBlockOperation<Bool> { finish -> Cancellable in
guard self.nextScheduleDate() <= Date() else {
finish(.success(false))
Expand Down
31 changes: 11 additions & 20 deletions ios/MullvadVPN/AppDelegate.swift
Original file line number Diff line number Diff line change
Expand Up @@ -277,15 +277,14 @@ class AppDelegate: UIResponder, UIApplicationDelegate, UNUserNotificationCenterD
private func registerAppRefreshTask() {
let isRegistered = BGTaskScheduler.shared.register(
forTaskWithIdentifier: BackgroundTask.appRefresh.identifier,
using: nil
using: .main
) { [self] task in
nonisolated(unsafe) let nonisolatedTask = task

let handle = relayCacheTracker.updateRelays { result in
nonisolatedTask.setTaskCompleted(success: result.isSuccess)
task.setTaskCompleted(success: result.isSuccess)
}

nonisolatedTask.expirationHandler = {
task.expirationHandler = {
handle.cancel()
}

Expand All @@ -302,18 +301,14 @@ class AppDelegate: UIResponder, UIApplicationDelegate, UNUserNotificationCenterD
private func registerKeyRotationTask() {
let isRegistered = BGTaskScheduler.shared.register(
forTaskWithIdentifier: BackgroundTask.privateKeyRotation.identifier,
using: nil
using: .main
) { [self] task in
nonisolated(unsafe) let nonisolatedTask = task
let handle = tunnelManager.rotatePrivateKey { [self] error in
Task { @MainActor in
scheduleKeyRotationTask()

nonisolatedTask.setTaskCompleted(success: error == nil)
}
scheduleKeyRotationTask()
task.setTaskCompleted(success: error == nil)
}

nonisolatedTask.expirationHandler = {
task.expirationHandler = {
handle.cancel()
}
}
Expand All @@ -328,18 +323,14 @@ class AppDelegate: UIResponder, UIApplicationDelegate, UNUserNotificationCenterD
private func registerAddressCacheUpdateTask() {
let isRegistered = BGTaskScheduler.shared.register(
forTaskWithIdentifier: BackgroundTask.addressCacheUpdate.identifier,
using: nil
using: .main
) { [self] task in
nonisolated(unsafe) let nonisolatedTask = task

let handle = addressCacheTracker.updateEndpoints { [self] result in
Task { @MainActor in
scheduleAddressCacheUpdateTask()
nonisolatedTask.setTaskCompleted(success: result.isSuccess)
}
scheduleAddressCacheUpdateTask()
task.setTaskCompleted(success: result.isSuccess)
}

nonisolatedTask.expirationHandler = {
task.expirationHandler = {
handle.cancel()
}
}
Expand Down
4 changes: 2 additions & 2 deletions ios/MullvadVPN/RelayCacheTracker/RelayCacheTracker.swift
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ import UIKit
protocol RelayCacheTrackerProtocol: Sendable {
func startPeriodicUpdates()
func stopPeriodicUpdates()
func updateRelays(completionHandler: (@Sendable (Result<RelaysFetchResult, Error>) -> Void)?) -> Cancellable
func updateRelays(completionHandler: ((sending Result<RelaysFetchResult, Error>) -> Void)?) -> Cancellable
func getCachedRelays() throws -> CachedRelays
func getNextUpdateDate() -> Date
func addObserver(_ observer: RelayCacheTrackerObserver)
Expand Down Expand Up @@ -164,7 +164,7 @@ final class RelayCacheTracker: RelayCacheTrackerProtocol, @unchecked Sendable {
timerSource = nil
}

func updateRelays(completionHandler: (@Sendable (Result<RelaysFetchResult, Error>) -> Void)? = nil)
func updateRelays(completionHandler: ((sending Result<RelaysFetchResult, Error>) -> Void)? = nil)
-> Cancellable {
let operation = ResultBlockOperation<RelaysFetchResult> { finish in
let cachedRelays = try? self.getCachedRelays()
Expand Down
24 changes: 3 additions & 21 deletions ios/MullvadVPN/TunnelManager/BackgroundTaskProvider.swift
Original file line number Diff line number Diff line change
Expand Up @@ -14,18 +14,10 @@ import UIKit
@available(iOSApplicationExtension, unavailable)
public protocol BackgroundTaskProviding: Sendable {
var backgroundTimeRemaining: TimeInterval { get }
#if compiler(>=6)
nonisolated
func beginBackgroundTask(
nonisolated func beginBackgroundTask(
withName taskName: String?,
expirationHandler handler: (@MainActor @Sendable () -> Void)?
) -> UIBackgroundTaskIdentifier
#else
func beginBackgroundTask(
withName taskName: String?,
expirationHandler handler: (() -> Void)?
) -> UIBackgroundTaskIdentifier
#endif

func endBackgroundTask(_ identifier: UIBackgroundTaskIdentifier)
}
Expand All @@ -40,22 +32,12 @@ public final class BackgroundTaskProvider: BackgroundTaskProviding {
self.application = application
}

#if compiler(>=6)
nonisolated
public func beginBackgroundTask(
withName taskName: String?,
expirationHandler handler: (@MainActor @Sendable () -> Void)?
) -> UIBackgroundTaskIdentifier {
application.beginBackgroundTask(withName: taskName, expirationHandler: handler)
}
#else
public func beginBackgroundTask(
nonisolated public func beginBackgroundTask(
withName taskName: String?,
expirationHandler handler: (() -> Void)?
expirationHandler handler: (@MainActor @Sendable () -> Void)? = nil
) -> UIBackgroundTaskIdentifier {
application.beginBackgroundTask(withName: taskName, expirationHandler: handler)
}
#endif

public func endBackgroundTask(_ identifier: UIBackgroundTaskIdentifier) {
application.endBackgroundTask(identifier)
Expand Down
66 changes: 30 additions & 36 deletions ios/MullvadVPN/TunnelManager/TunnelManager.swift
Original file line number Diff line number Diff line change
Expand Up @@ -218,29 +218,25 @@ final class TunnelManager: StorePaymentObserver, @unchecked Sendable {
}

func startTunnel(completionHandler: ((Error?) -> Void)? = nil) {
nonisolated(unsafe) let nonisolatedCompletionHandler = completionHandler

let operation = StartTunnelOperation(
dispatchQueue: internalQueue,
interactor: TunnelInteractorProxy(self),
completionHandler: { [weak self] result in
guard let self else { return }
DispatchQueue.main.async {
if let error = result.error {
self.logger.error(
error: error,
message: "Failed to start the tunnel."
)

let tunnelError = StartTunnelError(underlyingError: error)

self.observerList.notify { observer in
observer.tunnelManager(self, didFailWithError: tunnelError)
}
}
if let error = result.error {
self.logger.error(
error: error,
message: "Failed to start the tunnel."
)

let tunnelError = StartTunnelError(underlyingError: error)

nonisolatedCompletionHandler?(result.error)
self.observerList.notify { observer in
observer.tunnelManager(self, didFailWithError: tunnelError)
}
}

completionHandler?(result.error)
}
)

Expand All @@ -255,29 +251,26 @@ final class TunnelManager: StorePaymentObserver, @unchecked Sendable {
}

func stopTunnel(completionHandler: ((Error?) -> Void)? = nil) {
nonisolated(unsafe) let nonisolatedCompletionHandler = completionHandler
let operation = StopTunnelOperation(
dispatchQueue: internalQueue,
interactor: TunnelInteractorProxy(self)
) { [weak self] result in
guard let self else { return }

DispatchQueue.main.async {
if let error = result.error {
self.logger.error(
error: error,
message: "Failed to stop the tunnel."
)
if let error = result.error {
self.logger.error(
error: error,
message: "Failed to stop the tunnel."
)

let tunnelError = StopTunnelError(underlyingError: error)
let tunnelError = StopTunnelError(underlyingError: error)

self.observerList.notify { observer in
observer.tunnelManager(self, didFailWithError: tunnelError)
}
self.observerList.notify { observer in
observer.tunnelManager(self, didFailWithError: tunnelError)
}

nonisolatedCompletionHandler?(result.error)
}

completionHandler?(result.error)
}

operation.addObserver(BackgroundObserver(
Expand Down Expand Up @@ -482,7 +475,7 @@ final class TunnelManager: StorePaymentObserver, @unchecked Sendable {
operationQueue.addOperation(operation)
}

func rotatePrivateKey(completionHandler: @escaping @Sendable (Error?) -> Void) -> Cancellable {
func rotatePrivateKey(completionHandler: @MainActor @escaping @Sendable (Error?) -> Void) -> Cancellable {
let operation = RotateKeyOperation(
dispatchQueue: internalQueue,
interactor: TunnelInteractorProxy(self),
Expand All @@ -492,15 +485,16 @@ final class TunnelManager: StorePaymentObserver, @unchecked Sendable {
operation.completionQueue = .main
operation.completionHandler = { [weak self] result in
guard let self else { return }
MainActor.assumeIsolated {
self.updatePrivateKeyRotationTimer()

updatePrivateKeyRotationTimer()
let error = result.error
if let error {
self.handleRestError(error)
}

let error = result.error
if let error {
handleRestError(error)
completionHandler(error)
}

completionHandler(error)
}

operation.addObserver(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ struct RelayCacheTrackerStub: RelayCacheTrackerProtocol {

func stopPeriodicUpdates() {}

func updateRelays(completionHandler: ((Result<RelaysFetchResult, Error>) -> Void)?) -> Cancellable {
func updateRelays(completionHandler: ((sending Result<RelaysFetchResult, Error>) -> Void)?) -> Cancellable {
AnyCancellable()
}

Expand Down
15 changes: 8 additions & 7 deletions ios/Operations/ResultOperation.swift
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ import Foundation

/// Base class for operations producing result.
open class ResultOperation<Success: Sendable>: AsyncOperation, OutputOperation, @unchecked Sendable {
public typealias CompletionHandler = @Sendable (Result<Success, Error>) -> Void
public typealias CompletionHandler = (sending Result<Success, Error>) -> Void

private let nslock = NSLock()
private var _output: Success?
Expand Down Expand Up @@ -104,7 +104,7 @@ open class ResultOperation<Success: Sendable>: AsyncOperation, OutputOperation,
pendingFinish = true

// Copy completion handler.
let completionHandler = _completionHandler
nonisolated(unsafe) let completionHandler = _completionHandler

// Unset completion handler.
_completionHandler = nil
Expand All @@ -118,8 +118,7 @@ open class ResultOperation<Success: Sendable>: AsyncOperation, OutputOperation,
let completionQueue = _completionQueue
nslock.unlock()

let block: @Sendable () -> Void = {
// Call completion handler.
dispatchAsyncOn(completionQueue) {
completionHandler?(result)

var error: Error?
Expand All @@ -130,11 +129,13 @@ open class ResultOperation<Success: Sendable>: AsyncOperation, OutputOperation,
// Finish operation.
super.finish(error: error)
}
}

if let completionQueue {
completionQueue.async(execute: block)
} else {
private func dispatchAsyncOn(_ queue: DispatchQueue?, _ block: @escaping @Sendable () -> Void) {
guard let queue else {
block()
return
}
queue.async(execute: block)
}
}
Loading