//===----------------------------------------------------------------------===// // // This source file is part of the Swift.org open source project // // Copyright (c) 2020-2021 Apple Inc. and the Swift project authors // Licensed under Apache License v2.0 with Runtime Library Exception // // See https://swift.org/LICENSE.txt for license information // See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors // //===----------------------------------------------------------------------===// import Swift @_implementationOnly import _SwiftConcurrencyShims /// Property wrapper that defines a task-local value key. /// /// A task-local value is a value that can be bound and read in the context of a /// `Task`. It is implicitly carried with the task, and is accessible by any /// child tasks the task creates (such as TaskGroup or `async let` created tasks). /// /// ### Task-local declarations /// /// Task locals must be declared as static properties (or global properties, /// once property wrappers support these), like this: /// /// enum TracingExample { /// @TaskLocal /// static let traceID: TraceID? /// } /// /// ### Default values /// Task local values of optional types default to `nil`. It is possible to define /// not-optional task-local values, and an explicit default value must then be /// defined instead. /// /// The default value is returned whenever the task-local is read /// from a context which either: has no task available to read the value from /// (e.g. a synchronous function, called without any asynchronous function in its call stack), /// /// /// ### Reading task-local values /// Reading task local values is simple and looks the same as-if reading a normal /// static property: /// /// guard let traceID = TracingExample.traceID else { /// print("no trace id") /// return /// } /// print(traceID) /// /// It is possible to perform task-local value reads from either asynchronous /// or synchronous functions. Within asynchronous functions, as a "current" task /// is always guaranteed to exist, this will perform the lookup in the task local context. /// /// A lookup made from the context of a synchronous function, that is not called /// from an asynchronous function (!), will immediately return the task-local's /// default value. /// /// ### Binding task-local values /// Task local values cannot be `set` directly and must instead be bound using /// the scoped `$traceID.withValue() { ... }` operation. The value is only bound /// for the duration of that scope, and is available to any child tasks which /// are created within that scope. /// /// Detached tasks do not inherit task-local values, however tasks created using /// the `Task { ... }` initializer do inherit task-locals by copying them to the /// new asynchronous task, even though it is an un-structured task. /// /// ### Examples /// /// @TaskLocal /// static var traceID: TraceID? /// /// print("traceID: \(traceID)") // traceID: nil /// /// $traceID.withValue(1234) { // bind the value /// print("traceID: \(traceID)") // traceID: 1234 /// call() // traceID: 1234 /// /// Task { // unstructured tasks do inherit task locals by copying /// call() // traceID: 1234 /// } /// /// Task.detached { // detached tasks do not inherit task-local values /// call() // traceID: nil /// } /// } /// /// func call() { /// print("traceID: \(traceID)") // 1234 /// } /// /// This type must be a `class` so it has a stable identity, that is used as key /// value for lookups in the task local storage. @propertyWrapper @available(SwiftStdlib 5.1, *) public final class TaskLocal: Sendable, CustomStringConvertible { let defaultValue: Value public init(wrappedValue defaultValue: Value) { self.defaultValue = defaultValue } @_alwaysEmitIntoClient var key: Builtin.RawPointer { unsafeBitCast(self, to: Builtin.RawPointer.self) } /// Gets the value currently bound to this task-local from the current task. /// /// If no current task is available in the context where this call is made, /// or if the task-local has no value bound, this will return the `defaultValue` /// of the task local. public func get() -> Value { guard let rawValue = _taskLocalValueGet(key: key) else { return self.defaultValue } // Take the value; The type should be correct by construction let storagePtr = rawValue.bindMemory(to: Value.self, capacity: 1) return UnsafeMutablePointer(mutating: storagePtr).pointee } /// Binds the task-local to the specific value for the duration of the asynchronous operation. /// /// The value is available throughout the execution of the operation closure, /// including any `get` operations performed by child-tasks created during the /// execution of the operation closure. /// /// If the same task-local is bound multiple times, be it in the same task, or /// in specific child tasks, the more specific (i.e. "deeper") binding is /// returned when the value is read. /// /// If the value is a reference type, it will be retained for the duration of /// the operation closure. @inlinable @discardableResult @_unsafeInheritExecutor @available(SwiftStdlib 5.1, *) // back deploy requires we declare the availability explicitly on this method @_backDeploy(before: SwiftStdlib 5.8) public func withValue(_ valueDuringOperation: Value, operation: () async throws -> R, file: String = #fileID, line: UInt = #line) async rethrows -> R { // check if we're not trying to bind a value from an illegal context; this may crash _checkIllegalTaskLocalBindingWithinWithTaskGroup(file: file, line: line) _taskLocalValuePush(key: key, value: valueDuringOperation) defer { _taskLocalValuePop() } return try await operation() } /// Binds the task-local to the specific value for the duration of the /// synchronous operation. /// /// The value is available throughout the execution of the operation closure, /// including any `get` operations performed by child-tasks created during the /// execution of the operation closure. /// /// If the same task-local is bound multiple times, be it in the same task, or /// in specific child tasks, the "more specific" binding is returned when the /// value is read. /// /// If the value is a reference type, it will be retained for the duration of /// the operation closure. @inlinable @discardableResult public func withValue(_ valueDuringOperation: Value, operation: () throws -> R, file: String = #fileID, line: UInt = #line) rethrows -> R { // check if we're not trying to bind a value from an illegal context; this may crash _checkIllegalTaskLocalBindingWithinWithTaskGroup(file: file, line: line) _taskLocalValuePush(key: key, value: valueDuringOperation) defer { _taskLocalValuePop() } return try operation() } public var projectedValue: TaskLocal { get { self } @available(*, unavailable, message: "use '$myTaskLocal.withValue(_:do:)' instead") set { fatalError("Illegal attempt to set a \(Self.self) value, use `withValue(...) { ... }` instead.") } } // This subscript is used to enforce that the property wrapper may only be used // on static (or rather, "without enclosing instance") properties. // This is done by marking the `_enclosingInstance` as `Never` which informs // the type-checker that this property-wrapper never wants to have an enclosing // instance (it is impossible to declare a property wrapper inside the `Never` // type). @available(*, unavailable, message: "property wrappers cannot be instance members") public static subscript( _enclosingInstance object: Never, wrapped wrappedKeyPath: ReferenceWritableKeyPath, storage storageKeyPath: ReferenceWritableKeyPath> ) -> Value { get { fatalError("Will never be executed, since enclosing instance is Never") } } public var wrappedValue: Value { self.get() } public var description: String { "\(Self.self)(defaultValue: \(self.defaultValue))" } } // ==== ------------------------------------------------------------------------ @available(SwiftStdlib 5.1, *) @usableFromInline @_silgen_name("swift_task_localValuePush") func _taskLocalValuePush( key: Builtin.RawPointer/*: Key*/, value: __owned Value ) // where Key: TaskLocal @available(SwiftStdlib 5.1, *) @usableFromInline @_silgen_name("swift_task_localValuePop") func _taskLocalValuePop() @available(SwiftStdlib 5.1, *) @_silgen_name("swift_task_localValueGet") func _taskLocalValueGet( key: Builtin.RawPointer/*Key*/ ) -> UnsafeMutableRawPointer? // where Key: TaskLocal @available(SwiftStdlib 5.1, *) @_silgen_name("swift_task_localsCopyTo") func _taskLocalsCopy( to target: Builtin.NativeObject ) // ==== Checks ----------------------------------------------------------------- @available(SwiftStdlib 5.1, *) @usableFromInline func _checkIllegalTaskLocalBindingWithinWithTaskGroup(file: String, line: UInt) { if _taskHasTaskGroupStatusRecord() { file.withCString { _fileStart in _reportIllegalTaskLocalBindingWithinWithTaskGroup( _fileStart, file.count, true, line) } } } @available(SwiftStdlib 5.1, *) @usableFromInline @_silgen_name("swift_task_reportIllegalTaskLocalBindingWithinWithTaskGroup") func _reportIllegalTaskLocalBindingWithinWithTaskGroup( _ _filenameStart: UnsafePointer, _ _filenameLength: Int, _ _filenameIsASCII: Bool, _ _line: UInt)