Actor `assumeIsolated` erroneously crashes when using a dispatch queue as the underlying executor

import Dispatch

actor Example {
    final class DispatchQueueExecutor: SerialExecutor {
        private let queue: DispatchQueue

        init(queue: DispatchQueue) {
            self.queue = queue
        }

        func enqueue(_ job: UnownedJob) {
            self.queue.async {
                job.runSynchronously(on: self.asUnownedSerialExecutor())
            }
        }

        func asUnownedSerialExecutor() -> UnownedSerialExecutor {
            UnownedSerialExecutor(ordinary: self)
        }
    }

    private let executor: DispatchQueueExecutor

    init(queue: DispatchQueue) {
        self.executor = DispatchQueueExecutor(queue: queue)
    }

    func bridge() {
        print("Sadly, I never get my moment.")
    }

    nonisolated var unownedExecutor: UnownedSerialExecutor {
        executor.asUnownedSerialExecutor()
    }
}

let queue = DispatchQueue(label: "Example")
let example = Example(queue: queue)

queue.sync {
    example.assumeIsolated {
        $0.bridge()
    }
}

Fatal error: Incorrect actor executor assumption; Expected same executor as Example.

lldb confirms that it is in fact running in the correct Dispatch queue when assumeIsolated is invoked (and that, if I instead spawn up an intermediary Task and await into the actor that way, that the actor is using the given Dispatch queue for its execution).

Implementing isSameExclusiveExecutionContext makes no difference (whether or not I change it to use UnownedSerialExecutor(complexEquality:)).

This seems like it can only be a bug?

Is there a workaround, to allow me to efficiently (and synchronously) invoke the actor's methods from inside the Dispatch queue?

I'm using Swift 6 (swiftlang-6.0.0.3.300 clang-1600.0.20.10).

2 Likes