I trying to migrate a WatchConnectivity App to Swift6 and I am stuck migrating the nonisolated
delegate callback for didReceiveMessageData
.
The code runs on swift 5 with SWIFT_STRICT_CONCURRENCY = complete
.
However when I switch to swift 6 the code crashes with the debug message. Incorrect actor executor assumption
.
This is the delegate that works fine before changing the compiler to swift 6.
import Foundation
@preconcurrency import WatchConnectivity
actor ConnectivityManager: NSObject, WCSessionDelegate {
private var session: WCSession = .default
override init() {
super.init()
self.session.delegate = self
self.session.activate()
}
// all the other WCSessionDelegate functions
// ...
nonisolated func session(
_ session: WCSession,
didReceiveMessageData messageData: Data,
replyHandler: @escaping @Sendable (Data) -> Void
) {
// Simplified for the question ..
Task {
let data = try await asyncWork()
replyHandler(data)
}
}
}
}
After enabling SWIFT_STRICT_CONCURRENCY
. The compiler originally warned me about a potential data race that will be an error in Swift 6.
Passing closure as a 'sending' parameter risks causing data races between code in the current task and concurrent execution of the closure.
Closure captures 'replyHandler' which is accessible to code in the current task
This warning is solvable by adding the @Sendable
attribute to the replyHandler
signature and @preconcurrency
to the WatchConnectivity
import. (Reflected in the code example).
Compiler is happy. Everything compiles and works.
But then after switching the compiler Swift Language Version to Swift 6. The app crashes after trying to access the replyHandler
and outputs Incorrect actor executor assumption
.
I have also unsuccessfully tried changing ConnectivityManager to a @MainActor
class and/or only calling replyHandler
from the main Thread.
await MainActor.run {
replyHandler(data)
}
After further testing I found that it seems to be related to using @preconcurrency
with the WatchConnectivity
import.
This crashes every time (with swift 6):
import Foundation
@preconcurrency import WatchConnectivity
actor ConnectivityManager: NSObject, WCSessionDelegate {
// ...
nonisolated func session(
_ session: WCSession,
didReceiveMessageData messageData: Data,
replyHandler: @escaping (Data) -> Void
) {
replyHandler(data)
}
}
}
This never does:
import Foundation
import WatchConnectivity
actor ConnectivityManager: NSObject, WCSessionDelegate {
// ...
nonisolated func session(
_ session: WCSession,
didReceiveMessageData messageData: Data,
replyHandler: @escaping (Data) -> Void
) {
replyHandler(data)
}
}
}
(However both work with swift 5)
Backtrace:
Incorrect actor executor assumption
(lldb) bt
* thread #12, stop reason = signal SIGABRT
* frame #0: 0x2937ec94 libsystem_kernel.dylib`__pthread_kill + 8
frame #1: 0x29a5f3c8 libsystem_pthread.dylib`pthread_kill + 208
frame #2: 0x222fe53c libsystem_c.dylib`abort + 124
frame #3: 0x24581c70 libswift_Concurrency.dylib`swift::swift_Concurrency_fatalErrorv(unsigned int, char const*, char*) + 28
frame #4: 0x24581c8c libswift_Concurrency.dylib`swift::swift_Concurrency_fatalError(unsigned int, char const*, ...) + 28
frame #5: 0x24581904 libswift_Concurrency.dylib`swift_task_checkIsolated + 152
frame #6: 0x2457ebd8 libswift_Concurrency.dylib`swift_task_isCurrentExecutorImpl(swift::SerialExecutorRef) + 280
frame #7: 0x033c2440 tricorder Watch App.debug.dylib`closure #1 in closure #2 in ConnectivityManager.sendMessageData(data=15 bytes, continuation=(canary = 0x0000000014d0bf90)) at <stdin>:0
frame #8: 0x033c2a30 tricorder Watch App.debug.dylib`thunk for @escaping @callee_guaranteed (@guaranteed Data) -> () at <compiler-generated>:0
frame #9: 0x592c14e4 WatchConnectivity`__61-[WCSession onqueue_handleResponseData:record:withPairingID:]_block_invoke + 180
frame #10: 0x206f6978 Foundation`__NSINDEXSET_IS_CALLING_OUT_TO_A_BOOL_BLOCK__ + 16
frame #11: 0x20746384 Foundation`-[NSBlockOperation main] + 100
frame #12: 0x206ff6b8 Foundation`__NSOPERATION_IS_INVOKING_MAIN__ + 12
frame #13: 0x20706b9c Foundation`-[NSOperation start] + 620
frame #14: 0x2071e56c Foundation`__NSOPERATIONQUEUE_IS_STARTING_AN_OPERATION__ + 12
frame #15: 0x2071a03c Foundation`__NSOQSchedule_f + 168
frame #16: 0x03234aec libdispatch.dylib`_dispatch_call_block_and_release + 24
frame #17: 0x03236404 libdispatch.dylib`_dispatch_client_callout + 16
frame #18: 0x032390b8 libdispatch.dylib`_dispatch_continuation_pop + 564
frame #19: 0x03238540 libdispatch.dylib`_dispatch_async_redirect_invoke + 652
frame #20: 0x03247b44 libdispatch.dylib`_dispatch_root_queue_drain + 336
frame #21: 0x03248468 libdispatch.dylib`_dispatch_worker_thread2 + 192
frame #22: 0x29a59780 libsystem_pthread.dylib`_pthread_wqthread + 220
Any ideas what I am doing wrong?
Edit 2024.11.17: Fixed some wording.
Edit 2024.11.17: Added backtrace.