From 475236c79c774bd9c397b8d397f67b06f68bccd3 Mon Sep 17 00:00:00 2001 From: Feng Ruohang Date: Tue, 4 Aug 2026 21:47:33 +0800 Subject: [PATCH] test(s3select): stop racing minio-go's parser for the response body SelectResults spawns a parser goroutine that drains and closes the response body when the stream ends; deferring res.Close() had the test drain and close the same bytes.Reader concurrently, which the race detector catches reliably. Give the test body a close signal and wait for the parser to finish instead of competing with it. The double-close lives in minio-go's client parser, which no server code path uses; it remains worth an upstream report. Co-authored-by: ChatGPT Co-authored-by: Claude --- internal/s3select/select_test.go | 26 ++++++++++++++++++++++---- 1 file changed, 22 insertions(+), 4 deletions(-) diff --git a/internal/s3select/select_test.go b/internal/s3select/select_test.go index 02f4164c7..356f4676a 100644 --- a/internal/s3select/select_test.go +++ b/internal/s3select/select_test.go @@ -26,6 +26,7 @@ import ( "os" "reflect" "strings" + "sync" "testing" "github.com/klauspost/cpuid/v2" @@ -70,6 +71,17 @@ func (w *testResponseWriter) WriteHeader(statusCode int) { func (w *testResponseWriter) Flush() { } +type testResponseBody struct { + io.Reader + closed chan struct{} + closeOnce sync.Once +} + +func (b *testResponseBody) Close() error { + b.closeOnce.Do(func() { close(b.closed) }) + return nil +} + func evaluateSelectForTest(t *testing.T, requestXML, input []byte) ([]byte, error) { t.Helper() @@ -85,18 +97,24 @@ func evaluateSelectForTest(t *testing.T, requestXML, input []byte) ([]byte, erro s3Select.Evaluate(w) s3Select.Close() + body := &testResponseBody{ + Reader: bytes.NewReader(w.response), + closed: make(chan struct{}), + } resp := http.Response{ StatusCode: http.StatusOK, - Body: io.NopCloser(bytes.NewReader(w.response)), + Body: body, ContentLength: int64(len(w.response)), } res, err := minio.NewSelectResults(&resp, "testbucket") if err != nil { t.Fatal(err) } - defer res.Close() - - return io.ReadAll(res) + result, readErr := io.ReadAll(res) + // minio-go's parser closes the response after signaling EOF or an event + // error. Wait for that owner instead of racing it with SelectResults.Close. + <-body.closed + return result, readErr } func TestJSONLinesRejectsOversizedRecord(t *testing.T) {