Skip to content

Quadratic behaviour when streaming large bytes bodies as HTTP/2 #1039

Description

@lordmauve

httpcore2's HTTP/2 request-body path appears to do quadratic bytes copying when the request stream yields a large bytes object.

The issue is in _send_stream_data. Each loop iteration splits the body into a frame-sized chunk and a new copy of the remaining tail.

async def _send_stream_data(self, request: Request, stream_id: int, data: bytes) -> None:
"""
Send a single chunk of data in one or more data frames.
"""
while data:
max_flow = await self._wait_for_outgoing_flow(request, stream_id)
chunk_size = min(len(data), max_flow)
chunk, data = data[:chunk_size], data[chunk_size:]
self._h2_state.send_data(stream_id, chunk)
await self._write_outgoing_data(request)

For an n byte request body and a k byte HTTP/2 frame/window chunk, this copies roughly n^2 / (2k) bytes of tail data. For example, a 360 MiB body sent in 16 KiB frames implies roughly 4 TiB of cumulative tail copying. This presented as a prolonged hang with 100% CPU.

This can be triggered by the normal bytes content path, because bytes content is wrapped in ByteStream, and ByteStream yields the whole content as one chunk.

This looks specific to the HTTP/2 send path. The HTTP/1.1 request-body path passes each stream chunk through h11.Data then it is passed whole to various async socket wrappers, or for the sync path using a memoryview to avoid the copies:

def write(self, buffer: bytes, timeout: float | None = None) -> None:
if not buffer:
return
exc_map: ExceptionMapping = {socket.timeout: WriteTimeout, OSError: WriteError}
with map_exceptions(exc_map):
view = memoryview(buffer) # zero-copy slicing; avoids copies
while view:
self._sock.settimeout(timeout)
n = self._sock.send(view)
view = view[n:]

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions