From 4e84cd7461697e03c568156270bfd488be03b86b Mon Sep 17 00:00:00 2001 From: Lukasz Raczylo Date: Tue, 18 Nov 2025 16:54:47 +0000 Subject: [PATCH] Race condition in parseGraphQLQuery result pooling MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Under high concurrency, the sync.Pool pattern was creating a race condition where the same result pointer was being reused by multiple concurrent requests. The bug: - parseGraphQLQuery() returns a pointer to 'res' from the pool - The defer statement returns 'res' back to the pool on function exit - While the caller is still using the returned pointer, another concurrent request could get the SAME pointer from the pool and modify it This caused mutations to randomly get the wrong activeEndpoint value: - Request A: mutation parsed → activeEndpoint set to :8080 (write) - Request A: returns pointer to result - Request A: defer runs → result returned to pool - Request B: gets SAME pointer from pool - Request B: query parsed → activeEndpoint overwritten to :8088 (read-only) - Request A: still holding pointer, now sees :8088 instead of :8080! - Result: mutation routed to read-only endpoint → database write failure The fix: Create a copy of the result before returning, so the pooled object can be safely reused without affecting the returned value. --- graphql.go | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/graphql.go b/graphql.go index f064dce..86c1fc6 100644 --- a/graphql.go +++ b/graphql.go @@ -401,7 +401,10 @@ func parseGraphQLQuery(c *fiber.Ctx) *parseGraphQLQueryResult { cfg.Monitoring.IncrementFloat(libpack_monitoring.MetricsGraphQLParsingTime, nil, parseTime) } - return res + // Create a copy to return, since the original will be returned to the pool + // This prevents race conditions where concurrent requests could modify the same result + result := *res + return &result } // processDirectives extracts caching directives from the operation