13
13
import Swift
14
14
@_implementationOnly import _SwiftConcurrencyShims
15
15
16
+ /// Property wrapper that defines a task-local value key.
17
+ ///
18
+ /// A task-local value is a value that can be bound and read in the context of a
19
+ /// `Task`. It is implicitly carried with the task, and is accessible by any
20
+ /// child tasks the task creates (such as TaskGroup or `async let` created tasks).
21
+ ///
22
+ /// ### Task-local declarations
23
+ ///
24
+ /// Task locals must be declared as static properties (or global properties,
25
+ /// once property wrappers support these), like this:
26
+ ///
27
+ /// enum TracingExample {
28
+ /// @TaskLocal
29
+ /// static let traceID: TraceID?
30
+ /// }
31
+ ///
32
+ /// ### Default values
33
+ /// Task local values of optional types default to `nil`. It is possible to define
34
+ /// not-optional task-local values, and an explicit default value must then be
35
+ /// defined instead.
36
+ ///
37
+ /// The default value is returned whenever the task-local is read
38
+ /// from a context which either: has no task available to read the value from
39
+ /// (e.g. a synchronous function, called without any asynchronous function in its call stack),
40
+ ///
41
+ ///
42
+ /// ### Reading task-local values
43
+ /// Reading task local values is simple and looks the same as-if reading a normal
44
+ /// static property:
45
+ ///
46
+ /// guard let traceID = TracingExample.traceID else {
47
+ /// print("no trace id")
48
+ /// return
49
+ /// }
50
+ /// print(traceID)
51
+ ///
52
+ /// It is possible to perform task-local value reads from either asynchronous
53
+ /// or synchronous functions. Within asynchronous functions, as a "current" task
54
+ /// is always guaranteed to exist, this will perform the lookup in the task local context.
55
+ ///
56
+ /// A lookup made from the context of a synchronous function, that is not called
57
+ /// from an asynchronous function (!), will immediately return the task-local's
58
+ /// default value.
59
+ ///
60
+ /// ### Binding task-local values
61
+ /// Task local values cannot be `set` directly and must instead be bound using
62
+ /// the scoped `$traceID.withValue() { ... }` operation. The value is only bound
63
+ /// for the duration of that scope, and is available to any child tasks which
64
+ /// are created within that scope.
65
+ ///
66
+ /// Detached tasks do not inherit task-local values, however tasks created using
67
+ /// the `async {}` operation do inherit task-locals by copying them to the new
68
+ /// asynchronous task, even though it is an un-structured task.
69
+ ///
70
+ /// ### Examples
71
+ ///
72
+ /// @TaskLocal
73
+ /// static var traceID: TraceID?
74
+ ///
75
+ /// print("traceID: \(traceID)") // traceID: nil
76
+ ///
77
+ /// $traceID.withValue(1234) { // bind the value
78
+ /// print("traceID: \(traceID)") // traceID: 1234
79
+ /// call() // traceID: 1234
80
+ ///
81
+ /// asyncDetached { // detached tasks do not inherit task-local values
82
+ /// call() // traceID: nil
83
+ /// }
84
+ ///
85
+ /// async { // async tasks do inherit task locals by copying
86
+ /// call() // traceID: 1234
87
+ /// }
88
+ /// }
89
+ ///
90
+ ///
91
+ /// func call() {
92
+ /// print("traceID: \(traceID)") // 1234
93
+ /// }
94
+ ///
16
95
/// This type must be a `class` so it has a stable identity, that is used as key
17
96
/// value for lookups in the task local storage.
18
97
@propertyWrapper
19
98
@available ( macOS 9999 , iOS 9999 , watchOS 9999 , tvOS 9999 , * )
20
- public final class TaskLocal < Value: Sendable > : CustomStringConvertible {
21
- // only reason this is ! is to store the wrapper `self` in Access so we
22
- // can use its identity as the key for lookups.
23
- private var access : Access !
99
+ public final class TaskLocal < Value: Sendable > : Sendable , CustomStringConvertible {
100
+ let defaultValue : Value
24
101
25
- public init ( default defaultValue: Value ) {
26
- self . access = Access ( key : self , defaultValue: defaultValue )
102
+ public init ( wrappedValue defaultValue: Value ) {
103
+ self . defaultValue = defaultValue
27
104
}
28
105
106
+ var key : Builtin . RawPointer {
107
+ unsafeBitCast ( self , to: Builtin . RawPointer. self)
108
+ }
29
109
30
- public struct Access : CustomStringConvertible {
31
- let key : Builtin . RawPointer
32
- let defaultValue : Value
33
-
34
- init ( key: TaskLocal < Value > , defaultValue: Value ) {
35
- self . key = unsafeBitCast ( key, to: Builtin . RawPointer. self)
36
- self . defaultValue = defaultValue
37
- }
38
-
39
- public func get( ) -> Value {
40
- withUnsafeCurrentTask { task in
41
- guard let task = task else {
42
- return self . defaultValue
43
- }
44
-
45
- let value = _taskLocalValueGet ( task. _task, key: key)
110
+ /// Gets the value currently bound to this task-local from the current task.
111
+ ///
112
+ /// If no current task is available in the context where this call is made,
113
+ /// or if the task-local has no value bound, this will return the `defaultValue`
114
+ /// of the task local.
115
+ public func get( ) -> Value {
116
+ withUnsafeCurrentTask { task in
117
+ guard let task = task else {
118
+ return self . defaultValue
119
+ }
46
120
47
- guard let rawValue = value else {
48
- return self . defaultValue
49
- }
121
+ let value = _taskLocalValueGet ( task. _task, key: key)
50
122
51
- // Take the value; The type should be correct by construction
52
- let storagePtr =
53
- rawValue. bindMemory ( to: Value . self, capacity: 1 )
54
- return UnsafeMutablePointer < Value > ( mutating: storagePtr) . pointee
123
+ guard let rawValue = value else {
124
+ return self . defaultValue
55
125
}
56
- }
57
126
58
- /// Execute the `body` closure
59
- @discardableResult
60
- public func withValue< R> ( _ valueDuringBody: Value , do body: ( ) async throws -> R ,
61
- file: String = #file, line: UInt = #line) async rethrows -> R {
62
- // check if we're not trying to bind a value from an illegal context; this may crash
63
- _checkIllegalTaskLocalBindingWithinWithTaskGroup ( file: file, line: line)
64
-
65
- // we need to escape the `_task` since the withUnsafeCurrentTask closure is not `async`.
66
- // this is safe, since we know the task will remain alive because we are running inside of it.
67
- let _task = withUnsafeCurrentTask { task in
68
- task!. _task // !-safe, guaranteed to have task available inside async function
69
- }
127
+ // Take the value; The type should be correct by construction
128
+ let storagePtr =
129
+ rawValue. bindMemory ( to: Value . self, capacity: 1 )
130
+ return UnsafeMutablePointer < Value > ( mutating: storagePtr) . pointee
131
+ }
132
+ }
70
133
71
- _taskLocalValuePush ( _task, key: key, value: valueDuringBody)
72
- defer { _taskLocalValuePop ( _task) }
134
+ /// Binds the task-local to the specific value for the duration of the body.
135
+ ///
136
+ /// The value is available throughout the execution of the body closure,
137
+ /// including any `get` operations performed by child-tasks created during the
138
+ /// execution of the body closure.
139
+ ///
140
+ /// If the same task-local is bound multiple times, be it in the same task, or
141
+ /// in specific child tasks, the more specific (i.e. "deeper") binding is
142
+ /// returned when the value is read.
143
+ ///
144
+ /// If the value is a reference type, it will be retained for the duration of
145
+ /// the body closure.
146
+ @discardableResult
147
+ public func withValue< R> ( _ valueDuringBody: Value , do body: ( ) async throws -> R ,
148
+ file: String = #file, line: UInt = #line) async rethrows -> R {
149
+ // check if we're not trying to bind a value from an illegal context; this may crash
150
+ _checkIllegalTaskLocalBindingWithinWithTaskGroup ( file: file, line: line)
73
151
74
- return try await body ( )
152
+ // we need to escape the `_task` since the withUnsafeCurrentTask closure is not `async`.
153
+ // this is safe, since we know the task will remain alive because we are running inside of it.
154
+ let _task = withUnsafeCurrentTask { task in
155
+ task!. _task // !-safe, guaranteed to have task available inside async function
75
156
}
76
157
77
- public var description : String {
78
- " TaskLocal< \( Value . self) >.Access "
79
- }
158
+ _taskLocalValuePush ( _task, key: key, value: valueDuringBody)
159
+ defer { _taskLocalValuePop ( _task) }
160
+
161
+ return try await body ( )
80
162
}
81
163
82
- public var wrappedValue : TaskLocal < Value > . Access {
164
+ public var projectedValue : TaskLocal < Value > {
83
165
get {
84
- self . access
166
+ self
85
167
}
86
168
87
169
@available ( * , unavailable, message: " use 'myTaskLocal.withValue(_:do:)' instead " )
@@ -90,17 +172,14 @@ public final class TaskLocal<Value: Sendable>: CustomStringConvertible {
90
172
}
91
173
}
92
174
93
- public var description : String {
94
- " \( Self . self) (defaultValue: \( self . access . defaultValue ) ) "
175
+ public var wrappedValue : Value {
176
+ self . get ( )
95
177
}
96
178
97
- }
98
-
99
- @available ( macOS 9999 , iOS 9999 , watchOS 9999 , tvOS 9999 , * )
100
- extension TaskLocal {
101
- public convenience init < V> ( ) where Value == Optional < V > {
102
- self . init ( default: nil )
179
+ public var description : String {
180
+ " \( Self . self) (defaultValue: \( self . defaultValue) ) "
103
181
}
182
+
104
183
}
105
184
106
185
@available ( macOS 9999 , iOS 9999 , watchOS 9999 , tvOS 9999 , * )
@@ -113,13 +192,14 @@ extension UnsafeCurrentTask {
113
192
/// represented by this object.
114
193
@discardableResult
115
194
public func withTaskLocal< Value: Sendable , R> (
116
- _ access: TaskLocal < Value > . Access , boundTo valueDuringBody: Value ,
195
+ _ taskLocal: TaskLocal < Value > ,
196
+ boundTo valueDuringBody: Value ,
117
197
do body: ( ) throws -> R ,
118
198
file: String = #file, line: UInt = #line) rethrows -> R {
119
199
// check if we're not trying to bind a value from an illegal context; this may crash
120
200
_checkIllegalTaskLocalBindingWithinWithTaskGroup ( file: file, line: line)
121
201
122
- _taskLocalValuePush ( self . _task, key: access . key, value: valueDuringBody)
202
+ _taskLocalValuePush ( self . _task, key: taskLocal . key, value: valueDuringBody)
123
203
defer { _taskLocalValuePop ( _task) }
124
204
125
205
return try body ( )
0 commit comments