@@ -33,8 +33,10 @@ public actor ServiceGroup: Sendable {
33
33
private let logger : Logger
34
34
/// The logging configuration.
35
35
private let loggingConfiguration : ServiceGroupConfiguration . LoggingConfiguration
36
- /// The escalation configuration.
37
- private let escalationConfiguration : ServiceGroupConfiguration . EscalationBehaviour
36
+ /// The maximum amount of time that graceful shutdown is allowed to take.
37
+ private var maximumGracefulShutdownDuration : ( secondsComponent: Int64 , attosecondsComponent: Int64 ) ?
38
+ /// The maximum amount of time that task cancellation is allowed to take.
39
+ private var maximumCancellationDuration : ( secondsComponent: Int64 , attosecondsComponent: Int64 ) ?
38
40
/// The signals that lead to graceful shutdown.
39
41
private let gracefulShutdownSignals : [ UnixSignal ]
40
42
/// The signals that lead to cancellation.
@@ -59,7 +61,8 @@ public actor ServiceGroup: Sendable {
59
61
self . cancellationSignals = configuration. cancellationSignals
60
62
self . logger = configuration. logger
61
63
self . loggingConfiguration = configuration. logging
62
- self . escalationConfiguration = configuration. escalation
64
+ self . maximumGracefulShutdownDuration = configuration. _maximumCancellationDuration
65
+ self . maximumCancellationDuration = configuration. _maximumCancellationDuration
63
66
}
64
67
65
68
/// Initializes a new ``ServiceGroup``.
@@ -97,7 +100,8 @@ public actor ServiceGroup: Sendable {
97
100
self . cancellationSignals = configuration. cancellationSignals
98
101
self . logger = logger
99
102
self . loggingConfiguration = configuration. logging
100
- self . escalationConfiguration = configuration. escalation
103
+ self . maximumGracefulShutdownDuration = configuration. _maximumCancellationDuration
104
+ self . maximumCancellationDuration = configuration. _maximumCancellationDuration
101
105
}
102
106
103
107
/// Runs all the services by spinning up a child task per service.
@@ -310,7 +314,7 @@ public actor ServiceGroup: Sendable {
310
314
self . loggingConfiguration. keys. serviceKey: " \( service. service) " ,
311
315
]
312
316
)
313
- cancellationTimeoutTask = self . cancelGroupAndSpawnTimeoutIfNeeded ( group: & group)
317
+ self . cancelGroupAndSpawnTimeoutIfNeeded ( group: & group, cancellationTimeoutTask : & cancellationTimeoutTask )
314
318
return . failure( ServiceGroupError . serviceFinishedUnexpectedly ( ) )
315
319
316
320
case . gracefullyShutdownGroup:
@@ -345,7 +349,7 @@ public actor ServiceGroup: Sendable {
345
349
self . logger. debug (
346
350
" All services finished. "
347
351
)
348
- cancellationTimeoutTask = self . cancelGroupAndSpawnTimeoutIfNeeded ( group: & group)
352
+ self . cancelGroupAndSpawnTimeoutIfNeeded ( group: & group, cancellationTimeoutTask : & cancellationTimeoutTask )
349
353
return . success( ( ) )
350
354
}
351
355
}
@@ -360,7 +364,7 @@ public actor ServiceGroup: Sendable {
360
364
self . loggingConfiguration. keys. errorKey: " \( serviceError) " ,
361
365
]
362
366
)
363
- cancellationTimeoutTask = self . cancelGroupAndSpawnTimeoutIfNeeded ( group: & group)
367
+ self . cancelGroupAndSpawnTimeoutIfNeeded ( group: & group, cancellationTimeoutTask : & cancellationTimeoutTask )
364
368
return . failure( serviceError)
365
369
366
370
case . gracefullyShutdownGroup:
@@ -400,7 +404,7 @@ public actor ServiceGroup: Sendable {
400
404
" All services finished. "
401
405
)
402
406
403
- cancellationTimeoutTask = self . cancelGroupAndSpawnTimeoutIfNeeded ( group: & group)
407
+ self . cancelGroupAndSpawnTimeoutIfNeeded ( group: & group, cancellationTimeoutTask : & cancellationTimeoutTask )
404
408
return . success( ( ) )
405
409
}
406
410
}
@@ -433,7 +437,7 @@ public actor ServiceGroup: Sendable {
433
437
]
434
438
)
435
439
436
- cancellationTimeoutTask = self . cancelGroupAndSpawnTimeoutIfNeeded ( group: & group)
440
+ self . cancelGroupAndSpawnTimeoutIfNeeded ( group: & group, cancellationTimeoutTask : & cancellationTimeoutTask )
437
441
}
438
442
439
443
case . gracefulShutdownCaught:
@@ -455,7 +459,7 @@ public actor ServiceGroup: Sendable {
455
459
// We caught cancellation in our child task so we have to spawn
456
460
// our cancellation timeout task if needed
457
461
self . logger. debug ( " Caught cancellation. " )
458
- cancellationTimeoutTask = self . cancelGroupAndSpawnTimeoutIfNeeded ( group: & group)
462
+ self . cancelGroupAndSpawnTimeoutIfNeeded ( group: & group, cancellationTimeoutTask : & cancellationTimeoutTask )
459
463
460
464
case . signalSequenceFinished, . gracefulShutdownFinished:
461
465
// This can happen when we are either cancelling everything or
@@ -491,9 +495,12 @@ public actor ServiceGroup: Sendable {
491
495
fatalError ( " Unexpected state " )
492
496
}
493
497
494
- if #available( macOS 13 . 0 , * ) , let maximumGracefulShutdownDuration = self . escalationConfiguration . maximumGracefulShutdownDuration {
498
+ if #available( macOS 13 . 0 , iOS 16 . 0 , watchOS 9 . 0 , tvOS 16 . 0 , * ) , let maximumGracefulShutdownDuration = self . maximumGracefulShutdownDuration {
495
499
group. addTask {
496
- try await Task . sleep ( for: maximumGracefulShutdownDuration)
500
+ try await Task . sleep ( for: Duration (
501
+ secondsComponent: maximumGracefulShutdownDuration. secondsComponent,
502
+ attosecondsComponent: maximumGracefulShutdownDuration. attosecondsComponent
503
+ ) )
497
504
return . gracefulShutdownTimedOut
498
505
}
499
506
}
@@ -549,7 +556,7 @@ public actor ServiceGroup: Sendable {
549
556
]
550
557
)
551
558
552
- cancellationTimeoutTask = self . cancelGroupAndSpawnTimeoutIfNeeded ( group: & group)
559
+ self . cancelGroupAndSpawnTimeoutIfNeeded ( group: & group, cancellationTimeoutTask : & cancellationTimeoutTask )
553
560
throw ServiceGroupError . serviceFinishedUnexpectedly ( )
554
561
}
555
562
@@ -601,7 +608,7 @@ public actor ServiceGroup: Sendable {
601
608
]
602
609
)
603
610
604
- cancellationTimeoutTask = self . cancelGroupAndSpawnTimeoutIfNeeded ( group: & group)
611
+ self . cancelGroupAndSpawnTimeoutIfNeeded ( group: & group, cancellationTimeoutTask : & cancellationTimeoutTask )
605
612
}
606
613
607
614
case . gracefulShutdownTimedOut:
@@ -613,13 +620,13 @@ public actor ServiceGroup: Sendable {
613
620
self . loggingConfiguration. keys. serviceKey: " \( service. service) " ,
614
621
]
615
622
)
616
- cancellationTimeoutTask = self . cancelGroupAndSpawnTimeoutIfNeeded ( group: & group)
623
+ self . cancelGroupAndSpawnTimeoutIfNeeded ( group: & group, cancellationTimeoutTask : & cancellationTimeoutTask )
617
624
618
625
case . cancellationCaught:
619
626
// We caught cancellation in our child task so we have to spawn
620
627
// our cancellation timeout task if needed
621
628
self . logger. debug ( " Caught cancellation. " )
622
- cancellationTimeoutTask = self . cancelGroupAndSpawnTimeoutIfNeeded ( group: & group)
629
+ self . cancelGroupAndSpawnTimeoutIfNeeded ( group: & group, cancellationTimeoutTask : & cancellationTimeoutTask )
623
630
624
631
case . signalSequenceFinished, . gracefulShutdownCaught, . gracefulShutdownFinished:
625
632
// We just have to tolerate this since signals and parent graceful shutdowns downs can race.
@@ -645,19 +652,29 @@ public actor ServiceGroup: Sendable {
645
652
}
646
653
647
654
private func cancelGroupAndSpawnTimeoutIfNeeded(
648
- group: inout ThrowingTaskGroup < ChildTaskResult , Error >
649
- ) -> Task < Void , Never > ? {
655
+ group: inout ThrowingTaskGroup < ChildTaskResult , Error > ,
656
+ cancellationTimeoutTask: inout Task < Void , Never > ?
657
+ ) {
658
+ guard cancellationTimeoutTask == nil else {
659
+ // We already have a cancellation timeout task running.
660
+ return
661
+ }
650
662
group. cancelAll ( )
651
- if #available( macOS 13 . 0 , iOS 16 . 0 , watchOS 9 . 0 , tvOS 16 . 0 , * ) , let maximumCancellationDuration = self . escalationConfiguration. maximumCancellationDuration {
663
+
664
+
665
+ if #available( macOS 13 . 0 , iOS 16 . 0 , watchOS 9 . 0 , tvOS 16 . 0 , * ) , let maximumCancellationDuration = self . maximumCancellationDuration {
652
666
// We have to spawn an unstructured task here because the call to our `run`
653
667
// method might have already been cancelled and we need to protect the sleep
654
668
// from being cancelled.
655
- return Task {
669
+ cancellationTimeoutTask = Task {
656
670
do {
657
671
self . logger. debug (
658
672
" Task cancellation timeout task started. "
659
673
)
660
- try await Task . sleep ( for: maximumCancellationDuration)
674
+ try await Task . sleep ( for: Duration (
675
+ secondsComponent: maximumCancellationDuration. secondsComponent,
676
+ attosecondsComponent: maximumCancellationDuration. attosecondsComponent
677
+ ) )
661
678
self . logger. debug (
662
679
" Cancellation took longer than allowed by the configuration. "
663
680
)
@@ -667,7 +684,7 @@ public actor ServiceGroup: Sendable {
667
684
}
668
685
}
669
686
} else {
670
- return nil
687
+ cancellationTimeoutTask = nil
671
688
}
672
689
}
673
690
}
0 commit comments