Crash when running in Swift 6 language mode

Hi,

Over the past few days, I have been looking into getting my apps work in Swift 6 language mode. This went mostly great! However, one of my apps was crashing in Swift 6 lanaguage mode, while it runs fine in Swift 5 language mode. After doing some digging, I found the culprit. In the AppKit SDK shipping with Xcode 16, NSDocument is marked as @MainActor. However, some of its functions can be called from another thread, such as the autosavesInPlace, which was causing problems in my case. This function is not marked as nonisolated, so it will incorrectly inherit the MainActor from NSDocument (FB13874012). In Swift 6 language mode, the code will correctly crash. In Swift 5 language mode, it won't, even though it is called from another thread. My question is, why is that? I would expect the code to crash in both cases.

@MainActor
class FileDocument: NSDocument {

    ...

    // Incorrect
    override class var autosavesInPlace: Bool {
        true
    }

 // |
 // |
 // v

    // Correct
    override nonisolated class var autosavesInPlace: Bool {
        true
    }
}

Stack trace:

* thread #5, queue = 'com.apple.root.default-qos', stop reason = EXC_BREAKPOINT (code=1, subcode=0x1004f8a5c)
  * frame #0: 0x00000001004f8a5c libdispatch.dylib`_dispatch_assert_queue_fail + 120
    frame #1: 0x00000001004f89e4 libdispatch.dylib`dispatch_assert_queue + 196
    frame #2: 0x0000000275bd293c libswift_Concurrency.dylib`swift_task_isCurrentExecutorImpl(swift::SerialExecutorRef) + 280
    frame #3: 0x0000000275b78588 libswift_Concurrency.dylib`Swift._checkExpectedExecutor(_filenameStart: Builtin.RawPointer, _filenameLength: Builtin.Word, _filenameIsASCII: Builtin.Int1, _line: Builtin.Word, _executor: Builtin.Executor) -> () + 60
    frame #4: 0x0000000108922788 MyApp.debug.dylib`@objc static FileDocument.autosavesInPlace.getter at <compiler-generated>:0
    frame #5: 0x00000001a2f7d780 AppKit`-[NSDocument(NSDocument_Versioning) _preserveContentsIfNecessaryAfterWriting:toURL:forSaveOperation:version:error:] + 92
    frame #6: 0x00000001a2fe6d1c AppKit`__85-[NSDocument(NSDocumentSaving) _saveToURL:ofType:forSaveOperation:completionHandler:]_block_invoke_2.398 + 116
    frame #7: 0x00000001004f4bd4 libdispatch.dylib`_dispatch_call_block_and_release + 32
    frame #8: 0x00000001004f6914 libdispatch.dylib`_dispatch_client_callout + 20
    frame #9: 0x00000001004f9878 libdispatch.dylib`_dispatch_queue_override_invoke + 1076
    frame #10: 0x000000010050e9f8 libdispatch.dylib`_dispatch_root_queue_drain + 404
    frame #11: 0x000000010050f604 libdispatch.dylib`_dispatch_worker_thread2 + 188
    frame #12: 0x00000001005910c4 libsystem_pthread.dylib`_pthread_wqthread + 228
3 Likes

Swift language mode 6 has more strict isolation runtime checks and such incorrect threading is now going to cause preemptive crashes rather than just silently running on the wrong thread.

You can see the assertion failing here: dispatch_assert_queue, called from: checkExpectedExecutor

In swift 5 mode this is allowed not to crash and proceed with the unsafe execution.

I’ll have a look at the exact case to make sure the error/crash is doing the right expected thing though.

The way you handle it via making the witness nonisolated is the right way to work around it until the SDK fixes its annotation issue :+1:

1 Like

Thank you for the explanation!

1 Like