Files
swift-mirror/test/Concurrency/Runtime/data_race_detection.swift
Doug Gregor e77a27e8ed [Concurrency] Introduce runtime detection of data races.
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.
2021-04-12 15:19:51 -07:00

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)
}
}