mac: stop leaking ssh processes on quit
parent
7aefcab8b0
commit
e7cdac90f5
|
|
@ -234,9 +234,15 @@ actor AgentRPC {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func shutdown() async {
|
||||||
|
await self.stop()
|
||||||
|
}
|
||||||
|
|
||||||
private func stop() async {
|
private func stop() async {
|
||||||
self.stdoutHandle?.readabilityHandler = nil
|
self.stdoutHandle?.readabilityHandler = nil
|
||||||
self.process?.terminate()
|
let proc = self.process
|
||||||
|
proc?.terminate()
|
||||||
|
proc?.waitUntilExit()
|
||||||
self.process = nil
|
self.process = nil
|
||||||
self.stdinHandle = nil
|
self.stdinHandle = nil
|
||||||
self.stdoutHandle = nil
|
self.stdoutHandle = nil
|
||||||
|
|
|
||||||
|
|
@ -111,6 +111,8 @@ final class AppDelegate: NSObject, NSApplicationDelegate, NSXPCListenerDelegate
|
||||||
func applicationWillTerminate(_ notification: Notification) {
|
func applicationWillTerminate(_ notification: Notification) {
|
||||||
RelayProcessManager.shared.stop()
|
RelayProcessManager.shared.stop()
|
||||||
PresenceReporter.shared.stop()
|
PresenceReporter.shared.stop()
|
||||||
|
WebChatManager.shared.close()
|
||||||
|
Task { await AgentRPC.shared.shutdown() }
|
||||||
}
|
}
|
||||||
|
|
||||||
@MainActor
|
@MainActor
|
||||||
|
|
|
||||||
|
|
@ -14,6 +14,7 @@ final class WebChatWindowController: NSWindowController, WKNavigationDelegate {
|
||||||
private var baseEndpoint: URL?
|
private var baseEndpoint: URL?
|
||||||
private let remotePort: Int
|
private let remotePort: Int
|
||||||
private var reachabilityTask: Task<Void, Never>?
|
private var reachabilityTask: Task<Void, Never>?
|
||||||
|
private var tunnelRestartEnabled = false
|
||||||
|
|
||||||
init(sessionKey: String) {
|
init(sessionKey: String) {
|
||||||
webChatLogger.debug("init WebChatWindowController sessionKey=\(sessionKey, privacy: .public)")
|
webChatLogger.debug("init WebChatWindowController sessionKey=\(sessionKey, privacy: .public)")
|
||||||
|
|
@ -46,7 +47,7 @@ final class WebChatWindowController: NSWindowController, WKNavigationDelegate {
|
||||||
|
|
||||||
@MainActor deinit {
|
@MainActor deinit {
|
||||||
self.reachabilityTask?.cancel()
|
self.reachabilityTask?.cancel()
|
||||||
self.tunnel?.terminate()
|
self.stopTunnel(allowRestart: false)
|
||||||
}
|
}
|
||||||
|
|
||||||
private func loadPlaceholder() {
|
private func loadPlaceholder() {
|
||||||
|
|
@ -125,14 +126,16 @@ final class WebChatWindowController: NSWindowController, WKNavigationDelegate {
|
||||||
|
|
||||||
private func startOrRestartTunnel() async throws -> URL {
|
private func startOrRestartTunnel() async throws -> URL {
|
||||||
// Kill existing tunnel if any
|
// Kill existing tunnel if any
|
||||||
self.tunnel?.terminate()
|
self.stopTunnel(allowRestart: false)
|
||||||
|
|
||||||
let tunnel = try await WebChatTunnel.create(remotePort: self.remotePort, preferredLocalPort: 18_788)
|
let tunnel = try await WebChatTunnel.create(remotePort: self.remotePort, preferredLocalPort: 18_788)
|
||||||
self.tunnel = tunnel
|
self.tunnel = tunnel
|
||||||
|
self.tunnelRestartEnabled = true
|
||||||
|
|
||||||
// Auto-restart on unexpected termination while window lives
|
// Auto-restart on unexpected termination while window lives
|
||||||
tunnel.process.terminationHandler = { [weak self] _ in
|
tunnel.process.terminationHandler = { [weak self] _ in
|
||||||
guard let self else { return }
|
guard let self else { return }
|
||||||
|
guard self.tunnelRestartEnabled else { return }
|
||||||
webChatLogger.error("webchat tunnel terminated; restarting")
|
webChatLogger.error("webchat tunnel terminated; restarting")
|
||||||
Task { @MainActor [weak self] in
|
Task { @MainActor [weak self] in
|
||||||
guard let self else { return }
|
guard let self else { return }
|
||||||
|
|
@ -152,6 +155,12 @@ final class WebChatWindowController: NSWindowController, WKNavigationDelegate {
|
||||||
return URL(string: "http://127.0.0.1:\(port)/")!
|
return URL(string: "http://127.0.0.1:\(port)/")!
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private func stopTunnel(allowRestart: Bool) {
|
||||||
|
self.tunnelRestartEnabled = allowRestart
|
||||||
|
self.tunnel?.terminate()
|
||||||
|
self.tunnel = nil
|
||||||
|
}
|
||||||
|
|
||||||
private func showError(_ text: String) {
|
private func showError(_ text: String) {
|
||||||
let html = """
|
let html = """
|
||||||
<html><body style='font-family:-apple-system;padding:24px;color:#c00'>Web chat failed to connect.<br><br>\(text)</body></html>
|
<html><body style='font-family:-apple-system;padding:24px;color:#c00'>Web chat failed to connect.<br><br>\(text)</body></html>
|
||||||
|
|
@ -159,6 +168,11 @@ final class WebChatWindowController: NSWindowController, WKNavigationDelegate {
|
||||||
self.webView.loadHTMLString(html, baseURL: nil)
|
self.webView.loadHTMLString(html, baseURL: nil)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func shutdown() {
|
||||||
|
self.reachabilityTask?.cancel()
|
||||||
|
self.stopTunnel(allowRestart: false)
|
||||||
|
}
|
||||||
|
|
||||||
func webView(_ webView: WKWebView, didFinish navigation: WKNavigation!) {
|
func webView(_ webView: WKWebView, didFinish navigation: WKNavigation!) {
|
||||||
webChatLogger.debug("didFinish navigation url=\(webView.url?.absoluteString ?? "nil", privacy: .public)")
|
webChatLogger.debug("didFinish navigation url=\(webView.url?.absoluteString ?? "nil", privacy: .public)")
|
||||||
}
|
}
|
||||||
|
|
@ -189,6 +203,12 @@ final class WebChatManager {
|
||||||
self.controller?.window?.makeKeyAndOrderFront(nil)
|
self.controller?.window?.makeKeyAndOrderFront(nil)
|
||||||
NSApp.activate(ignoringOtherApps: true)
|
NSApp.activate(ignoringOtherApps: true)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func close() {
|
||||||
|
self.controller?.shutdown()
|
||||||
|
self.controller?.close()
|
||||||
|
self.controller = nil
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// MARK: - Port forwarding tunnel
|
// MARK: - Port forwarding tunnel
|
||||||
|
|
@ -209,6 +229,7 @@ final class WebChatTunnel {
|
||||||
func terminate() {
|
func terminate() {
|
||||||
if self.process.isRunning {
|
if self.process.isRunning {
|
||||||
self.process.terminate()
|
self.process.terminate()
|
||||||
|
self.process.waitUntilExit()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue