1
0
mirror of https://github.com/nicoverbruggen/phpmon.git synced 2026-04-03 18:00:10 +02:00

🐛 Fix race condition on RealShell.attach()

This commit is contained in:
2025-12-23 12:07:28 +01:00
parent 8097502a76
commit c28e3e562c

View File

@@ -227,18 +227,28 @@ class RealShell: ShellProtocol {
process.standardError = errorPipe process.standardError = errorPipe
let output = ShellOutput.empty() let output = ShellOutput.empty()
// Only access `resumed`, `output` from serialQueue to ensure thread safety
let serialQueue = DispatchQueue(label: "com.nicoverbruggen.phpmon.shell_output") let serialQueue = DispatchQueue(label: "com.nicoverbruggen.phpmon.shell_output")
return try await withCheckedThrowingContinuation({ continuation in return try await withCheckedThrowingContinuation({ continuation in
let timeoutTask = Task { // Guard against resuming the continuation twice (race between timeout and termination)
try? await Task.sleep(nanoseconds: timeout.nanoseconds) var resumed = false
// Only terminate if the process is still running
// We are using GCD here because we're already using a serial queue anyway
let timeoutTaskTermination = DispatchWorkItem {
if process.isRunning { if process.isRunning {
process.terminationHandler = nil process.terminationHandler = nil
process.terminate() process.terminate()
if !resumed {
resumed = true
continuation.resume(throwing: ShellError.timedOut) continuation.resume(throwing: ShellError.timedOut)
} }
} }
}
// Let's make sure that once our timeout occurs, our process is terminated
serialQueue.asyncAfter(deadline: .now() + timeout, execute: timeoutTaskTermination)
// Set up background reading for stdout // Set up background reading for stdout
outputPipe.fileHandleForReading.readabilityHandler = { fileHandle in outputPipe.fileHandleForReading.readabilityHandler = { fileHandle in
@@ -263,7 +273,7 @@ class RealShell: ShellProtocol {
} }
process.terminationHandler = { process in process.terminationHandler = { process in
timeoutTask.cancel() timeoutTaskTermination.cancel()
// Clean up readability handlers // Clean up readability handlers
outputPipe.fileHandleForReading.readabilityHandler = nil outputPipe.fileHandleForReading.readabilityHandler = nil
@@ -284,9 +294,12 @@ class RealShell: ShellProtocol {
didReceiveOutput(string, .stdErr) didReceiveOutput(string, .stdErr)
} }
if !resumed {
resumed = true
continuation.resume(returning: (process, output)) continuation.resume(returning: (process, output))
} }
} }
}
process.launch() process.launch()
}) })