@@ -105,25 +105,55 @@ extension Task where Success == Never, Failure == Never {
105
105
}
106
106
}
107
107
108
+ /// A simple wrapper for a pointer to heap allocated storage of a `SleepState`
109
+ /// value. This wrapper is `Sendable` because it facilitates atomic load and
110
+ /// exchange operations on the underlying storage. However, this wrapper is also
111
+ /// _unsafe_ because the owner must manually deallocate the token once it is no
112
+ /// longer needed.
113
+ struct UnsafeSleepStateToken : @unchecked Sendable {
114
+ let wordPtr : UnsafeMutablePointer < Builtin . Word >
115
+
116
+ /// Allocates the underlying storage and sets the value to `.notStarted`.
117
+ init ( ) {
118
+ wordPtr = . allocate( capacity: 1 )
119
+ Builtin . atomicstore_seqcst_Word (
120
+ wordPtr. _rawValue, SleepState . notStarted. word. _builtinWordValue)
121
+ }
122
+
123
+ /// Atomically loads the current state.
124
+ func load( ) -> SleepState {
125
+ return SleepState ( word: Builtin . atomicload_seqcst_Word ( wordPtr. _rawValue) )
126
+ }
127
+
128
+ /// Attempts to atomically set the stored value to `desired` if the current
129
+ /// value is equal to `expected`. Returns true if the exchange was successful.
130
+ func exchange( expected: SleepState , desired: SleepState ) -> Bool {
131
+ let ( _, won) = Builtin . cmpxchg_seqcst_seqcst_Word (
132
+ wordPtr. _rawValue,
133
+ expected. word. _builtinWordValue,
134
+ desired. word. _builtinWordValue)
135
+ return Bool ( _builtinBooleanLiteral: won)
136
+ }
137
+
138
+ /// Deallocates the underlying storage.
139
+ func deallocate( ) {
140
+ wordPtr. deallocate ( )
141
+ }
142
+ }
143
+
108
144
/// Called when the sleep(nanoseconds:) operation woke up without being
109
145
/// canceled.
110
- static func onSleepWake(
111
- _ wordPtr: UnsafeMutablePointer < Builtin . Word >
112
- ) {
146
+ static func onSleepWake( _ token: UnsafeSleepStateToken ) {
113
147
while true {
114
- let state = SleepState ( loading : wordPtr )
148
+ let state = token . load ( )
115
149
switch state {
116
150
case . notStarted:
117
151
fatalError ( " Cannot wake before we even started " )
118
152
119
153
case . activeContinuation( let continuation) :
120
154
// We have an active continuation, so try to transition to the
121
155
// "finished" state.
122
- let ( _, won) = Builtin . cmpxchg_seqcst_seqcst_Word (
123
- wordPtr. _rawValue,
124
- state. word. _builtinWordValue,
125
- SleepState . finished. word. _builtinWordValue)
126
- if Bool ( _builtinBooleanLiteral: won) {
156
+ if token. exchange ( expected: state, desired: . finished) {
127
157
// The sleep finished, so invoke the continuation: we're done.
128
158
continuation. resume ( )
129
159
return
@@ -137,9 +167,9 @@ extension Task where Success == Never, Failure == Never {
137
167
138
168
case . cancelled:
139
169
// The task was cancelled, which means the continuation was
140
- // called by the cancellation handler. We need to deallocate the flag
141
- // word, because it was left over for this task to complete.
142
- wordPtr . deallocate ( )
170
+ // called by the cancellation handler. We need to deallocate the token
171
+ // because it was left over for this task to complete.
172
+ token . deallocate ( )
143
173
return
144
174
145
175
case . cancelledBeforeStarted:
@@ -151,20 +181,14 @@ extension Task where Success == Never, Failure == Never {
151
181
152
182
/// Called when the sleep(nanoseconds:) operation has been canceled before
153
183
/// the sleep completed.
154
- static func onSleepCancel(
155
- _ wordPtr: UnsafeMutablePointer < Builtin . Word >
156
- ) {
184
+ static func onSleepCancel( _ token: UnsafeSleepStateToken ) {
157
185
while true {
158
- let state = SleepState ( loading : wordPtr )
186
+ let state = token . load ( )
159
187
switch state {
160
188
case . notStarted:
161
189
// We haven't started yet, so try to transition to the cancelled-before
162
190
// started state.
163
- let ( _, won) = Builtin . cmpxchg_seqcst_seqcst_Word (
164
- wordPtr. _rawValue,
165
- state. word. _builtinWordValue,
166
- SleepState . cancelledBeforeStarted. word. _builtinWordValue)
167
- if Bool ( _builtinBooleanLiteral: won) {
191
+ if token. exchange ( expected: state, desired: . cancelledBeforeStarted) {
168
192
return
169
193
}
170
194
@@ -174,11 +198,7 @@ extension Task where Success == Never, Failure == Never {
174
198
case . activeContinuation( let continuation) :
175
199
// We have an active continuation, so try to transition to the
176
200
// "cancelled" state.
177
- let ( _, won) = Builtin . cmpxchg_seqcst_seqcst_Word (
178
- wordPtr. _rawValue,
179
- state. word. _builtinWordValue,
180
- SleepState . cancelled. word. _builtinWordValue)
181
- if Bool ( _builtinBooleanLiteral: won) {
201
+ if token. exchange ( expected: state, desired: . cancelled) {
182
202
// We recorded the task cancellation before the sleep finished, so
183
203
// invoke the continuation with the cancellation error.
184
204
continuation. resume ( throwing: _Concurrency. CancellationError ( ) )
@@ -203,33 +223,22 @@ extension Task where Success == Never, Failure == Never {
203
223
///
204
224
/// This function doesn't block the underlying thread.
205
225
public static func sleep( nanoseconds duration: UInt64 ) async throws {
206
- // Allocate storage for the storage word.
207
- let wordPtr = UnsafeMutablePointer< Builtin . Word> . allocate( capacity: 1 )
208
-
209
- // Initialize the flag word to "not started", which means the continuation
210
- // has neither been created nor completed.
211
- Builtin . atomicstore_seqcst_Word (
212
- wordPtr. _rawValue, SleepState . notStarted. word. _builtinWordValue)
226
+ // Create a token which will initially have the value "not started", which
227
+ // means the continuation has neither been created nor completed.
228
+ let token = UnsafeSleepStateToken ( )
213
229
214
230
do {
215
231
// Install a cancellation handler to resume the continuation by
216
232
// throwing CancellationError.
217
233
try await withTaskCancellationHandler {
218
234
let _: ( ) = try await withUnsafeThrowingContinuation { continuation in
219
235
while true {
220
- let state = SleepState ( loading : wordPtr )
236
+ let state = token . load ( )
221
237
switch state {
222
238
case . notStarted:
223
- // The word that describes the active continuation state.
224
- let continuationWord =
225
- SleepState . activeContinuation ( continuation) . word
226
-
227
- // Try to swap in the continuation word.
228
- let ( _, won) = Builtin . cmpxchg_seqcst_seqcst_Word (
229
- wordPtr. _rawValue,
230
- state. word. _builtinWordValue,
231
- continuationWord. _builtinWordValue)
232
- if !Bool( _builtinBooleanLiteral: won) {
239
+ // Try to swap in the continuation state.
240
+ let newState = SleepState . activeContinuation ( continuation)
241
+ if !token. exchange ( expected: state, desired: newState) {
233
242
// Keep trying!
234
243
continue
235
244
}
@@ -243,7 +252,7 @@ extension Task where Success == Never, Failure == Never {
243
252
addPendingGroupTaskUnconditionally: false ,
244
253
isDiscardingTask: false )
245
254
let ( sleepTask, _) = Builtin . createAsyncTask ( sleepTaskFlags) {
246
- onSleepWake ( wordPtr )
255
+ onSleepWake ( token )
247
256
}
248
257
_enqueueJobGlobalWithDelay (
249
258
duration, Builtin . convertTaskToJob ( sleepTask) )
@@ -264,12 +273,12 @@ extension Task where Success == Never, Failure == Never {
264
273
}
265
274
}
266
275
} onCancel: {
267
- onSleepCancel ( wordPtr )
276
+ onSleepCancel ( token )
268
277
}
269
278
270
279
// Determine whether we got cancelled before we even started.
271
280
let cancelledBeforeStarted : Bool
272
- switch SleepState ( loading : wordPtr ) {
281
+ switch token . load ( ) {
273
282
case . notStarted, . activeContinuation, . cancelled:
274
283
fatalError ( " Invalid state for non-cancelled sleep task " )
275
284
@@ -282,7 +291,7 @@ extension Task where Success == Never, Failure == Never {
282
291
283
292
// We got here without being cancelled, so deallocate the storage for
284
293
// the flag word and continuation.
285
- wordPtr . deallocate ( )
294
+ token . deallocate ( )
286
295
287
296
// If we got cancelled before we even started, through the cancellation
288
297
// error now.
0 commit comments