mirror of
https://github.com/nicoverbruggen/phpmon.git
synced 2026-03-26 06:00:07 +01:00
🐛 Fix race condition on RealShell.attach()
This commit is contained in:
@@ -227,19 +227,29 @@ class RealShell: ShellProtocol {
|
||||
process.standardError = errorPipe
|
||||
|
||||
let output = ShellOutput.empty()
|
||||
|
||||
// Only access `resumed`, `output` from serialQueue to ensure thread safety
|
||||
let serialQueue = DispatchQueue(label: "com.nicoverbruggen.phpmon.shell_output")
|
||||
|
||||
return try await withCheckedThrowingContinuation({ continuation in
|
||||
let timeoutTask = Task {
|
||||
try? await Task.sleep(nanoseconds: timeout.nanoseconds)
|
||||
// Only terminate if the process is still running
|
||||
// Guard against resuming the continuation twice (race between timeout and termination)
|
||||
var resumed = false
|
||||
|
||||
// We are using GCD here because we're already using a serial queue anyway
|
||||
let timeoutTaskTermination = DispatchWorkItem {
|
||||
if process.isRunning {
|
||||
process.terminationHandler = nil
|
||||
process.terminate()
|
||||
continuation.resume(throwing: ShellError.timedOut)
|
||||
if !resumed {
|
||||
resumed = true
|
||||
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
|
||||
outputPipe.fileHandleForReading.readabilityHandler = { fileHandle in
|
||||
let data = fileHandle.availableData
|
||||
@@ -263,7 +273,7 @@ class RealShell: ShellProtocol {
|
||||
}
|
||||
|
||||
process.terminationHandler = { process in
|
||||
timeoutTask.cancel()
|
||||
timeoutTaskTermination.cancel()
|
||||
|
||||
// Clean up readability handlers
|
||||
outputPipe.fileHandleForReading.readabilityHandler = nil
|
||||
@@ -284,7 +294,10 @@ class RealShell: ShellProtocol {
|
||||
didReceiveOutput(string, .stdErr)
|
||||
}
|
||||
|
||||
continuation.resume(returning: (process, output))
|
||||
if !resumed {
|
||||
resumed = true
|
||||
continuation.resume(returning: (process, output))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user