mirror of
https://github.com/apple/swift.git
synced 2025-12-21 12:14:44 +01:00
Through various means, it is possible for a synchronous actor-isolated function to escape to another concurrency domain and be called from outside the actor. The problem existed previously, but has become far easier to trigger now that `@escaping` closures and local functions can be actor-isolated. Introduce runtime detection of such data races, where a synchronous actor-isolated function ends up being called from the wrong executor. Do this by emitting an executor check in actor-isolated synchronous functions, where we query the executor in thread-local storage and ensure that it is what we expect. If it isn't, the runtime complains. The runtime's complaints can be controlled with the environment variable `SWIFT_UNEXPECTED_EXECUTOR_LOG_LEVEL`: 0 - disable checking 1 - warn when a data race is detected 2 - error and abort when a data race is detected At an implementation level, this introduces a new concurrency runtime entry point `_checkExpectedExecutor` that checks the given executor (on which the function should always have been called) against the executor on which is called (which is in thread-local storage). There is a special carve-out here for `@MainActor` code, where we check against the OS's notion of "main thread" as well, so that `@MainActor` code can be called via (e.g.) the Dispatch library's `DispatchQueue.main.async`. The new SIL instruction `extract_executor` performs the lowering of an actor down to its executor, which is implicit in the `hop_to_executor` instruction. Extend the LowerHopToExecutor pass to perform said lowering.
67 lines
1.5 KiB
Swift
67 lines
1.5 KiB
Swift
// RUN: %target-run-simple-swift(-Xfrontend -enable-experimental-concurrency %import-libdispatch -parse-as-library) > %t.log 2>&1
|
|
// RUN: %FileCheck %s < %t.log
|
|
|
|
// REQUIRES: executable_test
|
|
// REQUIRES: concurrency
|
|
// REQUIRES: libdispatch
|
|
|
|
// rdar://76038845
|
|
// UNSUPPORTED: use_os_stdlib
|
|
|
|
import _Concurrency
|
|
import Dispatch
|
|
import Darwin
|
|
|
|
@MainActor func onMainActor() {
|
|
print("I'm on the main actor!")
|
|
}
|
|
|
|
func promiseMainThread(_ fn: @escaping @MainActor () -> Void) -> (() -> Void) {
|
|
typealias Fn = () -> Void
|
|
return unsafeBitCast(fn, to: Fn.self)
|
|
}
|
|
|
|
func launchTask(_ fn: @escaping () -> Void) {
|
|
if #available(macOS 10.10, iOS 7.0, watchOS 2.0, tvOS 8.0, *) {
|
|
DispatchQueue.global().async {
|
|
fn()
|
|
}
|
|
}
|
|
}
|
|
|
|
@MainActor func launchFromMainThread() {
|
|
launchTask(promiseMainThread(onMainActor))
|
|
}
|
|
|
|
actor MyActor {
|
|
var counter = 0
|
|
|
|
func onMyActor() {
|
|
counter = counter + 1
|
|
}
|
|
|
|
func getTaskOnMyActor() -> (() -> Void) {
|
|
return {
|
|
self.onMyActor()
|
|
}
|
|
}
|
|
}
|
|
|
|
@main
|
|
struct Runner {
|
|
@MainActor static func main() async {
|
|
print("Launching a main-actor task")
|
|
// CHECK: warning: data race detected: @MainActor function at main/data_race_detection.swift:15 was not called on the main thread
|
|
launchFromMainThread()
|
|
sleep(1)
|
|
|
|
let actor = MyActor()
|
|
let actorFn = await actor.getTaskOnMyActor()
|
|
print("Launching an actor-instance task")
|
|
// CHECK: warning: data race detected: actor-isolated function at main/data_race_detection.swift:44 was not called on the same actor
|
|
launchTask(actorFn)
|
|
|
|
sleep(1)
|
|
}
|
|
}
|