mirror of
https://github.com/nicoverbruggen/phpmon.git
synced 2026-04-01 17:20:09 +02:00
🐛 Fix race condition on RealShell.attach()
This commit is contained in:
@@ -227,19 +227,29 @@ 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()
|
||||||
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
|
// Set up background reading for stdout
|
||||||
outputPipe.fileHandleForReading.readabilityHandler = { fileHandle in
|
outputPipe.fileHandleForReading.readabilityHandler = { fileHandle in
|
||||||
let data = fileHandle.availableData
|
let data = fileHandle.availableData
|
||||||
@@ -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,7 +294,10 @@ class RealShell: ShellProtocol {
|
|||||||
didReceiveOutput(string, .stdErr)
|
didReceiveOutput(string, .stdErr)
|
||||||
}
|
}
|
||||||
|
|
||||||
continuation.resume(returning: (process, output))
|
if !resumed {
|
||||||
|
resumed = true
|
||||||
|
continuation.resume(returning: (process, output))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user