Terminate subprocesses of macOS command line tool in Swift

≯℡__Kan透↙ 提交于 2019-12-22 01:09:10

问题


I'm writing a macOS command line tool in swift which executes shell commands:

let process = Process()
process.launchPath = "/bin/sleep"
process.arguments = ["100"]
process.launch()
process.waitUntilExit()

However, if an interrupt (CTRL-C) or a terminate signal gets sent to my program, these shell commands don't get terminated and just continue with their execution.

Is there a way to automatically terminate them if my program gets terminated unexpectedly?


回答1:


Here is what we did in order to react on interrupt (CTRL-C) when using two piped subprocesses.

Idea behind: Blocking waitUntilExit() call replaced with async terminationHandler. Infinite loop dispatchMain() used to serve dispatch events. On receiving Interrupt signal we calling interrupt() on subprocesses.

Example class which incapsulates subprocess launch and interrupt logic:

class AppTester: Builder {

   private var processes: [Process] = [] // Keeps references to launched processes.

   func test(completion: @escaping (Int32) -> Void) {

      let xcodebuildProcess = Process(executableName: "xcodebuild", arguments: ...)
      let xcprettyProcess = Process(executableName: "xcpretty", arguments: ...)

      // Organising pipe between processes. Like `xcodebuild ... | xcpretty` in shell
      let pipe = Pipe()
      xcodebuildProcess.standardOutput = pipe
      xcprettyProcess.standardInput = pipe

      // Assigning `terminationHandler` for needed subprocess.
      processes.append(xcodebuildProcess)
      xcodebuildProcess.terminationHandler = { process in
         completion(process.terminationStatus)
      }

      xcodebuildProcess.launch()
      xcprettyProcess.launch()
      // Note. We should not use blocking `waitUntilExit()` call.
   }

   func interrupt() {
      // Interrupting running processes (if any).
      processes.filter { $0.isRunning }.forEach { $0.interrupt() }
   }
}

Usage (i.e. main.swift):

let tester = AppTester(...)
tester.test(....) {
   if $0 == EXIT_SUCCESS {
      // Do some other work.
   } else {
      exit($0)
   }
}

// Making Interrupt signal listener.
let source = DispatchSource.makeSignalSource(signal: SIGINT)
source.setEventHandler {
   tester.interrupt() // Will interrupt running processes (if any).
   exit(SIGINT)
}
source.resume()
dispatchMain() // Starting dispatch loop. This function never returns.

Example output in shell:

...
▸ Running script 'Run Script: Verify Sources'
▸ Processing Framework-Info.plist
▸ Running script 'Run Script: Verify Sources'
▸ Linking AppTestability
^C** BUILD INTERRUPTED **


来源:https://stackoverflow.com/questions/52786443/terminate-subprocesses-of-macos-command-line-tool-in-swift

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!