Fix CampusNAS recovery after VPN loss

This commit is contained in:
2026-08-13 01:15:14 +06:00
parent cd53b6a1a1
commit b08f0e8cf1
3 changed files with 53 additions and 5 deletions
+16 -3
View File
@@ -15,6 +15,19 @@ private final class ResumeOnce: @unchecked Sendable {
}
}
/// `Process.terminate()` only sends SIGTERM. A wedged network client (notably
/// `smbutil` after a tunnel disappears) may ignore it and keep the pipe open,
/// leaving the reader thread in `readDataToEndOfFile()` forever. Escalate to
/// SIGKILL after a short grace period so every probe is actually bounded.
private func stop(_ process: Process) {
guard process.isRunning else { return }
process.terminate()
let pid = process.processIdentifier
DispatchQueue.global().asyncAfter(deadline: .now() + 1) {
if process.isRunning { Darwin.kill(pid, SIGKILL) }
}
}
enum Probe {
/// Runs a command and reports whether it exited 0. A process that outlives
/// `timeout` is killed and treated as failure — the VPN wrapper can block on
@@ -51,7 +64,7 @@ enum Probe {
DispatchQueue.global().asyncAfter(deadline: .now() + timeout) {
guard process.isRunning else { return }
process.terminate()
stop(process)
if once.claim() { continuation.resume(returning: false) }
}
}
@@ -119,7 +132,7 @@ enum Probe {
DispatchQueue.global().asyncAfter(deadline: .now() + timeout) {
guard process.isRunning else { return }
process.terminate()
stop(process)
if once.claim() {
continuation.resume(returning: RunResult(
ok: false, output: "", error: "timeout after \(timeout)s"))
@@ -170,7 +183,7 @@ enum Probe {
DispatchQueue.global().asyncAfter(deadline: .now() + timeout) {
guard process.isRunning else { return }
process.terminate()
stop(process)
if once.claim() { continuation.resume(returning: nil) }
}
}