File: InterruptHandler.swift

package info (click to toggle)
swiftlang 6.0.3-2
  • links: PTS, VCS
  • area: main
  • in suites: forky, sid, trixie
  • size: 2,519,992 kB
  • sloc: cpp: 9,107,863; ansic: 2,040,022; asm: 1,135,751; python: 296,500; objc: 82,456; f90: 60,502; lisp: 34,951; pascal: 19,946; sh: 18,133; perl: 7,482; ml: 4,937; javascript: 4,117; makefile: 3,840; awk: 3,535; xml: 914; fortran: 619; cs: 573; ruby: 573
file content (141 lines) | stat: -rw-r--r-- 4,975 bytes parent folder | download
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
/*
 This source file is part of the Swift.org open source project

 Copyright 2016 Apple Inc. and the Swift project authors
 Licensed under Apache License v2.0 with Runtime Library Exception

 See http://swift.org/LICENSE.txt for license information
 See http://swift.org/CONTRIBUTORS.txt for Swift project authors
*/

#if !canImport(Musl)

import TSCLibc
import TSCBasic

/// This class can be used by command line tools to install a handler which
/// should be called when a interrupt signal is delivered to the process.
@available(*, deprecated, message: "use DispatchSource instead")
public final class InterruptHandler {
    /// Interrupt signal handling global variables
    private static var wasInterrupted = false
    private static var wasInterruptedLock = Lock()
    #if os(Windows)
    private static var signalWatchingPipe: [HANDLE] = [INVALID_HANDLE_VALUE, INVALID_HANDLE_VALUE]
    #else
    private static var signalWatchingPipe: [Int32] = [0, 0]
    private static var oldAction = sigaction()
    #endif

    /// The thread which waits to be notified when a signal is received.
    let thread: Thread
  #if os(Windows)
    let signalHandler: @convention(c)(UInt32) -> WindowsBool
  #else
    let signalHandler: @convention(c)(Int32) -> Void
  #endif

    /// Start watching for interrupt signal and call the handler whenever the signal is received.
    public init(_ handler: @escaping () -> Void) throws {
        // Swift reserves the right to lazily-initialize globals & statics
        // force initialize the statics so they as signal-safe
        _ = Self.wasInterrupted
        _ = Self.wasInterruptedLock
        _ = Self.signalWatchingPipe
#if !os(Windows)
        _ = Self.oldAction
#endif

        // Create a signal handler.
        self.signalHandler = { _ in
            // Turn on the interrupt bool.
            InterruptHandler.wasInterruptedLock.withLock {
                InterruptHandler.wasInterrupted = true
            }
            // Write on pipe to notify the watching thread.
            var byte: UInt8 = 0
          #if os(Windows)
            var bytesWritten: DWORD = 0
            WriteFile(InterruptHandler.signalWatchingPipe[1], &byte, 1, &bytesWritten, nil)
            return true
          #else
            write(InterruptHandler.signalWatchingPipe[1], &byte, 1)
          #endif
        }
      #if os(Windows)
        SetConsoleCtrlHandler(signalHandler, true)

        var readPipe: HANDLE?
        var writePipe: HANDLE?
        let rv = CreatePipe(&readPipe, &writePipe, nil, 1)
        Self.signalWatchingPipe = [readPipe!, writePipe!]
        guard rv else {
            throw SystemError.pipe(Int32(GetLastError()))
        }
      #else
        var action = sigaction()
      #if canImport(Darwin) || os(OpenBSD)
        action.__sigaction_u.__sa_handler = self.signalHandler
      #elseif os(Android)
        action.sa_handler = self.signalHandler
      #else
        action.__sigaction_handler = unsafeBitCast(
            self.signalHandler,
            to: sigaction.__Unnamed_union___sigaction_handler.self)
      #endif
        // Install the new handler.
        sigaction(SIGINT, &action, &Self.oldAction)
        // Create pipe.
        let rv = TSCLibc.pipe(&Self.signalWatchingPipe)
        guard rv == 0 else {
            throw SystemError.pipe(rv)
        }
      #endif

        // This thread waits to be notified via pipe. If something is read from pipe, check the interrupt bool
        // and send termination signal to all spawned processes in the process group.
        thread = Thread {
            while true {
                var buf: Int8 = 0
              #if os(Windows)
                var n: DWORD = 0
                ReadFile(Self.signalWatchingPipe[1], &buf, 1, &n, nil)
              #else
                let n = read(Self.signalWatchingPipe[0], &buf, 1)
              #endif
                // Pipe closed, nothing to do.
                if n == 0 { break }
                // Read the value of wasInterrupted and set it to false.
                let wasInt = Self.wasInterruptedLock.withLock { () -> Bool in
                    let oldValue = Self.wasInterrupted
                    Self.wasInterrupted = false
                    return oldValue
                }
                // Terminate all processes if was interrupted.
                if wasInt {
                    handler()
                }
            }
          #if os(Windows)
            CloseHandle(Self.signalWatchingPipe[0])
          #else
            close(Self.signalWatchingPipe[0])
          #endif
        }
        thread.start()
    }

    deinit {
      #if os(Windows)
        SetConsoleCtrlHandler(self.signalHandler, false)
        CloseHandle(Self.signalWatchingPipe[1])
      #else
        // Restore the old action and close the write end of pipe.
        sigaction(SIGINT, &Self.oldAction, nil)
        close(Self.signalWatchingPipe[1])
      #endif
        thread.join()
    }
}

#endif