@@ -95,6 +95,10 @@ public final class NIOAsyncTestingEventLoop: EventLoop, @unchecked Sendable {
95
95
/// The queue on which we run all our operations.
96
96
private let queue = DispatchQueue ( label: " io.swiftnio.AsyncEmbeddedEventLoop " )
97
97
98
+ private enum State : Int , AtomicValue { case open, closing, closed }
99
+ private let _state = ManagedAtomic ( State . open)
100
+ private var state : State { self . _state. load ( ordering: . relaxed) }
101
+
98
102
// This function must only be called on queue.
99
103
private func nextTaskNumber( ) -> UInt64 {
100
104
dispatchPrecondition ( condition: . onQueue( self . queue) )
@@ -150,6 +154,15 @@ public final class NIOAsyncTestingEventLoop: EventLoop, @unchecked Sendable {
150
154
let promise : EventLoopPromise < T > = self . makePromise ( )
151
155
let taskID = self . scheduledTaskCounter. loadThenWrappingIncrement ( ordering: . relaxed)
152
156
157
+ switch self . state {
158
+ case . open:
159
+ break
160
+ case . closing, . closed:
161
+ // If the event loop is shut down, or shutting down, immediately cancel the task.
162
+ promise. fail ( EventLoopError . cancelled)
163
+ return Scheduled ( promise: promise, cancellationTask: { } )
164
+ }
165
+
153
166
let scheduled = Scheduled (
154
167
promise: promise,
155
168
cancellationTask: {
@@ -187,27 +200,7 @@ public final class NIOAsyncTestingEventLoop: EventLoop, @unchecked Sendable {
187
200
} else {
188
201
self . queue. async {
189
202
self . scheduleTask ( deadline: self . now, task)
190
-
191
- var tasks = CircularBuffer < EmbeddedScheduledTask > ( )
192
- while let nextTask = self . scheduledTasks. peek ( ) {
193
- guard nextTask. readyTime <= self . now else {
194
- break
195
- }
196
-
197
- // Now we want to grab all tasks that are ready to execute at the same
198
- // time as the first.
199
- while let candidateTask = self . scheduledTasks. peek ( ) , candidateTask. readyTime == nextTask. readyTime
200
- {
201
- tasks. append ( candidateTask)
202
- self . scheduledTasks. pop ( )
203
- }
204
-
205
- for task in tasks {
206
- task. task ( )
207
- }
208
-
209
- tasks. removeAll ( keepingCapacity: true )
210
- }
203
+ self . _run ( )
211
204
}
212
205
}
213
206
}
@@ -233,41 +226,50 @@ public final class NIOAsyncTestingEventLoop: EventLoop, @unchecked Sendable {
233
226
///
234
227
/// - Note: If `deadline` is before the current time, the current time will not be advanced.
235
228
public func advanceTime( to deadline: NIODeadline ) async {
236
- await withCheckedContinuation { ( continuation: CheckedContinuation < Void , Never > ) in
229
+ await withCheckedContinuation { continuation in
237
230
self . queue. async {
238
- let newTime = max ( deadline, self . now)
239
-
240
- var tasks = CircularBuffer < EmbeddedScheduledTask > ( )
241
- while let nextTask = self . scheduledTasks. peek ( ) {
242
- guard nextTask. readyTime <= newTime else {
243
- break
244
- }
231
+ self . _advanceTime ( to: deadline)
232
+ continuation. resume ( )
233
+ }
234
+ }
235
+ }
245
236
246
- // Now we want to grab all tasks that are ready to execute at the same
247
- // time as the first.
248
- while let candidateTask = self . scheduledTasks. peek ( ) , candidateTask. readyTime == nextTask. readyTime
249
- {
250
- tasks. append ( candidateTask)
251
- self . scheduledTasks. pop ( )
252
- }
237
+ internal func _advanceTime( to deadline: NIODeadline ) {
238
+ dispatchPrecondition ( condition: . onQueue( self . queue) )
253
239
254
- // Set the time correctly before we call into user code, then
255
- // call in for all tasks.
256
- self . _now. store ( nextTask. readyTime. uptimeNanoseconds, ordering: . relaxed)
240
+ let newTime = max ( deadline, self . now)
257
241
258
- for task in tasks {
259
- task. task ( )
260
- }
242
+ var tasks = CircularBuffer < EmbeddedScheduledTask > ( )
243
+ while let nextTask = self . scheduledTasks. peek ( ) {
244
+ guard nextTask. readyTime <= newTime else {
245
+ break
246
+ }
261
247
262
- tasks. removeAll ( keepingCapacity: true )
263
- }
248
+ // Now we want to grab all tasks that are ready to execute at the same
249
+ // time as the first.
250
+ while let candidateTask = self . scheduledTasks. peek ( ) , candidateTask. readyTime == nextTask. readyTime {
251
+ tasks. append ( candidateTask)
252
+ self . scheduledTasks. pop ( )
253
+ }
264
254
265
- // Finally ensure we got the time right.
266
- self . _now. store ( newTime. uptimeNanoseconds, ordering: . relaxed)
255
+ // Set the time correctly before we call into user code, then
256
+ // call in for all tasks.
257
+ self . _now. store ( nextTask. readyTime. uptimeNanoseconds, ordering: . relaxed)
267
258
268
- continuation. resume ( )
259
+ for task in tasks {
260
+ task. task ( )
269
261
}
262
+
263
+ tasks. removeAll ( keepingCapacity: true )
270
264
}
265
+
266
+ // Finally ensure we got the time right.
267
+ self . _now. store ( newTime. uptimeNanoseconds, ordering: . relaxed)
268
+ }
269
+
270
+ internal func _run( ) {
271
+ dispatchPrecondition ( condition: . onQueue( self . queue) )
272
+ self . _advanceTime ( to: self . now)
271
273
}
272
274
273
275
/// Executes the given function in the context of this event loop. This is useful when it's necessary to be confident that an operation
@@ -293,6 +295,13 @@ public final class NIOAsyncTestingEventLoop: EventLoop, @unchecked Sendable {
293
295
}
294
296
}
295
297
298
+ internal func _cancelRemainingScheduledTasks( ) {
299
+ dispatchPrecondition ( condition: . onQueue( self . queue) )
300
+ while let task = self . scheduledTasks. pop ( ) {
301
+ task. fail ( EventLoopError . cancelled)
302
+ }
303
+ }
304
+
296
305
internal func drainScheduledTasksByRunningAllCurrentlyScheduledTasks( ) {
297
306
var currentlyScheduledTasks = self . scheduledTasks
298
307
while let nextTask = currentlyScheduledTasks. pop ( ) {
@@ -309,7 +318,8 @@ public final class NIOAsyncTestingEventLoop: EventLoop, @unchecked Sendable {
309
318
310
319
private func _shutdownGracefully( ) {
311
320
dispatchPrecondition ( condition: . onQueue( self . queue) )
312
- self . drainScheduledTasksByRunningAllCurrentlyScheduledTasks ( )
321
+ self . _run ( )
322
+ self . _cancelRemainingScheduledTasks ( )
313
323
}
314
324
315
325
/// - see: `EventLoop.shutdownGracefully`
@@ -324,9 +334,11 @@ public final class NIOAsyncTestingEventLoop: EventLoop, @unchecked Sendable {
324
334
325
335
/// The concurrency-aware equivalent of `shutdownGracefully(queue:_:)`.
326
336
public func shutdownGracefully( ) async {
327
- await withCheckedContinuation { ( continuation: CheckedContinuation < Void , Never > ) in
337
+ await withCheckedContinuation { continuation in
338
+ self . _state. store ( . closing, ordering: . relaxed)
328
339
self . queue. async {
329
340
self . _shutdownGracefully ( )
341
+ self . _state. store ( . closed, ordering: . relaxed)
330
342
continuation. resume ( )
331
343
}
332
344
}
0 commit comments