- [ConsoleLogger]: Fix race conditions during shutdown and possible crashes by writing to closed stream handler

This commit is contained in:
mahee96
2025-07-30 23:35:16 +05:30
parent a12b6cd62b
commit 669d33183e

View File

@@ -59,48 +59,64 @@ public class AbstractConsoleLogger<T: OutputStream>: ConsoleLogger{
originalStdout = dup(STDOUT_FILENO) originalStdout = dup(STDOUT_FILENO)
originalStderr = dup(STDERR_FILENO) originalStderr = dup(STDERR_FILENO)
let redirectedOutStream = self.outPipe?.fileHandleForWriting.fileDescriptor ?? -1
let redirectedErrStream = self.errPipe?.fileHandleForWriting.fileDescriptor ?? -1
// Redirect stdout and stderr to our pipes // Redirect stdout and stderr to our pipes
dup2(self.outPipe?.fileHandleForWriting.fileDescriptor ?? -1, STDOUT_FILENO) dup2(redirectedOutStream, STDOUT_FILENO)
dup2(self.errPipe?.fileHandleForWriting.fileDescriptor ?? -1, STDERR_FILENO) dup2(redirectedErrStream, STDERR_FILENO)
// Disable libc-level buffering
// (libc by default uses bufferring except its own console/TTYs such as for pipes)
// we do have our own buffering so we disable stdlib io level bufferring
setvbuf(stdout, nil, _IONBF, 0) // disable buffering for stdout
setvbuf(stderr, nil, _IONBF, 0) // disable buffering for stderr
// Setup readability handlers for raw data // Setup readability handlers for raw data
setupReadabilityHandler(for: outputHandle, isError: false) setupReadabilityHandler(for: outputHandle, isError: false)
setupReadabilityHandler(for: errorHandle, isError: true) setupReadabilityHandler(for: errorHandle, isError: true)
} }
let shutdownLock = NSLock()
private func setupReadabilityHandler(for handle: FileHandle?, isError: Bool) { private func setupReadabilityHandler(for handle: FileHandle?, isError: Bool) {
handle?.readabilityHandler = { [weak self] handle in handle?.readabilityHandler = readHandler(for: handle, isError: isError)
let data = handle.availableData
if !data.isEmpty {
self?.writeQueue.async {
try? self?.writeData(data)
} }
// Forward to original std stream private func readHandler(for handle: FileHandle?, isError: Bool) -> (FileHandle) -> Void {
if let originalFD = isError ? self?.originalStderr : self?.originalStdout { return { [weak self] _ in
data.withUnsafeBytes { (bufferPointer) -> Void in guard let self, let data = handle?.availableData else { return }
if let baseAddress = bufferPointer.baseAddress, bufferPointer.count > 0 {
write(originalFD, baseAddress, bufferPointer.count) shutdownLock.lock()
} defer { shutdownLock.unlock() }
}
} writeQueue.async {
try? self.writeData(data)
}
if let fd = isError ? self.originalStderr : self.originalStdout {
data.withUnsafeBytes { _ = write(fd, $0.baseAddress, $0.count) }
} }
} }
} }
func writeData(_ data: Data) throws { func writeData(_ data: Data) throws {
throw AbstractClassError.abstractMethodInvoked throw AbstractClassError.abstractMethodInvoked
} }
func stopCapturing() { func stopCapturing() {
shutdownLock.lock()
defer { shutdownLock.unlock() }
ostream.close() ostream.close()
// Restore original stdout and stderr // Restore original stdout and stderr
if let stdout = originalStdout { if let stdout = originalStdout, stdout != STDOUT_FILENO {
dup2(stdout, STDOUT_FILENO) dup2(stdout, STDOUT_FILENO)
close(stdout) close(stdout)
} }
if let stderr = originalStderr { if let stderr = originalStderr, stderr != STDERR_FILENO {
dup2(stderr, STDERR_FILENO) dup2(stderr, STDERR_FILENO)
close(stderr) close(stderr)
} }