File: ProcessSet.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 (128 lines) | stat: -rw-r--r-- 4,227 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
/*
 This source file is part of the Swift.org open source project
 
 Copyright (c) 2014 - 2017 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
*/

import Dispatch
import Foundation

public enum ProcessSetError: Swift.Error {
    /// The process group was cancelled and doesn't allow adding more processes.
    case cancelled
}

/// A process set is a small wrapper for collection of processes.
/// 
/// This class is thread safe.
@available(*, deprecated, message: "Use `TaskGroup` with async `Process` APIs instead")
public final class ProcessSet {

    /// Array to hold the processes.
    private var processes: Set<Process> = []

    /// Queue to mutate internal states of the process group.
    private let serialQueue = DispatchQueue(label: "org.swift.swiftpm.process-set")

    /// If the process group was asked to cancel all active processes.
    private var cancelled = false

    /// The timeout (in seconds) after which the processes should be killed if they don't respond to SIGINT.
    public let killTimeout: Double

    /// Condition to block kill thread until timeout.
    private var killingCondition = Condition()

    /// Boolean predicate for killing condition.
    private var shouldKill = false

    /// Create a process set.
    public init(killTimeout: Double = 5) {
        self.killTimeout = killTimeout
    }

    /// Add a process to the process set. This method will throw if the process set is terminated using the terminate()
    /// method.
    ///
    /// Call remove() method to remove the process from set once it has terminated.
    ///
    /// - Parameters:
    ///   - process: The process to add.
    /// - Throws: ProcessGroupError
    public func add(_ process: TSCBasic.Process) throws {
        return try serialQueue.sync {
            guard !cancelled else {
                throw ProcessSetError.cancelled
            }
            self.processes.insert(process)
        }
    }

    /// Terminate all the processes. This method blocks until all processes in the set are terminated.
    ///
    /// A process set cannot be used once it has been asked to terminate.
//    #if compiler(>=5.8)
//    @available(*, noasync)
//    #endif
    public func terminate() {
        // Mark a process set as cancelled.
        serialQueue.sync {
            cancelled = true
        }

        // Interrupt all processes.
        signalAll(SIGINT)

        // Create a thread that will kill all processes after a timeout.
        let thread = TSCBasic.Thread {
            // Compute the timeout date.
            let timeout = Date() + self.killTimeout
            // Block until we timeout or notification.
            self.killingCondition.whileLocked {
                while !self.shouldKill {
                    // Block until timeout expires.
                    let timeLimitReached = !self.killingCondition.wait(until: timeout)
                    // Set should kill to true if time limit was reached.
                    if timeLimitReached {
                        self.shouldKill = true
                    }
                }
            }
            // Send kill signal to all processes.
          #if os(Windows)
            self.signalAll(SIGTERM)
          #else
            self.signalAll(SIGKILL)
          #endif
        }

        thread.start()

        // Wait until all processes terminate and notify the kill thread
        // if everyone exited to avoid waiting till timeout.
        for process in self.processes {
            _ = try? process.waitUntilExit()
        }
        killingCondition.whileLocked {
            shouldKill = true
            killingCondition.signal()
        }

        // Join the kill thread so we don't exit before everything terminates.
        thread.join()
    }

    /// Sends signal to all processes in the set.
    private func signalAll(_ signal: Int32) {
        serialQueue.sync {
            // Signal all active processes.
            for process in self.processes {
                process.signal(signal)
            }
        }
    }
}