-
Notifications
You must be signed in to change notification settings - Fork 10.5k
/
Copy pathTaskLocal.swift
334 lines (307 loc) · 12.2 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
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
//===----------------------------------------------------------------------===//
//
// 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 Example {
/// @TaskLocal
/// static var traceID: TraceID?
/// }
///
/// ### Default values
/// Reading a task local value when no value was bound to it results in returning
/// its default value. For a task local declared as optional (such as e.g. `TraceID?`),
/// this defaults to nil, however a different default value may be defined at declaration
/// site of the task local, like this:
///
/// enum Example {
/// @TaskLocal
/// static var traceID: TraceID = TraceID.default
/// }
///
/// 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),
/// or no value was bound within the scope of the current task or any of its parent tasks.
///
/// ### Reading task-local values
/// Reading task local values is simple and looks the same as-if reading a normal
/// static property:
///
/// guard let traceID = Example.traceID else {
/// print("no trace id")
/// return
/// }
/// print(traceID)
///
/// It is possible to perform task-local value reads from either asynchronous
/// or synchronous functions.
///
/// ### 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.
///
/// ### Using task local values outside of tasks
/// It is possible to bind and read task local values outside of tasks.
///
/// This comes in handy within synchronous functions which are not guaranteed
/// to be called from within a task. When binding a task-local value from
/// outside of a task, the runtime will set a thread-local in which the same
/// storage mechanism as used within tasks will be used. This means that you
/// can reliably bind and read task local values without having to worry
/// about the specific calling context, e.g.:
///
/// func enter() {
/// Example.$traceID.withValue("1234") {
/// read() // always "1234", regardless if enter() was called from inside a task or not:
/// }
///
/// func read() -> String {
/// if let value = Self.traceID {
/// "\(value)"
/// } else {
/// "<no value>"
/// }
/// }
///
/// // 1) Call `enter` from non-Task code
/// // e.g. synchronous main() or non-Task thread (e.g. a plain pthread)
/// enter()
///
/// // 2) Call 'enter' from Task
/// Task {
/// enter()
/// }
///
/// In either cases listed above, the binding and reading of the task-local value works as expected.
///
/// ### Examples
///
///
/// enum Example {
/// @TaskLocal
/// static var traceID: TraceID?
/// }
///
/// func read() -> String {
/// if let value = Self.traceID {
/// "\(value)"
/// } else {
/// "<no value>"
/// }
/// }
///
/// await Example.$traceID.withValue(1234) { // bind the value
/// print("traceID: \(Example.traceID)") // traceID: 1234
/// read() // traceID: 1234
///
/// async let id = read() // async let child task, traceID: 1234
///
/// await withTaskGroup(of: String.self) { group in
/// group.addTask { read() } // task group child task, traceID: 1234
/// return await group.next()!
/// }
///
/// Task { // unstructured tasks do inherit task locals by copying
/// read() // traceID: 1234
/// }
///
/// Task.detached { // detached tasks do not inherit task-local values
/// read() // traceID: nil
/// }
/// }
@propertyWrapper
@available(SwiftStdlib 5.1, *)
public final class TaskLocal<Value: Sendable>: 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<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.
@inlinable
@discardableResult
@_unsafeInheritExecutor
@backDeployed(before: SwiftStdlib 5.8)
public func withValue<R>(_ valueDuringOperation: Value, operation: () async throws -> R,
file: String = #fileID, line: UInt = #line) async rethrows -> R {
return try await withValueImpl(valueDuringOperation, operation: operation, file: file, line: line)
}
/// Implementation for withValue that consumes valueDuringOperation.
///
/// Because _taskLocalValuePush and _taskLocalValuePop involve calls to
/// swift_task_alloc/swift_task_dealloc respectively unbeknownst to the
/// compiler, compiler-emitted calls to swift_task_de/alloc must be avoided
/// in a function that calls them.
///
/// A copy of valueDuringOperation is required because withValue borrows its
/// argument but _taskLocalValuePush consumes its. Because
/// valueDuringOperation is of generic type, its size is not generally known,
/// so such a copy entails a stack allocation and a copy to that allocation.
/// That stack traffic gets lowered to calls to
/// swift_task_alloc/swift_task_deallloc.
///
/// Split the calls _taskLocalValuePush/Pop from the compiler-emitted calls
/// to swift_task_de/alloc for the copy as follows:
/// - withValue contains the compiler-emitted calls swift_task_de/alloc.
/// - withValueImpl contains the calls to _taskLocalValuePush/Pop
@inlinable
@discardableResult
@_unsafeInheritExecutor
@backDeployed(before: SwiftStdlib 5.9)
internal func withValueImpl<R>(_ valueDuringOperation: __owned 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: consume 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<R>(_ 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<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 {
}
}
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<Value>(
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<Int8>,
_ _filenameLength: Int,
_ _filenameIsASCII: Bool,
_ _line: UInt)