From 314125e7ecb5a625c76a6fead7b687a0d7e9407b Mon Sep 17 00:00:00 2001 From: hkfires <10558748+hkfires@users.noreply.github.com> Date: Mon, 22 Sep 2025 16:46:01 +0800 Subject: [PATCH] fix(logging): Prevent race conditions in FileStreamingLogWriter --- internal/logging/request_logger.go | 48 ++++++++++++++++++++++++------ 1 file changed, 39 insertions(+), 9 deletions(-) diff --git a/internal/logging/request_logger.go b/internal/logging/request_logger.go index 17ed7715..838fd678 100644 --- a/internal/logging/request_logger.go +++ b/internal/logging/request_logger.go @@ -13,6 +13,7 @@ import ( "path/filepath" "regexp" "strings" + "sync" "time" "github.com/router-for-me/CLIProxyAPI/v6/internal/interfaces" @@ -493,6 +494,12 @@ type FileStreamingLogWriter struct { // statusWritten indicates whether the response status has been written. statusWritten bool + + // mu protects concurrent access to the writer state. + mu sync.RWMutex + + // closed indicates whether the streaming writer has been closed. + closed bool } // WriteChunkAsync writes a response chunk asynchronously (non-blocking). @@ -500,7 +507,10 @@ type FileStreamingLogWriter struct { // Parameters: // - chunk: The response chunk to write func (w *FileStreamingLogWriter) WriteChunkAsync(chunk []byte) { - if w.chunkChan == nil { + w.mu.RLock() + defer w.mu.RUnlock() + + if w.chunkChan == nil || w.closed { return } @@ -525,6 +535,9 @@ func (w *FileStreamingLogWriter) WriteChunkAsync(chunk []byte) { // Returns: // - error: An error if writing fails, nil otherwise func (w *FileStreamingLogWriter) WriteStatus(status int, headers map[string][]string) error { + w.mu.Lock() + defer w.mu.Unlock() + if w.file == nil || w.statusWritten { return nil } @@ -553,21 +566,38 @@ func (w *FileStreamingLogWriter) WriteStatus(status int, headers map[string][]st // Returns: // - error: An error if closing fails, nil otherwise func (w *FileStreamingLogWriter) Close() error { - if w.chunkChan != nil { - close(w.chunkChan) + w.mu.Lock() + if w.closed { + w.mu.Unlock() + return nil + } + w.closed = true + chunkChan := w.chunkChan + closeChan := w.closeChan + file := w.file + w.mu.Unlock() + + if chunkChan != nil { + close(chunkChan) } // Wait for async writer to finish - if w.closeChan != nil { - <-w.closeChan - w.chunkChan = nil + if closeChan != nil { + <-closeChan } - if w.file != nil { - return w.file.Close() + var err error + if file != nil { + err = file.Close() } - return nil + w.mu.Lock() + w.chunkChan = nil + w.closeChan = nil + w.file = nil + w.mu.Unlock() + + return err } // asyncWriter runs in a goroutine to handle async chunk writing.