diff --git a/.jules/bolt.md b/.jules/bolt.md index 5049e2f8..6042edc0 100644 --- a/.jules/bolt.md +++ b/.jules/bolt.md @@ -219,3 +219,6 @@ go tool pprof mem.prof Remember: You're Bolt, making switchAILocal lightning fast. But speed without correctness is useless. Measure, optimize, verify. **If you can't find a clear performance win today, stop and do not create a PR.** +## $(date +%Y-%m-%d) - sync.Pool defer Cleanup Caution +**Learning:** When using `sync.Pool` to recycle objects inside middleware or wrapper functions like `ResponseWriterWrapper.Finalize`, returning the object inside multiple conditional return paths increases the risk of memory leaks if a new path is added later or double-free panics if cleanup logic becomes misaligned. +**Action:** Always structure `sync.Pool.Put` operations within a single `defer` block at the top of the function to guarantee cleanup across all execution paths, rather than duplicating the `Put` logic across multiple early return statements. diff --git a/internal/api/middleware/response_writer.go b/internal/api/middleware/response_writer.go index 52ef09b2..11152592 100644 --- a/internal/api/middleware/response_writer.go +++ b/internal/api/middleware/response_writer.go @@ -11,6 +11,7 @@ import ( "bytes" "net/http" "strings" + "sync" "github.com/gin-gonic/gin" "github.com/traylinx/switchAILocal/internal/interfaces" @@ -28,6 +29,12 @@ type RequestInfo struct { // ResponseWriterWrapper wraps the standard gin.ResponseWriter to intercept and log response data. // It is designed to handle both standard and streaming responses, ensuring that logging operations do not block the client response. +var bufferPool = sync.Pool{ + New: func() interface{} { + return new(bytes.Buffer) + }, +} + type ResponseWriterWrapper struct { gin.ResponseWriter body *bytes.Buffer // body is a buffer to store the response body for non-streaming responses. @@ -53,9 +60,11 @@ type ResponseWriterWrapper struct { // Returns: // - A pointer to a new ResponseWriterWrapper. func NewResponseWriterWrapper(w gin.ResponseWriter, logger logging.RequestLogger, requestInfo *RequestInfo) *ResponseWriterWrapper { + buf := bufferPool.Get().(*bytes.Buffer) + buf.Reset() return &ResponseWriterWrapper{ ResponseWriter: w, - body: &bytes.Buffer{}, + body: buf, logger: logger, requestInfo: requestInfo, headers: make(map[string][]string), @@ -246,6 +255,15 @@ func (w *ResponseWriterWrapper) processStreamingChunks(done chan struct{}) { // For non-streaming responses, it logs the complete request and response details, // including any API-specific request/response data stored in the Gin context. func (w *ResponseWriterWrapper) Finalize(c *gin.Context) error { + defer func() { + if w.body != nil { + if w.body.Cap() <= 128*1024 { + bufferPool.Put(w.body) + } + w.body = nil + } + }() + if w.logger == nil { return nil }