-
Notifications
You must be signed in to change notification settings - Fork 10.2k
/
TaskLocal.swift
257 lines (231 loc) · 9.27 KB
/
TaskLocal.swift
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
//===----------------------------------------------------------------------===//
//
// 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.5, *)
public final class TaskLocal<Value: Sendable>: Sendable, CustomStringConvertible {
let defaultValue: Value
public init(wrappedValue defaultValue: Value) {
self.defaultValue = defaultValue
}
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<Value>(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.
@discardableResult
public func withValue<R>(_ valueDuringOperation: Value, operation: () async throws -> R,
file: String = #file, 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.
@discardableResult
public func withValue<R>(_ valueDuringOperation: Value, operation: () throws -> R,
file: String = #file, 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<Value> {
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<Never, Value>,
storage storageKeyPath: ReferenceWritableKeyPath<Never, TaskLocal<Value>>
) -> 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.5, *)
@_silgen_name("swift_task_localValuePush")
func _taskLocalValuePush<Value>(
key: Builtin.RawPointer/*: Key*/,
value: __owned Value
) // where Key: TaskLocal
@available(SwiftStdlib 5.5, *)
@_silgen_name("swift_task_localValuePop")
func _taskLocalValuePop()
@available(SwiftStdlib 5.5, *)
@_silgen_name("swift_task_localValueGet")
func _taskLocalValueGet(
key: Builtin.RawPointer/*Key*/
) -> UnsafeMutableRawPointer? // where Key: TaskLocal
@available(SwiftStdlib 5.5, *)
@_silgen_name("swift_task_localsCopyTo")
func _taskLocalsCopy(
to target: Builtin.NativeObject
)
// ==== Checks -----------------------------------------------------------------
@available(SwiftStdlib 5.5, *)
@usableFromInline
func _checkIllegalTaskLocalBindingWithinWithTaskGroup(file: String, line: UInt) {
if _taskHasTaskGroupStatusRecord() {
file.withCString { _fileStart in
_reportIllegalTaskLocalBindingWithinWithTaskGroup(
_fileStart, file.count, true, line)
}
}
}
@available(SwiftStdlib 5.5, *)
@usableFromInline
@_silgen_name("swift_task_reportIllegalTaskLocalBindingWithinWithTaskGroup")
func _reportIllegalTaskLocalBindingWithinWithTaskGroup(
_ _filenameStart: UnsafePointer<Int8>,
_ _filenameLength: Int,
_ _filenameIsASCII: Bool,
_ _line: UInt)