@@ -943,7 +943,8 @@ final class ServiceGroupTests: XCTestCase {
943
943
// Let's throw from the second service
944
944
await service2. resumeRunContinuation ( with: . failure( ExampleError ( ) ) )
945
945
946
- // The first service should still be running
946
+ // The first service should still be running but seeing a graceful shutdown signal firsts
947
+ await XCTAsyncAssertEqual ( await eventIterator1. next ( ) , . shutdownGracefully)
947
948
service1. sendPing ( )
948
949
await XCTAsyncAssertEqual ( await eventIterator1. next ( ) , . runPing)
949
950
@@ -1194,6 +1195,281 @@ final class ServiceGroupTests: XCTestCase {
1194
1195
}
1195
1196
}
1196
1197
1198
+
1199
+ func testTriggerGracefulShutdown_serviceThrows_inOrder_gracefullyShutdownGroup( ) async throws {
1200
+ let service1 = MockService ( description: " Service1 " )
1201
+ let service2 = MockService ( description: " Service2 " )
1202
+ let service3 = MockService ( description: " Service3 " )
1203
+ let serviceGroup = self . makeServiceGroup (
1204
+ services: [ . init( service: service1, failureTerminationBehavior: . gracefullyShutdownGroup) ,
1205
+ . init( service: service2, failureTerminationBehavior: . gracefullyShutdownGroup) ,
1206
+ . init( service: service3, failureTerminationBehavior: . gracefullyShutdownGroup) ]
1207
+ )
1208
+
1209
+ do {
1210
+ try await withThrowingTaskGroup ( of: Void . self) { group in
1211
+ group. addTask {
1212
+ try await serviceGroup. run ( )
1213
+ }
1214
+
1215
+ var eventIterator1 = service1. events. makeAsyncIterator ( )
1216
+ await XCTAsyncAssertEqual ( await eventIterator1. next ( ) , . run)
1217
+
1218
+ var eventIterator2 = service2. events. makeAsyncIterator ( )
1219
+ await XCTAsyncAssertEqual ( await eventIterator2. next ( ) , . run)
1220
+
1221
+ var eventIterator3 = service3. events. makeAsyncIterator ( )
1222
+ await XCTAsyncAssertEqual ( await eventIterator3. next ( ) , . run)
1223
+
1224
+ await serviceGroup. triggerGracefulShutdown ( )
1225
+
1226
+ // The last service should receive the shutdown signal first
1227
+ await XCTAsyncAssertEqual ( await eventIterator3. next ( ) , . shutdownGracefully)
1228
+
1229
+ // Waiting to see that all three are still running
1230
+ service1. sendPing ( )
1231
+ service2. sendPing ( )
1232
+ service3. sendPing ( )
1233
+ await XCTAsyncAssertEqual ( await eventIterator1. next ( ) , . runPing)
1234
+ await XCTAsyncAssertEqual ( await eventIterator2. next ( ) , . runPing)
1235
+ await XCTAsyncAssertEqual ( await eventIterator3. next ( ) , . runPing)
1236
+
1237
+ // Let's exit from the last service
1238
+ await service3. resumeRunContinuation ( with: . success( ( ) ) )
1239
+
1240
+ // The middle service should now receive the signal
1241
+ await XCTAsyncAssertEqual ( await eventIterator2. next ( ) , . shutdownGracefully)
1242
+
1243
+ // Waiting to see that the two remaining are still running
1244
+ service1. sendPing ( )
1245
+ service2. sendPing ( )
1246
+ await XCTAsyncAssertEqual ( await eventIterator1. next ( ) , . runPing)
1247
+ await XCTAsyncAssertEqual ( await eventIterator2. next ( ) , . runPing)
1248
+
1249
+ // Let's exit from the second service
1250
+ await service2. resumeRunContinuation ( with: . failure( ExampleError ( ) ) )
1251
+
1252
+ // The final service should now receive the signal
1253
+ await XCTAsyncAssertEqual ( await eventIterator1. next ( ) , . shutdownGracefully)
1254
+
1255
+ // Waiting to see that the one remaining are still running
1256
+ service1. sendPing ( )
1257
+ await XCTAsyncAssertEqual ( await eventIterator1. next ( ) , . runPing)
1258
+
1259
+ // Let's exit from the first service
1260
+ await service1. resumeRunContinuation ( with: . success( ( ) ) )
1261
+
1262
+ try await group. waitForAll ( )
1263
+ }
1264
+
1265
+ XCTFail ( " Expected error not thrown " )
1266
+ } catch is ExampleError {
1267
+ // expected error
1268
+ }
1269
+ }
1270
+
1271
+ func testTriggerGracefulShutdown_serviceThrows_inOrder_ignore( ) async throws {
1272
+ let service1 = MockService ( description: " Service1 " )
1273
+ let service2 = MockService ( description: " Service2 " )
1274
+ let service3 = MockService ( description: " Service3 " )
1275
+ let serviceGroup = self . makeServiceGroup (
1276
+ services: [ . init( service: service1, failureTerminationBehavior: . ignore) ,
1277
+ . init( service: service2, failureTerminationBehavior: . ignore) ,
1278
+ . init( service: service3, failureTerminationBehavior: . ignore) ]
1279
+ )
1280
+
1281
+ try await withThrowingTaskGroup ( of: Void . self) { group in
1282
+ group. addTask {
1283
+ try await serviceGroup. run ( )
1284
+ }
1285
+
1286
+ var eventIterator1 = service1. events. makeAsyncIterator ( )
1287
+ await XCTAsyncAssertEqual ( await eventIterator1. next ( ) , . run)
1288
+
1289
+ var eventIterator2 = service2. events. makeAsyncIterator ( )
1290
+ await XCTAsyncAssertEqual ( await eventIterator2. next ( ) , . run)
1291
+
1292
+ var eventIterator3 = service3. events. makeAsyncIterator ( )
1293
+ await XCTAsyncAssertEqual ( await eventIterator3. next ( ) , . run)
1294
+
1295
+ await serviceGroup. triggerGracefulShutdown ( )
1296
+
1297
+ // The last service should receive the shutdown signal first
1298
+ await XCTAsyncAssertEqual ( await eventIterator3. next ( ) , . shutdownGracefully)
1299
+
1300
+ // Waiting to see that all three are still running
1301
+ service1. sendPing ( )
1302
+ service2. sendPing ( )
1303
+ service3. sendPing ( )
1304
+ await XCTAsyncAssertEqual ( await eventIterator1. next ( ) , . runPing)
1305
+ await XCTAsyncAssertEqual ( await eventIterator2. next ( ) , . runPing)
1306
+ await XCTAsyncAssertEqual ( await eventIterator3. next ( ) , . runPing)
1307
+
1308
+ // Let's exit from the last service
1309
+ await service3. resumeRunContinuation ( with: . success( ( ) ) )
1310
+
1311
+ // The middle service should now receive the signal
1312
+ await XCTAsyncAssertEqual ( await eventIterator2. next ( ) , . shutdownGracefully)
1313
+
1314
+ // Waiting to see that the two remaining are still running
1315
+ service1. sendPing ( )
1316
+ service2. sendPing ( )
1317
+ await XCTAsyncAssertEqual ( await eventIterator1. next ( ) , . runPing)
1318
+ await XCTAsyncAssertEqual ( await eventIterator2. next ( ) , . runPing)
1319
+
1320
+ // Let's exit from the second service
1321
+ await service2. resumeRunContinuation ( with: . failure( ExampleError ( ) ) )
1322
+
1323
+ // The final service should now receive the signal
1324
+ await XCTAsyncAssertEqual ( await eventIterator1. next ( ) , . shutdownGracefully)
1325
+
1326
+ // Waiting to see that the one remaining are still running
1327
+ service1. sendPing ( )
1328
+ await XCTAsyncAssertEqual ( await eventIterator1. next ( ) , . runPing)
1329
+
1330
+ // Let's exit from the first service
1331
+ await service1. resumeRunContinuation ( with: . success( ( ) ) )
1332
+
1333
+ try await group. waitForAll ( )
1334
+ }
1335
+ }
1336
+
1337
+ func testTriggerGracefulShutdown_serviceThrows_outOfOrder_gracefullyShutdownGroup( ) async throws {
1338
+ let service1 = MockService ( description: " Service1 " )
1339
+ let service2 = MockService ( description: " Service2 " )
1340
+ let service3 = MockService ( description: " Service3 " )
1341
+ let serviceGroup = self . makeServiceGroup (
1342
+ services: [ . init( service: service1, failureTerminationBehavior: . gracefullyShutdownGroup) ,
1343
+ . init( service: service2, failureTerminationBehavior: . gracefullyShutdownGroup) ,
1344
+ . init( service: service3, failureTerminationBehavior: . gracefullyShutdownGroup) ]
1345
+ )
1346
+
1347
+ do {
1348
+ try await withThrowingTaskGroup ( of: Void . self) { group in
1349
+ group. addTask {
1350
+ try await serviceGroup. run ( )
1351
+ }
1352
+
1353
+ var eventIterator1 = service1. events. makeAsyncIterator ( )
1354
+ await XCTAsyncAssertEqual ( await eventIterator1. next ( ) , . run)
1355
+
1356
+ var eventIterator2 = service2. events. makeAsyncIterator ( )
1357
+ await XCTAsyncAssertEqual ( await eventIterator2. next ( ) , . run)
1358
+
1359
+ var eventIterator3 = service3. events. makeAsyncIterator ( )
1360
+ await XCTAsyncAssertEqual ( await eventIterator3. next ( ) , . run)
1361
+
1362
+ await serviceGroup. triggerGracefulShutdown ( )
1363
+
1364
+ // The last service should receive the shutdown signal first
1365
+ await XCTAsyncAssertEqual ( await eventIterator3. next ( ) , . shutdownGracefully)
1366
+
1367
+ // Waiting to see that all three are still running
1368
+ service1. sendPing ( )
1369
+ service2. sendPing ( )
1370
+ service3. sendPing ( )
1371
+ await XCTAsyncAssertEqual ( await eventIterator1. next ( ) , . runPing)
1372
+ await XCTAsyncAssertEqual ( await eventIterator2. next ( ) , . runPing)
1373
+ await XCTAsyncAssertEqual ( await eventIterator3. next ( ) , . runPing)
1374
+
1375
+ // Let's exit from the last service
1376
+ await service3. resumeRunContinuation ( with: . success( ( ) ) )
1377
+
1378
+ // The middle service should now receive the signal
1379
+ await XCTAsyncAssertEqual ( await eventIterator2. next ( ) , . shutdownGracefully)
1380
+
1381
+ // Waiting to see that the two remaining are still running
1382
+ service1. sendPing ( )
1383
+ service2. sendPing ( )
1384
+ await XCTAsyncAssertEqual ( await eventIterator1. next ( ) , . runPing)
1385
+ await XCTAsyncAssertEqual ( await eventIterator2. next ( ) , . runPing)
1386
+
1387
+ // Let's exit from the first service (even though the second service
1388
+ // is gracefully shutting down)
1389
+ await service1. resumeRunContinuation ( with: . failure( ExampleError ( ) ) )
1390
+
1391
+ // Waiting to see that the one remaining are still running
1392
+ service2. sendPing ( )
1393
+ await XCTAsyncAssertEqual ( await eventIterator2. next ( ) , . runPing)
1394
+
1395
+ // Let's exit from the second service
1396
+ await service2. resumeRunContinuation ( with: . success( ( ) ) )
1397
+
1398
+ // The first service shutdown will be skipped
1399
+ try await group. waitForAll ( )
1400
+ }
1401
+
1402
+ XCTFail ( " Expected error not thrown " )
1403
+ } catch is ExampleError {
1404
+ // expected error
1405
+ }
1406
+ }
1407
+
1408
+ func testTriggerGracefulShutdown_serviceThrows_outOfOrder_ignore( ) async throws {
1409
+ let service1 = MockService ( description: " Service1 " )
1410
+ let service2 = MockService ( description: " Service2 " )
1411
+ let service3 = MockService ( description: " Service3 " )
1412
+ let serviceGroup = self . makeServiceGroup (
1413
+ services: [ . init( service: service1, failureTerminationBehavior: . ignore) ,
1414
+ . init( service: service2, failureTerminationBehavior: . ignore) ,
1415
+ . init( service: service3, failureTerminationBehavior: . ignore) ]
1416
+ )
1417
+
1418
+ try await withThrowingTaskGroup ( of: Void . self) { group in
1419
+ group. addTask {
1420
+ try await serviceGroup. run ( )
1421
+ }
1422
+
1423
+ var eventIterator1 = service1. events. makeAsyncIterator ( )
1424
+ await XCTAsyncAssertEqual ( await eventIterator1. next ( ) , . run)
1425
+
1426
+ var eventIterator2 = service2. events. makeAsyncIterator ( )
1427
+ await XCTAsyncAssertEqual ( await eventIterator2. next ( ) , . run)
1428
+
1429
+ var eventIterator3 = service3. events. makeAsyncIterator ( )
1430
+ await XCTAsyncAssertEqual ( await eventIterator3. next ( ) , . run)
1431
+
1432
+ await serviceGroup. triggerGracefulShutdown ( )
1433
+
1434
+ // The last service should receive the shutdown signal first
1435
+ await XCTAsyncAssertEqual ( await eventIterator3. next ( ) , . shutdownGracefully)
1436
+
1437
+ // Waiting to see that all three are still running
1438
+ service1. sendPing ( )
1439
+ service2. sendPing ( )
1440
+ service3. sendPing ( )
1441
+ await XCTAsyncAssertEqual ( await eventIterator1. next ( ) , . runPing)
1442
+ await XCTAsyncAssertEqual ( await eventIterator2. next ( ) , . runPing)
1443
+ await XCTAsyncAssertEqual ( await eventIterator3. next ( ) , . runPing)
1444
+
1445
+ // Let's exit from the last service
1446
+ await service3. resumeRunContinuation ( with: . success( ( ) ) )
1447
+
1448
+ // The middle service should now receive the signal
1449
+ await XCTAsyncAssertEqual ( await eventIterator2. next ( ) , . shutdownGracefully)
1450
+
1451
+ // Waiting to see that the two remaining are still running
1452
+ service1. sendPing ( )
1453
+ service2. sendPing ( )
1454
+ await XCTAsyncAssertEqual ( await eventIterator1. next ( ) , . runPing)
1455
+ await XCTAsyncAssertEqual ( await eventIterator2. next ( ) , . runPing)
1456
+
1457
+ // Let's exit from the first service (even though the second service
1458
+ // is gracefully shutting down)
1459
+ await service1. resumeRunContinuation ( with: . failure( ExampleError ( ) ) )
1460
+
1461
+ // Waiting to see that the one remaining are still running
1462
+ service2. sendPing ( )
1463
+ await XCTAsyncAssertEqual ( await eventIterator2. next ( ) , . runPing)
1464
+
1465
+ // Let's exit from the second service
1466
+ await service2. resumeRunContinuation ( with: . success( ( ) ) )
1467
+
1468
+ // The first service shutdown will be skipped
1469
+ try await group. waitForAll ( )
1470
+ }
1471
+ }
1472
+
1197
1473
// MARK: - Helpers
1198
1474
1199
1475
private func makeServiceGroup(
0 commit comments