Skip to content

Commit 669d331

Browse files
committed
- [ConsoleLogger]: Fix race conditions during shutdown and possible crashes by writing to closed stream handler
1 parent a12b6cd commit 669d331

1 file changed

Lines changed: 35 additions & 19 deletions

File tree

SideStore/Utils/iostreams/ConsoleLogger.swift

Lines changed: 35 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -59,48 +59,64 @@ public class AbstractConsoleLogger<T: OutputStream>: ConsoleLogger{
5959
originalStdout = dup(STDOUT_FILENO)
6060
originalStderr = dup(STDERR_FILENO)
6161

62+
let redirectedOutStream = self.outPipe?.fileHandleForWriting.fileDescriptor ?? -1
63+
let redirectedErrStream = self.errPipe?.fileHandleForWriting.fileDescriptor ?? -1
64+
6265
// Redirect stdout and stderr to our pipes
63-
dup2(self.outPipe?.fileHandleForWriting.fileDescriptor ?? -1, STDOUT_FILENO)
64-
dup2(self.errPipe?.fileHandleForWriting.fileDescriptor ?? -1, STDERR_FILENO)
66+
dup2(redirectedOutStream, STDOUT_FILENO)
67+
dup2(redirectedErrStream, STDERR_FILENO)
6568

69+
// Disable libc-level buffering
70+
// (libc by default uses bufferring except its own console/TTYs such as for pipes)
71+
// we do have our own buffering so we disable stdlib io level bufferring
72+
setvbuf(stdout, nil, _IONBF, 0) // disable buffering for stdout
73+
setvbuf(stderr, nil, _IONBF, 0) // disable buffering for stderr
74+
6675
// Setup readability handlers for raw data
6776
setupReadabilityHandler(for: outputHandle, isError: false)
6877
setupReadabilityHandler(for: errorHandle, isError: true)
6978
}
7079

80+
let shutdownLock = NSLock()
81+
7182
private func setupReadabilityHandler(for handle: FileHandle?, isError: Bool) {
72-
handle?.readabilityHandler = { [weak self] handle in
73-
let data = handle.availableData
74-
if !data.isEmpty {
75-
self?.writeQueue.async {
76-
try? self?.writeData(data)
77-
}
78-
79-
// Forward to original std stream
80-
if let originalFD = isError ? self?.originalStderr : self?.originalStdout {
81-
data.withUnsafeBytes { (bufferPointer) -> Void in
82-
if let baseAddress = bufferPointer.baseAddress, bufferPointer.count > 0 {
83-
write(originalFD, baseAddress, bufferPointer.count)
84-
}
85-
}
86-
}
83+
handle?.readabilityHandler = readHandler(for: handle, isError: isError)
84+
}
85+
86+
private func readHandler(for handle: FileHandle?, isError: Bool) -> (FileHandle) -> Void {
87+
return { [weak self] _ in
88+
guard let self, let data = handle?.availableData else { return }
89+
90+
shutdownLock.lock()
91+
defer { shutdownLock.unlock() }
92+
93+
writeQueue.async {
94+
try? self.writeData(data)
95+
}
96+
97+
if let fd = isError ? self.originalStderr : self.originalStdout {
98+
data.withUnsafeBytes { _ = write(fd, $0.baseAddress, $0.count) }
8799
}
88100
}
89101
}
102+
90103

91104
func writeData(_ data: Data) throws {
92105
throw AbstractClassError.abstractMethodInvoked
93106
}
94107

95108
func stopCapturing() {
109+
shutdownLock.lock()
110+
defer { shutdownLock.unlock() }
111+
96112
ostream.close()
97113

98114
// Restore original stdout and stderr
99-
if let stdout = originalStdout {
115+
if let stdout = originalStdout, stdout != STDOUT_FILENO {
100116
dup2(stdout, STDOUT_FILENO)
101117
close(stdout)
102118
}
103-
if let stderr = originalStderr {
119+
if let stderr = originalStderr, stderr != STDERR_FILENO {
104120
dup2(stderr, STDERR_FILENO)
105121
close(stderr)
106122
}

0 commit comments

Comments
 (0)