Proposal Details
When (*clientStream).writeRequestBody sends a request body, it sizes its read/write scratch buffer via frameScratchBufferLen
The problem is requests whose body length is streaming (ContentLength == -1, like gRPC or other duplex http requests). For those, the buffer becomes min(MaxFrameSize, 512 KiB).
Peers very commonly advertise a large SETTINGS_MAX_FRAME_SIZE, like golang, cause the transport keeps 512 KiB buffer for each stream and may consume tens of MiB RAM.
As a supplement, although the buffer is from the pool, considering a long lived connection, IO is almost always blocked at read (wait for next data), so the stream will hold this big buffer until closed
Although we can change Transport.MaxReadFrameSize, but this will affect the entire http2 connection and needs to set at server
For comparison, io.Copy uses a 32 KiB buffer, it's a more appropriate limit,or consider gradually increasing the buffer based on the each read size
Proposal Details
When (*clientStream).writeRequestBody sends a request body, it sizes its read/write scratch buffer via frameScratchBufferLen
The problem is requests whose body length is streaming (ContentLength == -1, like gRPC or other duplex http requests). For those, the buffer becomes min(MaxFrameSize, 512 KiB).
Peers very commonly advertise a large SETTINGS_MAX_FRAME_SIZE, like golang, cause the transport keeps 512 KiB buffer for each stream and may consume tens of MiB RAM.
As a supplement, although the buffer is from the pool, considering a long lived connection, IO is almost always blocked at read (wait for next data), so the stream will hold this big buffer until closed
Although we can change Transport.MaxReadFrameSize, but this will affect the entire http2 connection and needs to set at server
For comparison, io.Copy uses a 32 KiB buffer, it's a more appropriate limit,or consider gradually increasing the buffer based on the each read size