@@ -14,20 +14,94 @@ import Dispatch
1414import Foundation
1515import TSCBasic
1616
17+ #if canImport(WinSDK)
18+ import WinSDK
19+ #endif
20+
1721public typealias CancellationHandler = ( DispatchTime ) throws -> Void
1822
19- public class Cancellator : Cancellable {
23+ public final class Cancellator : Cancellable {
2024 public typealias RegistrationKey = String
2125
2226 private let observabilityScope : ObservabilityScope ?
2327 private let registry = ThreadSafeKeyValueStore < String , ( name: String , handler: CancellationHandler ) > ( )
2428 private let cancelationQueue = DispatchQueue ( label: " org.swift.swiftpm.cancellator " , qos: . userInteractive, attributes: . concurrent)
2529 private let cancelling = ThreadSafeBox < Bool > ( false )
2630
31+ private static let signalHandlerLock = NSLock ( )
32+ private static var isSignalHandlerInstalled = false
33+
2734 public init ( observabilityScope: ObservabilityScope ? ) {
2835 self . observabilityScope = observabilityScope
2936 }
3037
38+ #if os(Windows)
39+ // unfortunately this is needed for C callback handlers used by Windows shutdown handler
40+ static var shared : Cancellator ?
41+ #endif
42+
43+ /// Installs signal handlers to terminate sub-processes on cancellation.
44+ public func installSignalHandlers( ) {
45+ Self . signalHandlerLock. withLock {
46+ precondition ( !Self. isSignalHandlerInstalled)
47+
48+ #if os(Windows)
49+ // Closures passed to `SetConsoleCtrlHandler` can't capture context, working around that with a global.
50+ Self . shared = self
51+
52+ // set shutdown handler to terminate sub-processes, etc
53+ _ = SetConsoleCtrlHandler ( { _ in
54+ // Terminate all processes on receiving an interrupt signal.
55+ try ? Cancellator . shared? . cancel ( deadline: . now( ) + . seconds( 30 ) )
56+
57+ // Reset the handler.
58+ _ = SetConsoleCtrlHandler ( nil , false )
59+
60+ // Exit as if by signal()
61+ TerminateProcess ( GetCurrentProcess ( ) , 3 )
62+
63+ return true
64+ } , true )
65+ #else
66+ // trap SIGINT to terminate sub-processes, etc
67+ signal ( SIGINT, SIG_IGN)
68+ let interruptSignalSource = DispatchSource . makeSignalSource ( signal: SIGINT)
69+ interruptSignalSource. setEventHandler { [ weak self] in
70+ // cancel the trap?
71+ interruptSignalSource. cancel ( )
72+
73+ // Terminate all processes on receiving an interrupt signal.
74+ try ? self ? . cancel ( deadline: . now( ) + . seconds( 30 ) )
75+
76+ #if os(macOS) || os(OpenBSD)
77+ // Install the default signal handler.
78+ var action = sigaction ( )
79+ action. __sigaction_u. __sa_handler = SIG_DFL
80+ sigaction ( SIGINT, & action, nil )
81+ kill ( getpid ( ) , SIGINT)
82+ #elseif os(Android)
83+ // Install the default signal handler.
84+ var action = sigaction ( )
85+ action. sa_handler = SIG_DFL
86+ sigaction ( SIGINT, & action, nil )
87+ kill ( getpid ( ) , SIGINT)
88+ #else
89+ var action = sigaction ( )
90+ action. __sigaction_handler = unsafeBitCast (
91+ SIG_DFL,
92+ to: sigaction. __Unnamed_union___sigaction_handler. self
93+ )
94+ sigaction ( SIGINT, & action, nil )
95+ kill ( getpid ( ) , SIGINT)
96+ #endif
97+ }
98+ interruptSignalSource. resume ( )
99+ #endif
100+
101+ Self . isSignalHandlerInstalled = true
102+ }
103+ }
104+
31105 @discardableResult
32106 public func register( name: String , handler: @escaping CancellationHandler ) -> RegistrationKey ? {
33107 if self . cancelling. get ( default: false ) {
0 commit comments