https://go.dev/cl/456555 changed io.Copy to allocate its copy buffers from a sync.Pool. This exposed a race condition in the HTTP/2 ResponseWriter #58446, which can be fixed, but I'm taking this as a sign that any change should be made more judiciously; https://go.dev/cl/467095 will revert that change.
io.Copy, prior to CL 456555, allocates a 32k []byte buffer (or a smaller one if the source is an *io.LimitedReader with a size less than 32k). Allocating these buffers from a pool reduces GC allocations and notably improves io.Copy performance; see benchmarks in that CL's description. The net/http package maintains its own pool of copy buffers for this reason. Having every package that uses io.Copy maintain its own buffer pool seems unfortunate.
If the destination of a Copy retains the buffer passed to the Write call after returning, this results in a race condition where the buffer may be recycled through the pool while the Write is still in progress. This behavior is a violation of the io.Writer contract, but as #58446 demonstrates, this does happen in practice.
Perhaps we should start by detecting io.Writers that retain the buffer. When the race detector is enabled, io.Copy could write over the copy buffer after each call to Write. This will help the race detector to identify cases where another goroutine continues to access the buffer, as well as invalidating the buffer contents. https://go.dev/cl/466865 contains this change.
https://go.dev/cl/456555 changed
io.Copyto allocate its copy buffers from async.Pool. This exposed a race condition in the HTTP/2ResponseWriter#58446, which can be fixed, but I'm taking this as a sign that any change should be made more judiciously; https://go.dev/cl/467095 will revert that change.io.Copy, prior to CL 456555, allocates a 32k[]bytebuffer (or a smaller one if the source is an*io.LimitedReaderwith a size less than 32k). Allocating these buffers from a pool reduces GC allocations and notably improvesio.Copyperformance; see benchmarks in that CL's description. Thenet/httppackage maintains its own pool of copy buffers for this reason. Having every package that usesio.Copymaintain its own buffer pool seems unfortunate.If the destination of a
Copyretains the buffer passed to theWritecall after returning, this results in a race condition where the buffer may be recycled through the pool while theWriteis still in progress. This behavior is a violation of theio.Writercontract, but as #58446 demonstrates, this does happen in practice.Perhaps we should start by detecting
io.Writers that retain the buffer. When the race detector is enabled,io.Copycould write over the copy buffer after each call toWrite. This will help the race detector to identify cases where another goroutine continues to access the buffer, as well as invalidating the buffer contents. https://go.dev/cl/466865 contains this change.