fix(coalesce): copy the primary response body for waiters

Waiters read the primary response buffer while fasthttp could reuse it for another request. A waiter could then send the wrong body. The primary now gives waiters their own copy.
This commit is contained in:
2026-11-03 00:02:15 +00:00
parent 10d3946f1e
commit e2af5b079f
2 changed files with 39 additions and 5 deletions
+27
View File
@@ -0,0 +1,27 @@
package main
import (
"testing"
"github.com/gofiber/fiber/v3"
"github.com/stretchr/testify/assert"
"github.com/valyala/fasthttp"
)
// Pins theorems/Theorems/CoalesceAlias.lean: waiters never alias the primary's response buffer.
func TestSnapshotCoalescedResponse_OwnsBody(t *testing.T) {
app := fiber.New()
fctx := &fasthttp.RequestCtx{}
c := app.AcquireCtx(fctx)
defer app.ReleaseCtx(c)
c.Response().SetStatusCode(201)
c.Response().SetBody([]byte(`{"user":"alice"}`))
snap := snapshotCoalescedResponse(c)
c.Response().SetBody([]byte(`{"user":"bobby"}`)) // buffer reused by a later request
assert.Equal(t, 201, snap.StatusCode)
assert.Equal(t, `{"user":"alice"}`, string(snap.Body))
}
+12 -5
View File
@@ -740,11 +740,7 @@ func performProxyRequest(c fiber.Ctx, proxyURL string) error {
}, proxyErr
}
return &CoalescedResponse{
Body: c.Response().Body(),
StatusCode: c.Response().StatusCode(),
// Headers intentionally left nil; not populated or read anywhere.
}, nil
return snapshotCoalescedResponse(c), nil
})
// Check for error from rc.Do (though it typically returns nil)
@@ -773,6 +769,17 @@ func performProxyRequest(c fiber.Ctx, proxyURL string) error {
return performProxyRequestCore(c, proxyURL, cacheKey)
}
// snapshotCoalescedResponse captures the primary's response for waiters. It
// copies the body because fasthttp reuses the buffer once the primary handler
// returns, while waiters can still be reading.
func snapshotCoalescedResponse(c fiber.Ctx) *CoalescedResponse {
return &CoalescedResponse{
Body: bytes.Clone(c.Response().Body()),
StatusCode: c.Response().StatusCode(),
// Headers intentionally left nil; not populated or read anywhere.
}
}
// performProxyRequestCore executes the proxy request with retries and circuit breaker
// This is the core implementation used by both direct calls and coalesced requests
func performProxyRequestCore(c fiber.Ctx, proxyURL string, cacheKey string) error {