@@ -32,45 +32,164 @@ extension Task where Success == Never, Failure == Never {
32
32
/// sleep(nanoseconds:).
33
33
private typealias SleepContinuation = UnsafeContinuation < ( ) , Error >
34
34
35
+ /// Describes the state of a sleep() operation.
36
+ private enum SleepState {
37
+ /// The sleep continuation has not yet begun.
38
+ case notStarted
39
+
40
+ // The sleep continuation has been created and is available here.
41
+ case activeContinuation( SleepContinuation )
42
+
43
+ /// The sleep has finished.
44
+ case finished
45
+
46
+ /// The sleep was cancelled.
47
+ case cancelled
48
+
49
+ /// The sleep was cancelled before it even got started.
50
+ case cancelledBeforeStarted
51
+
52
+ /// Decode sleep state from the word of storage.
53
+ init ( word: Builtin . Word ) {
54
+ switch UInt ( word) & 0x03 {
55
+ case 0 :
56
+ let continuationBits = UInt ( word) & ~ 0x03
57
+ if continuationBits == 0 {
58
+ self = . notStarted
59
+ } else {
60
+ let continuation = unsafeBitCast (
61
+ continuationBits, to: SleepContinuation . self)
62
+ self = . activeContinuation( continuation)
63
+ }
64
+
65
+ case 1 :
66
+ self = . finished
67
+
68
+ case 2 :
69
+ self = . cancelled
70
+
71
+ case 3 :
72
+ self = . cancelledBeforeStarted
73
+
74
+ default :
75
+ fatalError ( " Bitmask failure " )
76
+ }
77
+ }
78
+
79
+ /// Decode sleep state by loading from the given pointer
80
+ init ( loading wordPtr: UnsafeMutablePointer < Builtin . Word > ) {
81
+ self . init ( word: Builtin . atomicload_seqcst_Word ( wordPtr. _rawValue) )
82
+ }
83
+
84
+ /// Encode sleep state into a word of storage.
85
+ var word : UInt {
86
+ switch self {
87
+ case . notStarted:
88
+ return 0
89
+
90
+ case . activeContinuation( let continuation) :
91
+ let continuationBits = unsafeBitCast ( continuation, to: UInt . self)
92
+ return continuationBits
93
+
94
+ case . finished:
95
+ return 1
96
+
97
+ case . cancelled:
98
+ return 2
99
+
100
+ case . cancelledBeforeStarted:
101
+ return 3
102
+ }
103
+ }
104
+ }
105
+
35
106
/// Called when the sleep(nanoseconds:) operation woke up without being
36
107
/// cancelled.
37
108
private static func onSleepWake(
38
- _ wordPtr: UnsafeMutablePointer < Builtin . Word > ,
39
- _ continuation: UnsafeContinuation < ( ) , Error >
109
+ _ wordPtr: UnsafeMutablePointer < Builtin . Word >
40
110
) {
41
- // Indicate that we've finished by putting a "1" into the flag word.
42
- let ( _, won) = Builtin . cmpxchg_seqcst_seqcst_Word (
43
- wordPtr. _rawValue,
44
- UInt ( 0 ) . _builtinWordValue,
45
- UInt ( 1 ) . _builtinWordValue)
46
-
47
- if Bool ( _builtinBooleanLiteral: won) {
48
- // The sleep finished, invoke the continuation.
49
- continuation. resume ( )
50
- } else {
51
- // The task was cancelled first, which means the continuation was
52
- // called by the cancellation handler. We need to deallocate up the flag
53
- // word, because it was left over for this task to complete.
54
- wordPtr. deallocate ( )
111
+ while true {
112
+ let state = SleepState ( loading: wordPtr)
113
+ switch state {
114
+ case . notStarted:
115
+ fatalError ( " Cannot wake before we even started " )
116
+
117
+ case . activeContinuation( let continuation) :
118
+ // We have an active continuation, so try to transition to the
119
+ // "finished" state.
120
+ let ( _, won) = Builtin . cmpxchg_seqcst_seqcst_Word (
121
+ wordPtr. _rawValue,
122
+ state. word. _builtinWordValue,
123
+ SleepState . finished. word. _builtinWordValue)
124
+ if Bool ( _builtinBooleanLiteral: won) {
125
+ // The sleep finished, so invoke the continuation: we're done.
126
+ continuation. resume ( )
127
+ return
128
+ }
129
+
130
+ // Try again!
131
+ continue
132
+
133
+ case . finished:
134
+ fatalError ( " Already finished normally, can't do that again " )
135
+
136
+ case . cancelled:
137
+ // The task was cancelled, which means the continuation was
138
+ // called by the cancellation handler. We need to deallocate the flag
139
+ // word, because it was left over for this task to complete.
140
+ wordPtr. deallocate ( )
141
+ return
142
+
143
+ case . cancelledBeforeStarted:
144
+ // Nothing to do;
145
+ return
146
+ }
55
147
}
56
148
}
57
149
58
150
/// Called when the sleep(nanoseconds:) operation has been cancelled before
59
151
/// the sleep completed.
60
152
private static func onSleepCancel(
61
- _ wordPtr: UnsafeMutablePointer < Builtin . Word > ,
62
- _ continuation: UnsafeContinuation < ( ) , Error >
153
+ _ wordPtr: UnsafeMutablePointer < Builtin . Word >
63
154
) {
64
- // Indicate that we've finished by putting a "2" into the flag word.
65
- let ( _, won) = Builtin . cmpxchg_seqcst_seqcst_Word (
66
- wordPtr. _rawValue,
67
- UInt ( 0 ) . _builtinWordValue,
68
- UInt ( 2 ) . _builtinWordValue)
69
-
70
- if Bool ( _builtinBooleanLiteral: won) {
71
- // We recorded the task cancellation before the sleep finished, so
72
- // invoke the continuation with a the cancellation error.
73
- continuation. resume ( throwing: _Concurrency. CancellationError ( ) )
155
+ while true {
156
+ let state = SleepState ( loading: wordPtr)
157
+ switch state {
158
+ case . notStarted:
159
+ // We haven't started yet, so try to transition to the cancelled-before
160
+ // started state.
161
+ let ( _, won) = Builtin . cmpxchg_seqcst_seqcst_Word (
162
+ wordPtr. _rawValue,
163
+ state. word. _builtinWordValue,
164
+ SleepState . cancelledBeforeStarted. word. _builtinWordValue)
165
+ if Bool ( _builtinBooleanLiteral: won) {
166
+ return
167
+ }
168
+
169
+ // Try again!
170
+ continue
171
+
172
+ case . activeContinuation( let continuation) :
173
+ // We have an active continuation, so try to transition to the
174
+ // "cancelled" state.
175
+ let ( _, won) = Builtin . cmpxchg_seqcst_seqcst_Word (
176
+ wordPtr. _rawValue,
177
+ state. word. _builtinWordValue,
178
+ SleepState . cancelled. word. _builtinWordValue)
179
+ if Bool ( _builtinBooleanLiteral: won) {
180
+ // We recorded the task cancellation before the sleep finished, so
181
+ // invoke the continuation with the cancellation error.
182
+ continuation. resume ( throwing: _Concurrency. CancellationError ( ) )
183
+ return
184
+ }
185
+
186
+ // Try again!
187
+ continue
188
+
189
+ case . finished, . cancelled, . cancelledBeforeStarted:
190
+ // The operation already finished, so there is nothing more to do.
191
+ return
192
+ }
74
193
}
75
194
}
76
195
@@ -80,64 +199,95 @@ extension Task where Success == Never, Failure == Never {
80
199
///
81
200
/// This function does _not_ block the underlying thread.
82
201
public static func sleep( nanoseconds duration: UInt64 ) async throws {
83
- // If the task was already cancelled, go ahead and throw now.
84
- try checkCancellation ( )
85
-
86
- // Allocate storage for the flag word and continuation.
87
- let wordPtr = UnsafeMutablePointer< Builtin . Word> . allocate( capacity: 2 )
202
+ // Allocate storage for the storage word.
203
+ let wordPtr = UnsafeMutablePointer< Builtin . Word> . allocate( capacity: 1 )
88
204
89
- // Initialize the flag word to 0 , which means the continuation has not
90
- // executed .
205
+ // Initialize the flag word to "not started" , which means the continuation
206
+ // has neither been created nor completed .
91
207
Builtin . atomicstore_seqcst_Word (
92
- wordPtr. _rawValue, UInt ( 0 ) . _builtinWordValue)
93
-
94
- // A pointer to the storage continuation. Also initialize it to zero, to
95
- // indicate that there is no continuation.
96
- let continuationPtr = wordPtr + 1
97
- Builtin . atomicstore_seqcst_Word (
98
- continuationPtr. _rawValue, UInt ( 0 ) . _builtinWordValue)
208
+ wordPtr. _rawValue, SleepState . notStarted. word. _builtinWordValue)
99
209
100
210
do {
101
211
// Install a cancellation handler to resume the continuation by
102
212
// throwing CancellationError.
103
213
try await withTaskCancellationHandler {
104
214
let _: ( ) = try await withUnsafeThrowingContinuation { continuation in
105
- // Stash the continuation so the cancellation handler can see it.
106
- Builtin . atomicstore_seqcst_Word (
107
- continuationPtr. _rawValue,
108
- unsafeBitCast ( continuation, to: Builtin . Word. self) )
109
-
110
- // Create a task that resumes the continuation normally if it
111
- // finishes first. Enqueue it directly with the delay, so it fires
112
- // when we're done sleeping.
113
- let sleepTaskFlags = taskCreateFlags (
114
- priority: nil , isChildTask: false , copyTaskLocals: false ,
115
- inheritContext: false , enqueueJob: false ,
116
- addPendingGroupTaskUnconditionally: false )
117
- let ( sleepTask, _) = Builtin . createAsyncTask ( sleepTaskFlags) {
118
- onSleepWake ( wordPtr, continuation)
215
+ while true {
216
+ let state = SleepState ( loading: wordPtr)
217
+ switch state {
218
+ case . notStarted:
219
+ // The word that describes the active continuation state.
220
+ let continuationWord =
221
+ SleepState . activeContinuation ( continuation) . word
222
+
223
+ // Try to swap in the continuation word.
224
+ let ( _, won) = Builtin . cmpxchg_seqcst_seqcst_Word (
225
+ wordPtr. _rawValue,
226
+ state. word. _builtinWordValue,
227
+ continuationWord. _builtinWordValue)
228
+ if !Bool( _builtinBooleanLiteral: won) {
229
+ // Keep trying!
230
+ continue
231
+ }
232
+
233
+ // Create a task that resumes the continuation normally if it
234
+ // finishes first. Enqueue it directly with the delay, so it fires
235
+ // when we're done sleeping.
236
+ let sleepTaskFlags = taskCreateFlags (
237
+ priority: nil , isChildTask: false , copyTaskLocals: false ,
238
+ inheritContext: false , enqueueJob: false ,
239
+ addPendingGroupTaskUnconditionally: false )
240
+ let ( sleepTask, _) = Builtin . createAsyncTask ( sleepTaskFlags) {
241
+ onSleepWake ( wordPtr)
242
+ }
243
+ _enqueueJobGlobalWithDelay (
244
+ duration, Builtin . convertTaskToJob ( sleepTask) )
245
+ return
246
+
247
+ case . activeContinuation, . finished:
248
+ fatalError ( " Impossible to have multiple active continuations " )
249
+
250
+ case . cancelled:
251
+ fatalError ( " Impossible to have cancelled before we began " )
252
+
253
+ case . cancelledBeforeStarted:
254
+ // Finish the continuation normally. We'll throw later, after
255
+ // we clean up.
256
+ continuation. resume ( )
257
+ return
119
258
}
120
- _enqueueJobGlobalWithDelay (
121
- duration, Builtin . convertTaskToJob ( sleepTask) )
122
259
}
123
- } onCancel: {
124
- let continuationWord = continuationPtr. pointee
125
- if UInt ( continuationWord) != 0 {
126
- // Try to cancel, which will resume the continuation by throwing a
127
- // CancellationError if the continuation hasn't already been resumed.
128
- continuationPtr. withMemoryRebound (
129
- to: SleepContinuation . self, capacity: 1 ) {
130
- onSleepCancel ( wordPtr, $0. pointee)
131
- }
132
260
}
261
+ } onCancel: {
262
+ onSleepCancel ( wordPtr)
263
+ }
264
+
265
+ // Determine whether we got cancelled before we even started.
266
+ let cancelledBeforeStarted : Bool
267
+ switch SleepState ( loading: wordPtr) {
268
+ case . notStarted, . activeContinuation, . cancelled:
269
+ fatalError ( " Invalid state for non-cancelled sleep task " )
270
+
271
+ case . cancelledBeforeStarted:
272
+ cancelledBeforeStarted = true
273
+
274
+ case . finished:
275
+ cancelledBeforeStarted = false
133
276
}
134
277
135
278
// We got here without being cancelled, so deallocate the storage for
136
279
// the flag word and continuation.
137
280
wordPtr. deallocate ( )
281
+
282
+ // If we got cancelled before we even started, through the cancellation
283
+ // error now.
284
+ if cancelledBeforeStarted {
285
+ throw _Concurrency. CancellationError ( )
286
+ }
138
287
} catch {
139
288
// The task was cancelled; propagate the error. The "on wake" task is
140
- // responsible for deallocating the flag word.
289
+ // responsible for deallocating the flag word and continuation, if it's
290
+ // still running.
141
291
throw error
142
292
}
143
293
}
0 commit comments