Skip to content

Allow decompression to continue after exceeding max_length#11966

Open
Dreamsorcerer wants to merge 153 commits intomasterfrom
Dreamsorcerer-patch-5
Open

Allow decompression to continue after exceeding max_length#11966
Dreamsorcerer wants to merge 153 commits intomasterfrom
Dreamsorcerer-patch-5

Conversation

@Dreamsorcerer
Copy link
Copy Markdown
Member

@Dreamsorcerer Dreamsorcerer commented Jan 15, 2026

Architecture summary:

  • Compression utils now have a .data_available attribute, when True the decompress() call can be repeated with b"" to get more data. The decompression output has been reduced to 256KiB, matching the socket read limit.
  • DeflateBuffer/StreamReader.feed_data() now return True if there is more data available by calling the method again with b"".
  • (PY) PayloadParser.feed_data() now returns an enum indicating when the payload is complete or whether more input is needed to continue producing output.
  • (PY) HttpParser.feed_data() now returns early when the payload parser is paused and has a new attribute to track if more data is available from the payload parser.
  • (C) HttpParser.cb_on_body() now processes the decompressed data in chunks and pauses llhttp if the parser is asked to pause.
  • (C) HttpParser.feed_data() now call cb_on_body() again when more data is available.
  • Both parsers now have a .pause_reading() method to stop the parser from producing more output.
  • BaseProtocol.pause_reading() now calls the parser's .pause_reading() method.
  • BaseProtocol.resume_reading() now resumes parsing and then checks if it's been paused again before telling the transport to resume.

@Dreamsorcerer Dreamsorcerer added the backport-3.13 Trigger automatic backporting to the 3.13 release branch by Patchback robot label Jan 15, 2026
@Dreamsorcerer Dreamsorcerer added backport-3.14 Trigger automatic backporting to the 3.14 release branch by Patchback robot bot:chronographer:skip This PR does not need to include a change note labels Jan 15, 2026
@codecov
Copy link
Copy Markdown

codecov bot commented Jan 15, 2026

❌ 2 Tests Failed:

Tests completed Failed Passed Skipped
4406 2 4404 128
View the top 1 failed test(s) by shortest run time
tests.test_benchmarks_client::test_get_request_with_251308_compressed_chunked_payload[zlib_ng.zlib_ng-pyloop]
Stack Traces | 0.604s run time
self = <aiohttp.client_proto.ResponseHandler object at 0x0000014202505E50>
exc = None

    #x1B[0m#x1B[94mdef#x1B[39;49;00m#x1B[90m #x1B[39;49;00m#x1B[92mconnection_lost#x1B[39;49;00m(#x1B[96mself#x1B[39;49;00m, exc: #x1B[96mBaseException#x1B[39;49;00m | #x1B[94mNone#x1B[39;49;00m) -> #x1B[94mNone#x1B[39;49;00m:#x1B[90m#x1B[39;49;00m
        #x1B[96mself#x1B[39;49;00m._connection_lost_called = #x1B[94mTrue#x1B[39;49;00m#x1B[90m#x1B[39;49;00m
        #x1B[96mself#x1B[39;49;00m._drop_timeout()#x1B[90m#x1B[39;49;00m
    #x1B[90m#x1B[39;49;00m
        original_connection_error = exc#x1B[90m#x1B[39;49;00m
        reraised_exc = original_connection_error#x1B[90m#x1B[39;49;00m
    #x1B[90m#x1B[39;49;00m
        connection_closed_cleanly = original_connection_error #x1B[95mis#x1B[39;49;00m #x1B[94mNone#x1B[39;49;00m#x1B[90m#x1B[39;49;00m
    #x1B[90m#x1B[39;49;00m
        #x1B[94mif#x1B[39;49;00m #x1B[96mself#x1B[39;49;00m._closed #x1B[95mis#x1B[39;49;00m #x1B[95mnot#x1B[39;49;00m #x1B[94mNone#x1B[39;49;00m:#x1B[90m#x1B[39;49;00m
            #x1B[90m# If someone is waiting for the closed future,#x1B[39;49;00m#x1B[90m#x1B[39;49;00m
            #x1B[90m# we should set it to None or an exception. If#x1B[39;49;00m#x1B[90m#x1B[39;49;00m
            #x1B[90m# self._closed is None, it means that#x1B[39;49;00m#x1B[90m#x1B[39;49;00m
            #x1B[90m# connection_lost() was called already#x1B[39;49;00m#x1B[90m#x1B[39;49;00m
            #x1B[90m# or nobody is waiting for it.#x1B[39;49;00m#x1B[90m#x1B[39;49;00m
            #x1B[94mif#x1B[39;49;00m connection_closed_cleanly:#x1B[90m#x1B[39;49;00m
                set_result(#x1B[96mself#x1B[39;49;00m._closed, #x1B[94mNone#x1B[39;49;00m)#x1B[90m#x1B[39;49;00m
            #x1B[94melse#x1B[39;49;00m:#x1B[90m#x1B[39;49;00m
                #x1B[94massert#x1B[39;49;00m original_connection_error #x1B[95mis#x1B[39;49;00m #x1B[95mnot#x1B[39;49;00m #x1B[94mNone#x1B[39;49;00m#x1B[90m#x1B[39;49;00m
                set_exception(#x1B[90m#x1B[39;49;00m
                    #x1B[96mself#x1B[39;49;00m._closed,#x1B[90m#x1B[39;49;00m
                    ClientConnectionError(#x1B[90m#x1B[39;49;00m
                        #x1B[33mf#x1B[39;49;00m#x1B[33m"#x1B[39;49;00m#x1B[33mConnection lost: #x1B[39;49;00m#x1B[33m{#x1B[39;49;00moriginal_connection_error#x1B[90m #x1B[39;49;00m#x1B[33m!s}#x1B[39;49;00m#x1B[33m"#x1B[39;49;00m,#x1B[90m#x1B[39;49;00m
                    ),#x1B[90m#x1B[39;49;00m
                    original_connection_error,#x1B[90m#x1B[39;49;00m
                )#x1B[90m#x1B[39;49;00m
    #x1B[90m#x1B[39;49;00m
        #x1B[94mif#x1B[39;49;00m #x1B[96mself#x1B[39;49;00m._payload_parser #x1B[95mis#x1B[39;49;00m #x1B[95mnot#x1B[39;49;00m #x1B[94mNone#x1B[39;49;00m:#x1B[90m#x1B[39;49;00m
            #x1B[94mwith#x1B[39;49;00m suppress(#x1B[96mException#x1B[39;49;00m):  #x1B[90m# FIXME: log this somehow?#x1B[39;49;00m#x1B[90m#x1B[39;49;00m
                #x1B[96mself#x1B[39;49;00m._payload_parser.feed_eof()#x1B[90m#x1B[39;49;00m
    #x1B[90m#x1B[39;49;00m
        uncompleted = #x1B[94mNone#x1B[39;49;00m#x1B[90m#x1B[39;49;00m
        #x1B[94mif#x1B[39;49;00m #x1B[96mself#x1B[39;49;00m._parser #x1B[95mis#x1B[39;49;00m #x1B[95mnot#x1B[39;49;00m #x1B[94mNone#x1B[39;49;00m:#x1B[90m#x1B[39;49;00m
            #x1B[94mtry#x1B[39;49;00m:#x1B[90m#x1B[39;49;00m
>               uncompleted = #x1B[96mself#x1B[39;49;00m._parser.feed_eof()#x1B[90m#x1B[39;49;00m
                              ^^^^^^^^^^^^^^^^^^^^^^^#x1B[90m#x1B[39;49;00m

__class__  = <class 'aiohttp.client_proto.ResponseHandler'>
client_payload_exc_msg = "Response payload is not completed: <ContentLengthError: 400, message='Not enough data to satisfy content length header.'>"
connection_closed_cleanly = True
exc        = None
original_connection_error = None
reraised_exc = None
self       = <aiohttp.client_proto.ResponseHandler object at 0x0000014202505E50>
uncompleted = None

#x1B[1m#x1B[31maiohttp\client_proto.py#x1B[0m:148: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _

>   #x1B[0m#x1B[94mraise#x1B[39;49;00m ContentLengthError(#x1B[90m#x1B[39;49;00m
#x1B[1m#x1B[31mE   aiohttp.http_exceptions.ContentLengthError: 400, message:#x1B[0m
#x1B[1m#x1B[31mE     Not enough data to satisfy content length header.#x1B[0m


#x1B[1m#x1B[31maiohttp\_http_parser.pyx#x1B[0m:556: ContentLengthError

#x1B[33mThe above exception was the direct cause of the following exception:#x1B[0m

loop = <ProactorEventLoop running=False closed=False debug=False>
aiohttp_client = <function aiohttp_client.<locals>.go at 0x0000014201605F30>
benchmark = <pytest_codspeed.plugin.BenchmarkFixture object at 0x000001420250DB50>

    #x1B[0m#x1B[37m@pytest#x1B[39;49;00m.mark.usefixtures(#x1B[33m"#x1B[39;49;00m#x1B[33mparametrize_zlib_backend#x1B[39;49;00m#x1B[33m"#x1B[39;49;00m)#x1B[90m#x1B[39;49;00m
    #x1B[94mdef#x1B[39;49;00m#x1B[90m #x1B[39;49;00m#x1B[92mtest_get_request_with_251308_compressed_chunked_payload#x1B[39;49;00m(#x1B[90m#x1B[39;49;00m
        loop: asyncio.AbstractEventLoop,#x1B[90m#x1B[39;49;00m
        aiohttp_client: AiohttpClient,#x1B[90m#x1B[39;49;00m
        benchmark: BenchmarkFixture,#x1B[90m#x1B[39;49;00m
    ) -> #x1B[94mNone#x1B[39;49;00m:#x1B[90m#x1B[39;49;00m
    #x1B[90m    #x1B[39;49;00m#x1B[33m"""Benchmark compressed GET requests with a payload of 251308."""#x1B[39;49;00m#x1B[90m#x1B[39;49;00m
        #x1B[90m# This payload compresses to 251308 bytes#x1B[39;49;00m#x1B[90m#x1B[39;49;00m
        payload = #x1B[33mb#x1B[39;49;00m#x1B[33m"#x1B[39;49;00m#x1B[33m"#x1B[39;49;00m.join(#x1B[90m#x1B[39;49;00m
            [#x1B[90m#x1B[39;49;00m
                #x1B[96mbytes#x1B[39;49;00m((*#x1B[96mrange#x1B[39;49;00m(#x1B[94m0#x1B[39;49;00m, i), *#x1B[96mrange#x1B[39;49;00m(i, #x1B[94m0#x1B[39;49;00m, -#x1B[94m1#x1B[39;49;00m)))#x1B[90m#x1B[39;49;00m
                #x1B[94mfor#x1B[39;49;00m _ #x1B[95min#x1B[39;49;00m #x1B[96mrange#x1B[39;49;00m(#x1B[94m255#x1B[39;49;00m)#x1B[90m#x1B[39;49;00m
                #x1B[94mfor#x1B[39;49;00m i #x1B[95min#x1B[39;49;00m #x1B[96mrange#x1B[39;49;00m(#x1B[94m255#x1B[39;49;00m)#x1B[90m#x1B[39;49;00m
            ]#x1B[90m#x1B[39;49;00m
        )#x1B[90m#x1B[39;49;00m
    #x1B[90m#x1B[39;49;00m
        #x1B[94masync#x1B[39;49;00m #x1B[94mdef#x1B[39;49;00m#x1B[90m #x1B[39;49;00m#x1B[92mhandler#x1B[39;49;00m(request: web.Request) -> web.Response:#x1B[90m#x1B[39;49;00m
            resp = web.Response(body=payload, zlib_executor_size=#x1B[94m16384#x1B[39;49;00m)#x1B[90m#x1B[39;49;00m
            resp.enable_compression()#x1B[90m#x1B[39;49;00m
            #x1B[94mreturn#x1B[39;49;00m resp#x1B[90m#x1B[39;49;00m
    #x1B[90m#x1B[39;49;00m
        app = web.Application()#x1B[90m#x1B[39;49;00m
        app.router.add_route(#x1B[33m"#x1B[39;49;00m#x1B[33mGET#x1B[39;49;00m#x1B[33m"#x1B[39;49;00m, #x1B[33m"#x1B[39;49;00m#x1B[33m/#x1B[39;49;00m#x1B[33m"#x1B[39;49;00m, handler)#x1B[90m#x1B[39;49;00m
    #x1B[90m#x1B[39;49;00m
        #x1B[94masync#x1B[39;49;00m #x1B[94mdef#x1B[39;49;00m#x1B[90m #x1B[39;49;00m#x1B[92mrun_client_benchmark#x1B[39;49;00m() -> #x1B[94mNone#x1B[39;49;00m:#x1B[90m#x1B[39;49;00m
            client = #x1B[94mawait#x1B[39;49;00m aiohttp_client(app)#x1B[90m#x1B[39;49;00m
            resp = #x1B[94mawait#x1B[39;49;00m client.get(#x1B[33m"#x1B[39;49;00m#x1B[33m/#x1B[39;49;00m#x1B[33m"#x1B[39;49;00m)#x1B[90m#x1B[39;49;00m
            #x1B[94mawait#x1B[39;49;00m resp.read()#x1B[90m#x1B[39;49;00m
            #x1B[94mawait#x1B[39;49;00m client.close()#x1B[90m#x1B[39;49;00m
    #x1B[90m#x1B[39;49;00m
>       #x1B[37m@benchmark#x1B[39;49;00m#x1B[90m#x1B[39;49;00m
         ^^^^^^^^^#x1B[90m#x1B[39;49;00m

aiohttp_client = <function aiohttp_client.<locals>.go at 0x0000014201605F30>
app        = <Application 0x142013589e0>
benchmark  = <pytest_codspeed.plugin.BenchmarkFixture object at 0x000001420250DB50>
handler    = <function test_get_request_with_251308_compressed_chunked_payload.<locals>.handler at 0x00000142013675E0>
loop       = <ProactorEventLoop running=False closed=False debug=False>
payload    = b'\x00\x01\x00\x01\x02\x01\x00\x01\x02\x03\x02\x01\x00\x01\x02\x03\x04\x03\x02\x01\x00\x01\x02\x03\x04\x05\x04\x03\x02...\x1f\x1e\x1d\x1c\x1b\x1a\x19\x18\x17\x16\x15\x14\x13\x12\x11\x10\x0f\x0e\r\x0c\x0b\n\t\x08\x07\x06\x05\x04\x03\x02\x01'
run_client_benchmark = <function test_get_request_with_251308_compressed_chunked_payload.<locals>.run_client_benchmark at 0x0000014201358040>

#x1B[1m#x1B[31mtests\test_benchmarks_client.py#x1B[0m:269: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
#x1B[1m#x1B[31mC:\hostedtoolcache\windows\Python\3.14.3\x64\Lib\site-packages\pytest_codspeed\plugin.py#x1B[0m:358: in __call__
    #x1B[0m#x1B[94mreturn#x1B[39;49;00m target(*args, **kwargs)#x1B[90m#x1B[39;49;00m
           ^^^^^^^^^^^^^^^^^^^^^^^#x1B[90m#x1B[39;49;00m
        args       = ()
        kwargs     = {}
        self       = <pytest_codspeed.plugin.BenchmarkFixture object at 0x000001420250DB50>
        target     = <function test_get_request_with_251308_compressed_chunked_payload.<locals>._run at 0x00000142013583B0>
#x1B[1m#x1B[31mtests\test_benchmarks_client.py#x1B[0m:271: in _run
    #x1B[0mloop.run_until_complete(run_client_benchmark())#x1B[90m#x1B[39;49;00m
        loop       = <ProactorEventLoop running=False closed=False debug=False>
        run_client_benchmark = <function test_get_request_with_251308_compressed_chunked_payload.<locals>.run_client_benchmark at 0x0000014201358040>
#x1B[1m#x1B[31mC:\hostedtoolcache\windows\Python\3.14.3\x64\Lib\asyncio\base_events.py#x1B[0m:719: in run_until_complete
    #x1B[0m#x1B[94mreturn#x1B[39;49;00m future.result()#x1B[90m#x1B[39;49;00m
           ^^^^^^^^^^^^^^^#x1B[90m#x1B[39;49;00m
        future     = <Task finished name='Task-1086' coro=<test_get_request_with_251308_compressed_chunked_payload.<locals>.run_client_benc...nse payload is not completed: <ContentLengthError: 400, message='Not enough data to satisfy content length header.'>")>
        new_task   = True
        self       = <ProactorEventLoop running=False closed=False debug=False>
#x1B[1m#x1B[31mtests\test_benchmarks_client.py#x1B[0m:266: in run_client_benchmark
    #x1B[0m#x1B[94mawait#x1B[39;49;00m resp.read()#x1B[90m#x1B[39;49;00m
        aiohttp_client = <function aiohttp_client.<locals>.go at 0x0000014201605F30>
        app        = <Application 0x142013589e0>
        client     = <aiohttp.test_utils.TestClient object at 0x000001420250C650>
        resp       = <ClientResponse(http://127.0.0.1:58898/) [200 OK]>
<CIMultiDictProxy('Content-Length': '275644', 'Content-Encoding': '...': 'application/octet-stream', 'Date': 'Wed, 01 Apr 2026 03:24:57 GMT', 'Server': 'Python/3.14 aiohttp/4.0.0a2.dev0')>

#x1B[1m#x1B[31maiohttp\client_reqrep.py#x1B[0m:592: in read
    #x1B[0m#x1B[96mself#x1B[39;49;00m._body = #x1B[94mawait#x1B[39;49;00m #x1B[96mself#x1B[39;49;00m.content.read()#x1B[90m#x1B[39;49;00m
                 ^^^^^^^^^^^^^^^^^^^^^^^^^#x1B[90m#x1B[39;49;00m
        self       = <ClientResponse(http://127.0.0.1:58898/) [200 OK]>
<CIMultiDictProxy('Content-Length': '275644', 'Content-Encoding': '...': 'application/octet-stream', 'Date': 'Wed, 01 Apr 2026 03:24:57 GMT', 'Server': 'Python/3.14 aiohttp/4.0.0a2.dev0')>

#x1B[1m#x1B[31maiohttp\streams.py#x1B[0m:418: in read
    #x1B[0mblock = #x1B[94mawait#x1B[39;49;00m #x1B[96mself#x1B[39;49;00m.readany()#x1B[90m#x1B[39;49;00m
            ^^^^^^^^^^^^^^^^^^^^#x1B[90m#x1B[39;49;00m
        block      = b'utsrqponmlkjihgfedcba`_^]\\[ZYXWVUTSRQPONMLKJIHGFEDCBA@?>=<;:9876543210/.-,+*)(\'&%$#"! \x1f\x1e\x1d\x1c\x1b\x1a\x19...\x01\x00\x01\x02\x03\x04\x05\x06\x07\x08\t\n\t\x08\x07\x06\x05\x04\x03\x02\x01\x00\x01\x02\x03\x04\x05\x06\x07\x08\t\n'
        blocks     = [b'\x00\x01\x00\x01\x02\x01\x00\x01\x02\x03\x02\x01\x00\x01\x02\x03\x04\x03\x02\x01\x00\x01\x02\x03\x04\x05\x04\x03\x0...x01\x00\x01\x02\x03\x04\x05\x06\x07\x08\t\n\t\x08\x07\x06\x05\x04\x03\x02\x01\x00\x01\x02\x03\x04\x05\x06\x07\x08\t\n']
        n          = -1
        self       = <StreamReader e=ClientPayloadError("Response payload is not completed: <ContentLengthError: 400, message='Not enough data to satisfy content length header.'>")>
#x1B[1m#x1B[31maiohttp\streams.py#x1B[0m:440: in readany
    #x1B[0m#x1B[94mawait#x1B[39;49;00m #x1B[96mself#x1B[39;49;00m._wait(#x1B[33m"#x1B[39;49;00m#x1B[33mreadany#x1B[39;49;00m#x1B[33m"#x1B[39;49;00m)#x1B[90m#x1B[39;49;00m
        self       = <StreamReader e=ClientPayloadError("Response payload is not completed: <ContentLengthError: 400, message='Not enough data to satisfy content length header.'>")>
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _

self = <StreamReader e=ClientPayloadError("Response payload is not completed: <ContentLengthError: 400, message='Not enough data to satisfy content length header.'>")>
func_name = 'readany'

    #x1B[0m#x1B[94masync#x1B[39;49;00m #x1B[94mdef#x1B[39;49;00m#x1B[90m #x1B[39;49;00m#x1B[92m_wait#x1B[39;49;00m(#x1B[96mself#x1B[39;49;00m, func_name: #x1B[96mstr#x1B[39;49;00m) -> #x1B[94mNone#x1B[39;49;00m:#x1B[90m#x1B[39;49;00m
        #x1B[94mif#x1B[39;49;00m #x1B[95mnot#x1B[39;49;00m #x1B[96mself#x1B[39;49;00m._protocol.connected:#x1B[90m#x1B[39;49;00m
            #x1B[94mraise#x1B[39;49;00m #x1B[96mRuntimeError#x1B[39;49;00m(#x1B[33m"#x1B[39;49;00m#x1B[33mConnection closed.#x1B[39;49;00m#x1B[33m"#x1B[39;49;00m)#x1B[90m#x1B[39;49;00m
    #x1B[90m#x1B[39;49;00m
        #x1B[90m# StreamReader uses a future to link the protocol feed_data() method#x1B[39;49;00m#x1B[90m#x1B[39;49;00m
        #x1B[90m# to a read coroutine. Running two read coroutines at the same time#x1B[39;49;00m#x1B[90m#x1B[39;49;00m
        #x1B[90m# would have an unexpected behaviour. It would not possible to know#x1B[39;49;00m#x1B[90m#x1B[39;49;00m
        #x1B[90m# which coroutine would get the next data.#x1B[39;49;00m#x1B[90m#x1B[39;49;00m
        #x1B[94mif#x1B[39;49;00m #x1B[96mself#x1B[39;49;00m._waiter #x1B[95mis#x1B[39;49;00m #x1B[95mnot#x1B[39;49;00m #x1B[94mNone#x1B[39;49;00m:#x1B[90m#x1B[39;49;00m
            #x1B[94mraise#x1B[39;49;00m #x1B[96mRuntimeError#x1B[39;49;00m(#x1B[90m#x1B[39;49;00m
                #x1B[33m"#x1B[39;49;00m#x1B[33m%s#x1B[39;49;00m#x1B[33m() called while another coroutine is #x1B[39;49;00m#x1B[33m"#x1B[39;49;00m#x1B[90m#x1B[39;49;00m
                #x1B[33m"#x1B[39;49;00m#x1B[33malready waiting for incoming data#x1B[39;49;00m#x1B[33m"#x1B[39;49;00m % func_name#x1B[90m#x1B[39;49;00m
            )#x1B[90m#x1B[39;49;00m
    #x1B[90m#x1B[39;49;00m
        waiter = #x1B[96mself#x1B[39;49;00m._waiter = #x1B[96mself#x1B[39;49;00m._loop.create_future()#x1B[90m#x1B[39;49;00m
        #x1B[94mtry#x1B[39;49;00m:#x1B[90m#x1B[39;49;00m
            #x1B[94mwith#x1B[39;49;00m #x1B[96mself#x1B[39;49;00m._timer:#x1B[90m#x1B[39;49;00m
>               #x1B[94mawait#x1B[39;49;00m waiter#x1B[90m#x1B[39;49;00m
#x1B[1m#x1B[31mE               aiohttp.client_exceptions.ClientPayloadError: Response payload is not completed: <ContentLengthError: 400, message='Not enough data to satisfy content length header.'>#x1B[0m

func_name  = 'readany'
self       = <StreamReader e=ClientPayloadError("Response payload is not completed: <ContentLengthError: 400, message='Not enough data to satisfy content length header.'>")>
waiter     = <Future finished exception=ClientPayloadError("Response payload is not completed: <ContentLengthError: 400, message='Not enough data to satisfy content length header.'>")>

#x1B[1m#x1B[31maiohttp\streams.py#x1B[0m:358: ClientPayloadError
View the full list of 2 ❄️ flaky test(s)
tests.test_benchmarks_client::test_get_request_with_251308_compressed_chunked_payload[isal.isal_zlib-pyloop]

Flake rate in main: 4.76% (Passed 20 times, Failed 1 times)

Stack Traces | 0.588s run time
self = <aiohttp.client_proto.ResponseHandler object at 0x00000157C3BC5780>
exc = None

    #x1B[0m#x1B[94mdef#x1B[39;49;00m#x1B[90m #x1B[39;49;00m#x1B[92mconnection_lost#x1B[39;49;00m(#x1B[96mself#x1B[39;49;00m, exc: #x1B[96mBaseException#x1B[39;49;00m | #x1B[94mNone#x1B[39;49;00m) -> #x1B[94mNone#x1B[39;49;00m:#x1B[90m#x1B[39;49;00m
        #x1B[96mself#x1B[39;49;00m._connection_lost_called = #x1B[94mTrue#x1B[39;49;00m#x1B[90m#x1B[39;49;00m
        #x1B[96mself#x1B[39;49;00m._drop_timeout()#x1B[90m#x1B[39;49;00m
    #x1B[90m#x1B[39;49;00m
        original_connection_error = exc#x1B[90m#x1B[39;49;00m
        reraised_exc = original_connection_error#x1B[90m#x1B[39;49;00m
    #x1B[90m#x1B[39;49;00m
        connection_closed_cleanly = original_connection_error #x1B[95mis#x1B[39;49;00m #x1B[94mNone#x1B[39;49;00m#x1B[90m#x1B[39;49;00m
    #x1B[90m#x1B[39;49;00m
        #x1B[94mif#x1B[39;49;00m #x1B[96mself#x1B[39;49;00m._closed #x1B[95mis#x1B[39;49;00m #x1B[95mnot#x1B[39;49;00m #x1B[94mNone#x1B[39;49;00m:#x1B[90m#x1B[39;49;00m
            #x1B[90m# If someone is waiting for the closed future,#x1B[39;49;00m#x1B[90m#x1B[39;49;00m
            #x1B[90m# we should set it to None or an exception. If#x1B[39;49;00m#x1B[90m#x1B[39;49;00m
            #x1B[90m# self._closed is None, it means that#x1B[39;49;00m#x1B[90m#x1B[39;49;00m
            #x1B[90m# connection_lost() was called already#x1B[39;49;00m#x1B[90m#x1B[39;49;00m
            #x1B[90m# or nobody is waiting for it.#x1B[39;49;00m#x1B[90m#x1B[39;49;00m
            #x1B[94mif#x1B[39;49;00m connection_closed_cleanly:#x1B[90m#x1B[39;49;00m
                set_result(#x1B[96mself#x1B[39;49;00m._closed, #x1B[94mNone#x1B[39;49;00m)#x1B[90m#x1B[39;49;00m
            #x1B[94melse#x1B[39;49;00m:#x1B[90m#x1B[39;49;00m
                #x1B[94massert#x1B[39;49;00m original_connection_error #x1B[95mis#x1B[39;49;00m #x1B[95mnot#x1B[39;49;00m #x1B[94mNone#x1B[39;49;00m#x1B[90m#x1B[39;49;00m
                set_exception(#x1B[90m#x1B[39;49;00m
                    #x1B[96mself#x1B[39;49;00m._closed,#x1B[90m#x1B[39;49;00m
                    ClientConnectionError(#x1B[90m#x1B[39;49;00m
                        #x1B[33mf#x1B[39;49;00m#x1B[33m"#x1B[39;49;00m#x1B[33mConnection lost: #x1B[39;49;00m#x1B[33m{#x1B[39;49;00moriginal_connection_error#x1B[90m #x1B[39;49;00m#x1B[33m!s}#x1B[39;49;00m#x1B[33m"#x1B[39;49;00m,#x1B[90m#x1B[39;49;00m
                    ),#x1B[90m#x1B[39;49;00m
                    original_connection_error,#x1B[90m#x1B[39;49;00m
                )#x1B[90m#x1B[39;49;00m
    #x1B[90m#x1B[39;49;00m
        #x1B[94mif#x1B[39;49;00m #x1B[96mself#x1B[39;49;00m._payload_parser #x1B[95mis#x1B[39;49;00m #x1B[95mnot#x1B[39;49;00m #x1B[94mNone#x1B[39;49;00m:#x1B[90m#x1B[39;49;00m
            #x1B[94mwith#x1B[39;49;00m suppress(#x1B[96mException#x1B[39;49;00m):  #x1B[90m# FIXME: log this somehow?#x1B[39;49;00m#x1B[90m#x1B[39;49;00m
                #x1B[96mself#x1B[39;49;00m._payload_parser.feed_eof()#x1B[90m#x1B[39;49;00m
    #x1B[90m#x1B[39;49;00m
        uncompleted = #x1B[94mNone#x1B[39;49;00m#x1B[90m#x1B[39;49;00m
        #x1B[94mif#x1B[39;49;00m #x1B[96mself#x1B[39;49;00m._parser #x1B[95mis#x1B[39;49;00m #x1B[95mnot#x1B[39;49;00m #x1B[94mNone#x1B[39;49;00m:#x1B[90m#x1B[39;49;00m
            #x1B[94mtry#x1B[39;49;00m:#x1B[90m#x1B[39;49;00m
>               uncompleted = #x1B[96mself#x1B[39;49;00m._parser.feed_eof()#x1B[90m#x1B[39;49;00m
                              ^^^^^^^^^^^^^^^^^^^^^^^#x1B[90m#x1B[39;49;00m

__class__  = <class 'aiohttp.client_proto.ResponseHandler'>
client_payload_exc_msg = "Response payload is not completed: <ContentLengthError: 400, message='Not enough data to satisfy content length header.'>"
connection_closed_cleanly = True
exc        = None
original_connection_error = None
reraised_exc = None
self       = <aiohttp.client_proto.ResponseHandler object at 0x00000157C3BC5780>
uncompleted = None

#x1B[1m#x1B[31maiohttp\client_proto.py#x1B[0m:148: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _

>   #x1B[0m#x1B[94mraise#x1B[39;49;00m ContentLengthError(#x1B[90m#x1B[39;49;00m
#x1B[1m#x1B[31mE   aiohttp.http_exceptions.ContentLengthError: 400, message:#x1B[0m
#x1B[1m#x1B[31mE     Not enough data to satisfy content length header.#x1B[0m


#x1B[1m#x1B[31maiohttp\_http_parser.pyx#x1B[0m:556: ContentLengthError

#x1B[33mThe above exception was the direct cause of the following exception:#x1B[0m

loop = <ProactorEventLoop running=False closed=False debug=False>
aiohttp_client = <function aiohttp_client.<locals>.go at 0x00000157C41E54E0>
benchmark = <pytest_codspeed.plugin.BenchmarkFixture object at 0x00000157C5676F60>

    #x1B[0m#x1B[37m@pytest#x1B[39;49;00m.mark.usefixtures(#x1B[33m"#x1B[39;49;00m#x1B[33mparametrize_zlib_backend#x1B[39;49;00m#x1B[33m"#x1B[39;49;00m)#x1B[90m#x1B[39;49;00m
    #x1B[94mdef#x1B[39;49;00m#x1B[90m #x1B[39;49;00m#x1B[92mtest_get_request_with_251308_compressed_chunked_payload#x1B[39;49;00m(#x1B[90m#x1B[39;49;00m
        loop: asyncio.AbstractEventLoop,#x1B[90m#x1B[39;49;00m
        aiohttp_client: AiohttpClient,#x1B[90m#x1B[39;49;00m
        benchmark: BenchmarkFixture,#x1B[90m#x1B[39;49;00m
    ) -> #x1B[94mNone#x1B[39;49;00m:#x1B[90m#x1B[39;49;00m
    #x1B[90m    #x1B[39;49;00m#x1B[33m"""Benchmark compressed GET requests with a payload of 251308."""#x1B[39;49;00m#x1B[90m#x1B[39;49;00m
        #x1B[90m# This payload compresses to 251308 bytes#x1B[39;49;00m#x1B[90m#x1B[39;49;00m
        payload = #x1B[33mb#x1B[39;49;00m#x1B[33m"#x1B[39;49;00m#x1B[33m"#x1B[39;49;00m.join(#x1B[90m#x1B[39;49;00m
            [#x1B[90m#x1B[39;49;00m
                #x1B[96mbytes#x1B[39;49;00m((*#x1B[96mrange#x1B[39;49;00m(#x1B[94m0#x1B[39;49;00m, i), *#x1B[96mrange#x1B[39;49;00m(i, #x1B[94m0#x1B[39;49;00m, -#x1B[94m1#x1B[39;49;00m)))#x1B[90m#x1B[39;49;00m
                #x1B[94mfor#x1B[39;49;00m _ #x1B[95min#x1B[39;49;00m #x1B[96mrange#x1B[39;49;00m(#x1B[94m255#x1B[39;49;00m)#x1B[90m#x1B[39;49;00m
                #x1B[94mfor#x1B[39;49;00m i #x1B[95min#x1B[39;49;00m #x1B[96mrange#x1B[39;49;00m(#x1B[94m255#x1B[39;49;00m)#x1B[90m#x1B[39;49;00m
            ]#x1B[90m#x1B[39;49;00m
        )#x1B[90m#x1B[39;49;00m
    #x1B[90m#x1B[39;49;00m
        #x1B[94masync#x1B[39;49;00m #x1B[94mdef#x1B[39;49;00m#x1B[90m #x1B[39;49;00m#x1B[92mhandler#x1B[39;49;00m(request: web.Request) -> web.Response:#x1B[90m#x1B[39;49;00m
            resp = web.Response(body=payload, zlib_executor_size=#x1B[94m16384#x1B[39;49;00m)#x1B[90m#x1B[39;49;00m
            resp.enable_compression()#x1B[90m#x1B[39;49;00m
            #x1B[94mreturn#x1B[39;49;00m resp#x1B[90m#x1B[39;49;00m
    #x1B[90m#x1B[39;49;00m
        app = web.Application()#x1B[90m#x1B[39;49;00m
        app.router.add_route(#x1B[33m"#x1B[39;49;00m#x1B[33mGET#x1B[39;49;00m#x1B[33m"#x1B[39;49;00m, #x1B[33m"#x1B[39;49;00m#x1B[33m/#x1B[39;49;00m#x1B[33m"#x1B[39;49;00m, handler)#x1B[90m#x1B[39;49;00m
    #x1B[90m#x1B[39;49;00m
        #x1B[94masync#x1B[39;49;00m #x1B[94mdef#x1B[39;49;00m#x1B[90m #x1B[39;49;00m#x1B[92mrun_client_benchmark#x1B[39;49;00m() -> #x1B[94mNone#x1B[39;49;00m:#x1B[90m#x1B[39;49;00m
            client = #x1B[94mawait#x1B[39;49;00m aiohttp_client(app)#x1B[90m#x1B[39;49;00m
            resp = #x1B[94mawait#x1B[39;49;00m client.get(#x1B[33m"#x1B[39;49;00m#x1B[33m/#x1B[39;49;00m#x1B[33m"#x1B[39;49;00m)#x1B[90m#x1B[39;49;00m
            #x1B[94mawait#x1B[39;49;00m resp.read()#x1B[90m#x1B[39;49;00m
            #x1B[94mawait#x1B[39;49;00m client.close()#x1B[90m#x1B[39;49;00m
    #x1B[90m#x1B[39;49;00m
>       #x1B[37m@benchmark#x1B[39;49;00m#x1B[90m#x1B[39;49;00m
         ^^^^^^^^^#x1B[90m#x1B[39;49;00m

aiohttp_client = <function aiohttp_client.<locals>.go at 0x00000157C41E54E0>
app        = <Application 0x157c41252d0>
benchmark  = <pytest_codspeed.plugin.BenchmarkFixture object at 0x00000157C5676F60>
handler    = <function test_get_request_with_251308_compressed_chunked_payload.<locals>.handler at 0x00000157C41E6D40>
loop       = <ProactorEventLoop running=False closed=False debug=False>
payload    = b'\x00\x01\x00\x01\x02\x01\x00\x01\x02\x03\x02\x01\x00\x01\x02\x03\x04\x03\x02\x01\x00\x01\x02\x03\x04\x05\x04\x03\x02...\x1f\x1e\x1d\x1c\x1b\x1a\x19\x18\x17\x16\x15\x14\x13\x12\x11\x10\x0f\x0e\r\x0c\x0b\n\t\x08\x07\x06\x05\x04\x03\x02\x01'
run_client_benchmark = <function test_get_request_with_251308_compressed_chunked_payload.<locals>.run_client_benchmark at 0x00000157C41E7240>

#x1B[1m#x1B[31mtests\test_benchmarks_client.py#x1B[0m:269: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
#x1B[1m#x1B[31mC:\hostedtoolcache\windows\Python\3.12.10\x64\Lib\site-packages\pytest_codspeed\plugin.py#x1B[0m:358: in __call__
    #x1B[0m#x1B[94mreturn#x1B[39;49;00m target(*args, **kwargs)#x1B[90m#x1B[39;49;00m
           ^^^^^^^^^^^^^^^^^^^^^^^#x1B[90m#x1B[39;49;00m
        args       = ()
        kwargs     = {}
        self       = <pytest_codspeed.plugin.BenchmarkFixture object at 0x00000157C5676F60>
        target     = <function test_get_request_with_251308_compressed_chunked_payload.<locals>._run at 0x00000157C3BF8400>
#x1B[1m#x1B[31mtests\test_benchmarks_client.py#x1B[0m:271: in _run
    #x1B[0mloop.run_until_complete(run_client_benchmark())#x1B[90m#x1B[39;49;00m
        loop       = <ProactorEventLoop running=False closed=False debug=False>
        run_client_benchmark = <function test_get_request_with_251308_compressed_chunked_payload.<locals>.run_client_benchmark at 0x00000157C41E7240>
#x1B[1m#x1B[31mC:\hostedtoolcache\windows\Python\3.12.10\x64\Lib\asyncio\base_events.py#x1B[0m:691: in run_until_complete
    #x1B[0m#x1B[94mreturn#x1B[39;49;00m future.result()#x1B[90m#x1B[39;49;00m
           ^^^^^^^^^^^^^^^#x1B[90m#x1B[39;49;00m
        future     = <Task finished name='Task-1092' coro=<test_get_request_with_251308_compressed_chunked_payload.<locals>.run_client_benc...nse payload is not completed: <ContentLengthError: 400, message='Not enough data to satisfy content length header.'>")>
        new_task   = True
        self       = <ProactorEventLoop running=False closed=False debug=False>
#x1B[1m#x1B[31mtests\test_benchmarks_client.py#x1B[0m:266: in run_client_benchmark
    #x1B[0m#x1B[94mawait#x1B[39;49;00m resp.read()#x1B[90m#x1B[39;49;00m
        aiohttp_client = <function aiohttp_client.<locals>.go at 0x00000157C41E54E0>
        app        = <Application 0x157c41252d0>
        client     = <aiohttp.test_utils.TestClient object at 0x00000157C56758E0>
        resp       = <ClientResponse(http://127.0.0.1:50064/) [200 OK]>
<CIMultiDictProxy('Content-Length': '270372', 'Content-Encoding': '...': 'application/octet-stream', 'Date': 'Wed, 01 Apr 2026 03:25:29 GMT', 'Server': 'Python/3.12 aiohttp/4.0.0a2.dev0')>

#x1B[1m#x1B[31maiohttp\client_reqrep.py#x1B[0m:592: in read
    #x1B[0m#x1B[96mself#x1B[39;49;00m._body = #x1B[94mawait#x1B[39;49;00m #x1B[96mself#x1B[39;49;00m.content.read()#x1B[90m#x1B[39;49;00m
                 ^^^^^^^^^^^^^^^^^^^^^^^^^#x1B[90m#x1B[39;49;00m
        self       = <ClientResponse(http://127.0.0.1:50064/) [200 OK]>
<CIMultiDictProxy('Content-Length': '270372', 'Content-Encoding': '...': 'application/octet-stream', 'Date': 'Wed, 01 Apr 2026 03:25:29 GMT', 'Server': 'Python/3.12 aiohttp/4.0.0a2.dev0')>

#x1B[1m#x1B[31maiohttp\streams.py#x1B[0m:418: in read
    #x1B[0mblock = #x1B[94mawait#x1B[39;49;00m #x1B[96mself#x1B[39;49;00m.readany()#x1B[90m#x1B[39;49;00m
            ^^^^^^^^^^^^^^^^^^^^#x1B[90m#x1B[39;49;00m
        block      = b'\xb0\xaf\xae\xad\xac\xab\xaa\xa9\xa8\xa7\xa6\xa5\xa4\xa3\xa2\xa1\xa0\x9f\x9e\x9d\x9c\x9b\x9a\x99\x98\x97\x96\x95\x94...7\x18\x19\x1a\x1b\x1c\x1d\x1e\x1f !"#$%&\'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_`abcdefghijklmnopqr'
        blocks     = [b'\x00\x01\x00\x01\x02\x01\x00\x01\x02\x03\x02\x01\x00\x01\x02\x03\x04\x03\x02\x01\x00\x01\x02\x03\x04\x05\x04\x03\x0...\x18\x19\x1a\x1b\x1c\x1d\x1e\x1f !"#$%&\'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_`abcdefghijklmnopqr']
        n          = -1
        self       = <StreamReader e=ClientPayloadError("Response payload is not completed: <ContentLengthError: 400, message='Not enough data to satisfy content length header.'>")>
#x1B[1m#x1B[31maiohttp\streams.py#x1B[0m:440: in readany
    #x1B[0m#x1B[94mawait#x1B[39;49;00m #x1B[96mself#x1B[39;49;00m._wait(#x1B[33m"#x1B[39;49;00m#x1B[33mreadany#x1B[39;49;00m#x1B[33m"#x1B[39;49;00m)#x1B[90m#x1B[39;49;00m
        self       = <StreamReader e=ClientPayloadError("Response payload is not completed: <ContentLengthError: 400, message='Not enough data to satisfy content length header.'>")>
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _

self = <StreamReader e=ClientPayloadError("Response payload is not completed: <ContentLengthError: 400, message='Not enough data to satisfy content length header.'>")>
func_name = 'readany'

    #x1B[0m#x1B[94masync#x1B[39;49;00m #x1B[94mdef#x1B[39;49;00m#x1B[90m #x1B[39;49;00m#x1B[92m_wait#x1B[39;49;00m(#x1B[96mself#x1B[39;49;00m, func_name: #x1B[96mstr#x1B[39;49;00m) -> #x1B[94mNone#x1B[39;49;00m:#x1B[90m#x1B[39;49;00m
        #x1B[94mif#x1B[39;49;00m #x1B[95mnot#x1B[39;49;00m #x1B[96mself#x1B[39;49;00m._protocol.connected:#x1B[90m#x1B[39;49;00m
            #x1B[94mraise#x1B[39;49;00m #x1B[96mRuntimeError#x1B[39;49;00m(#x1B[33m"#x1B[39;49;00m#x1B[33mConnection closed.#x1B[39;49;00m#x1B[33m"#x1B[39;49;00m)#x1B[90m#x1B[39;49;00m
    #x1B[90m#x1B[39;49;00m
        #x1B[90m# StreamReader uses a future to link the protocol feed_data() method#x1B[39;49;00m#x1B[90m#x1B[39;49;00m
        #x1B[90m# to a read coroutine. Running two read coroutines at the same time#x1B[39;49;00m#x1B[90m#x1B[39;49;00m
        #x1B[90m# would have an unexpected behaviour. It would not possible to know#x1B[39;49;00m#x1B[90m#x1B[39;49;00m
        #x1B[90m# which coroutine would get the next data.#x1B[39;49;00m#x1B[90m#x1B[39;49;00m
        #x1B[94mif#x1B[39;49;00m #x1B[96mself#x1B[39;49;00m._waiter #x1B[95mis#x1B[39;49;00m #x1B[95mnot#x1B[39;49;00m #x1B[94mNone#x1B[39;49;00m:#x1B[90m#x1B[39;49;00m
            #x1B[94mraise#x1B[39;49;00m #x1B[96mRuntimeError#x1B[39;49;00m(#x1B[90m#x1B[39;49;00m
                #x1B[33m"#x1B[39;49;00m#x1B[33m%s#x1B[39;49;00m#x1B[33m() called while another coroutine is #x1B[39;49;00m#x1B[33m"#x1B[39;49;00m#x1B[90m#x1B[39;49;00m
                #x1B[33m"#x1B[39;49;00m#x1B[33malready waiting for incoming data#x1B[39;49;00m#x1B[33m"#x1B[39;49;00m % func_name#x1B[90m#x1B[39;49;00m
            )#x1B[90m#x1B[39;49;00m
    #x1B[90m#x1B[39;49;00m
        waiter = #x1B[96mself#x1B[39;49;00m._waiter = #x1B[96mself#x1B[39;49;00m._loop.create_future()#x1B[90m#x1B[39;49;00m
        #x1B[94mtry#x1B[39;49;00m:#x1B[90m#x1B[39;49;00m
            #x1B[94mwith#x1B[39;49;00m #x1B[96mself#x1B[39;49;00m._timer:#x1B[90m#x1B[39;49;00m
>               #x1B[94mawait#x1B[39;49;00m waiter#x1B[90m#x1B[39;49;00m
#x1B[1m#x1B[31mE               aiohttp.client_exceptions.ClientPayloadError: Response payload is not completed: <ContentLengthError: 400, message='Not enough data to satisfy content length header.'>#x1B[0m

func_name  = 'readany'
self       = <StreamReader e=ClientPayloadError("Response payload is not completed: <ContentLengthError: 400, message='Not enough data to satisfy content length header.'>")>
waiter     = <Future finished exception=ClientPayloadError("Response payload is not completed: <ContentLengthError: 400, message='Not enough data to satisfy content length header.'>")>

#x1B[1m#x1B[31maiohttp\streams.py#x1B[0m:358: ClientPayloadError
tests.test_benchmarks_client::test_get_request_with_251308_compressed_chunked_payload[zlib-pyloop]

Flake rate in main: 8.33% (Passed 11 times, Failed 1 times)

Stack Traces | 0.612s run time
self = <aiohttp.client_proto.ResponseHandler object at 0x0000014202506210>
exc = None

    #x1B[0m#x1B[94mdef#x1B[39;49;00m#x1B[90m #x1B[39;49;00m#x1B[92mconnection_lost#x1B[39;49;00m(#x1B[96mself#x1B[39;49;00m, exc: #x1B[96mBaseException#x1B[39;49;00m | #x1B[94mNone#x1B[39;49;00m) -> #x1B[94mNone#x1B[39;49;00m:#x1B[90m#x1B[39;49;00m
        #x1B[96mself#x1B[39;49;00m._connection_lost_called = #x1B[94mTrue#x1B[39;49;00m#x1B[90m#x1B[39;49;00m
        #x1B[96mself#x1B[39;49;00m._drop_timeout()#x1B[90m#x1B[39;49;00m
    #x1B[90m#x1B[39;49;00m
        original_connection_error = exc#x1B[90m#x1B[39;49;00m
        reraised_exc = original_connection_error#x1B[90m#x1B[39;49;00m
    #x1B[90m#x1B[39;49;00m
        connection_closed_cleanly = original_connection_error #x1B[95mis#x1B[39;49;00m #x1B[94mNone#x1B[39;49;00m#x1B[90m#x1B[39;49;00m
    #x1B[90m#x1B[39;49;00m
        #x1B[94mif#x1B[39;49;00m #x1B[96mself#x1B[39;49;00m._closed #x1B[95mis#x1B[39;49;00m #x1B[95mnot#x1B[39;49;00m #x1B[94mNone#x1B[39;49;00m:#x1B[90m#x1B[39;49;00m
            #x1B[90m# If someone is waiting for the closed future,#x1B[39;49;00m#x1B[90m#x1B[39;49;00m
            #x1B[90m# we should set it to None or an exception. If#x1B[39;49;00m#x1B[90m#x1B[39;49;00m
            #x1B[90m# self._closed is None, it means that#x1B[39;49;00m#x1B[90m#x1B[39;49;00m
            #x1B[90m# connection_lost() was called already#x1B[39;49;00m#x1B[90m#x1B[39;49;00m
            #x1B[90m# or nobody is waiting for it.#x1B[39;49;00m#x1B[90m#x1B[39;49;00m
            #x1B[94mif#x1B[39;49;00m connection_closed_cleanly:#x1B[90m#x1B[39;49;00m
                set_result(#x1B[96mself#x1B[39;49;00m._closed, #x1B[94mNone#x1B[39;49;00m)#x1B[90m#x1B[39;49;00m
            #x1B[94melse#x1B[39;49;00m:#x1B[90m#x1B[39;49;00m
                #x1B[94massert#x1B[39;49;00m original_connection_error #x1B[95mis#x1B[39;49;00m #x1B[95mnot#x1B[39;49;00m #x1B[94mNone#x1B[39;49;00m#x1B[90m#x1B[39;49;00m
                set_exception(#x1B[90m#x1B[39;49;00m
                    #x1B[96mself#x1B[39;49;00m._closed,#x1B[90m#x1B[39;49;00m
                    ClientConnectionError(#x1B[90m#x1B[39;49;00m
                        #x1B[33mf#x1B[39;49;00m#x1B[33m"#x1B[39;49;00m#x1B[33mConnection lost: #x1B[39;49;00m#x1B[33m{#x1B[39;49;00moriginal_connection_error#x1B[90m #x1B[39;49;00m#x1B[33m!s}#x1B[39;49;00m#x1B[33m"#x1B[39;49;00m,#x1B[90m#x1B[39;49;00m
                    ),#x1B[90m#x1B[39;49;00m
                    original_connection_error,#x1B[90m#x1B[39;49;00m
                )#x1B[90m#x1B[39;49;00m
    #x1B[90m#x1B[39;49;00m
        #x1B[94mif#x1B[39;49;00m #x1B[96mself#x1B[39;49;00m._payload_parser #x1B[95mis#x1B[39;49;00m #x1B[95mnot#x1B[39;49;00m #x1B[94mNone#x1B[39;49;00m:#x1B[90m#x1B[39;49;00m
            #x1B[94mwith#x1B[39;49;00m suppress(#x1B[96mException#x1B[39;49;00m):  #x1B[90m# FIXME: log this somehow?#x1B[39;49;00m#x1B[90m#x1B[39;49;00m
                #x1B[96mself#x1B[39;49;00m._payload_parser.feed_eof()#x1B[90m#x1B[39;49;00m
    #x1B[90m#x1B[39;49;00m
        uncompleted = #x1B[94mNone#x1B[39;49;00m#x1B[90m#x1B[39;49;00m
        #x1B[94mif#x1B[39;49;00m #x1B[96mself#x1B[39;49;00m._parser #x1B[95mis#x1B[39;49;00m #x1B[95mnot#x1B[39;49;00m #x1B[94mNone#x1B[39;49;00m:#x1B[90m#x1B[39;49;00m
            #x1B[94mtry#x1B[39;49;00m:#x1B[90m#x1B[39;49;00m
>               uncompleted = #x1B[96mself#x1B[39;49;00m._parser.feed_eof()#x1B[90m#x1B[39;49;00m
                              ^^^^^^^^^^^^^^^^^^^^^^^#x1B[90m#x1B[39;49;00m

__class__  = <class 'aiohttp.client_proto.ResponseHandler'>
client_payload_exc_msg = "Response payload is not completed: <ContentLengthError: 400, message='Not enough data to satisfy content length header.'>"
connection_closed_cleanly = True
exc        = None
original_connection_error = None
reraised_exc = None
self       = <aiohttp.client_proto.ResponseHandler object at 0x0000014202506210>
uncompleted = None

#x1B[1m#x1B[31maiohttp\client_proto.py#x1B[0m:148: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _

>   #x1B[0m#x1B[94mraise#x1B[39;49;00m ContentLengthError(#x1B[90m#x1B[39;49;00m
#x1B[1m#x1B[31mE   aiohttp.http_exceptions.ContentLengthError: 400, message:#x1B[0m
#x1B[1m#x1B[31mE     Not enough data to satisfy content length header.#x1B[0m


#x1B[1m#x1B[31maiohttp\_http_parser.pyx#x1B[0m:556: ContentLengthError

#x1B[33mThe above exception was the direct cause of the following exception:#x1B[0m

loop = <ProactorEventLoop running=False closed=False debug=False>
aiohttp_client = <function aiohttp_client.<locals>.go at 0x0000014201606A30>
benchmark = <pytest_codspeed.plugin.BenchmarkFixture object at 0x000001420250C850>

    #x1B[0m#x1B[37m@pytest#x1B[39;49;00m.mark.usefixtures(#x1B[33m"#x1B[39;49;00m#x1B[33mparametrize_zlib_backend#x1B[39;49;00m#x1B[33m"#x1B[39;49;00m)#x1B[90m#x1B[39;49;00m
    #x1B[94mdef#x1B[39;49;00m#x1B[90m #x1B[39;49;00m#x1B[92mtest_get_request_with_251308_compressed_chunked_payload#x1B[39;49;00m(#x1B[90m#x1B[39;49;00m
        loop: asyncio.AbstractEventLoop,#x1B[90m#x1B[39;49;00m
        aiohttp_client: AiohttpClient,#x1B[90m#x1B[39;49;00m
        benchmark: BenchmarkFixture,#x1B[90m#x1B[39;49;00m
    ) -> #x1B[94mNone#x1B[39;49;00m:#x1B[90m#x1B[39;49;00m
    #x1B[90m    #x1B[39;49;00m#x1B[33m"""Benchmark compressed GET requests with a payload of 251308."""#x1B[39;49;00m#x1B[90m#x1B[39;49;00m
        #x1B[90m# This payload compresses to 251308 bytes#x1B[39;49;00m#x1B[90m#x1B[39;49;00m
        payload = #x1B[33mb#x1B[39;49;00m#x1B[33m"#x1B[39;49;00m#x1B[33m"#x1B[39;49;00m.join(#x1B[90m#x1B[39;49;00m
            [#x1B[90m#x1B[39;49;00m
                #x1B[96mbytes#x1B[39;49;00m((*#x1B[96mrange#x1B[39;49;00m(#x1B[94m0#x1B[39;49;00m, i), *#x1B[96mrange#x1B[39;49;00m(i, #x1B[94m0#x1B[39;49;00m, -#x1B[94m1#x1B[39;49;00m)))#x1B[90m#x1B[39;49;00m
                #x1B[94mfor#x1B[39;49;00m _ #x1B[95min#x1B[39;49;00m #x1B[96mrange#x1B[39;49;00m(#x1B[94m255#x1B[39;49;00m)#x1B[90m#x1B[39;49;00m
                #x1B[94mfor#x1B[39;49;00m i #x1B[95min#x1B[39;49;00m #x1B[96mrange#x1B[39;49;00m(#x1B[94m255#x1B[39;49;00m)#x1B[90m#x1B[39;49;00m
            ]#x1B[90m#x1B[39;49;00m
        )#x1B[90m#x1B[39;49;00m
    #x1B[90m#x1B[39;49;00m
        #x1B[94masync#x1B[39;49;00m #x1B[94mdef#x1B[39;49;00m#x1B[90m #x1B[39;49;00m#x1B[92mhandler#x1B[39;49;00m(request: web.Request) -> web.Response:#x1B[90m#x1B[39;49;00m
            resp = web.Response(body=payload, zlib_executor_size=#x1B[94m16384#x1B[39;49;00m)#x1B[90m#x1B[39;49;00m
            resp.enable_compression()#x1B[90m#x1B[39;49;00m
            #x1B[94mreturn#x1B[39;49;00m resp#x1B[90m#x1B[39;49;00m
    #x1B[90m#x1B[39;49;00m
        app = web.Application()#x1B[90m#x1B[39;49;00m
        app.router.add_route(#x1B[33m"#x1B[39;49;00m#x1B[33mGET#x1B[39;49;00m#x1B[33m"#x1B[39;49;00m, #x1B[33m"#x1B[39;49;00m#x1B[33m/#x1B[39;49;00m#x1B[33m"#x1B[39;49;00m, handler)#x1B[90m#x1B[39;49;00m
    #x1B[90m#x1B[39;49;00m
        #x1B[94masync#x1B[39;49;00m #x1B[94mdef#x1B[39;49;00m#x1B[90m #x1B[39;49;00m#x1B[92mrun_client_benchmark#x1B[39;49;00m() -> #x1B[94mNone#x1B[39;49;00m:#x1B[90m#x1B[39;49;00m
            client = #x1B[94mawait#x1B[39;49;00m aiohttp_client(app)#x1B[90m#x1B[39;49;00m
            resp = #x1B[94mawait#x1B[39;49;00m client.get(#x1B[33m"#x1B[39;49;00m#x1B[33m/#x1B[39;49;00m#x1B[33m"#x1B[39;49;00m)#x1B[90m#x1B[39;49;00m
            #x1B[94mawait#x1B[39;49;00m resp.read()#x1B[90m#x1B[39;49;00m
            #x1B[94mawait#x1B[39;49;00m client.close()#x1B[90m#x1B[39;49;00m
    #x1B[90m#x1B[39;49;00m
>       #x1B[37m@benchmark#x1B[39;49;00m#x1B[90m#x1B[39;49;00m
         ^^^^^^^^^#x1B[90m#x1B[39;49;00m

aiohttp_client = <function aiohttp_client.<locals>.go at 0x0000014201606A30>
app        = <Application 0x1420138fa00>
benchmark  = <pytest_codspeed.plugin.BenchmarkFixture object at 0x000001420250C850>
handler    = <function test_get_request_with_251308_compressed_chunked_payload.<locals>.handler at 0x000001420138EF00>
loop       = <ProactorEventLoop running=False closed=False debug=False>
payload    = b'\x00\x01\x00\x01\x02\x01\x00\x01\x02\x03\x02\x01\x00\x01\x02\x03\x04\x03\x02\x01\x00\x01\x02\x03\x04\x05\x04\x03\x02...\x1f\x1e\x1d\x1c\x1b\x1a\x19\x18\x17\x16\x15\x14\x13\x12\x11\x10\x0f\x0e\r\x0c\x0b\n\t\x08\x07\x06\x05\x04\x03\x02\x01'
run_client_benchmark = <function test_get_request_with_251308_compressed_chunked_payload.<locals>.run_client_benchmark at 0x000001420138E090>

#x1B[1m#x1B[31mtests\test_benchmarks_client.py#x1B[0m:269: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
#x1B[1m#x1B[31mC:\hostedtoolcache\windows\Python\3.14.3\x64\Lib\site-packages\pytest_codspeed\plugin.py#x1B[0m:358: in __call__
    #x1B[0m#x1B[94mreturn#x1B[39;49;00m target(*args, **kwargs)#x1B[90m#x1B[39;49;00m
           ^^^^^^^^^^^^^^^^^^^^^^^#x1B[90m#x1B[39;49;00m
        args       = ()
        kwargs     = {}
        self       = <pytest_codspeed.plugin.BenchmarkFixture object at 0x000001420250C850>
        target     = <function test_get_request_with_251308_compressed_chunked_payload.<locals>._run at 0x000001420138ECF0>
#x1B[1m#x1B[31mtests\test_benchmarks_client.py#x1B[0m:271: in _run
    #x1B[0mloop.run_until_complete(run_client_benchmark())#x1B[90m#x1B[39;49;00m
        loop       = <ProactorEventLoop running=False closed=False debug=False>
        run_client_benchmark = <function test_get_request_with_251308_compressed_chunked_payload.<locals>.run_client_benchmark at 0x000001420138E090>
#x1B[1m#x1B[31mC:\hostedtoolcache\windows\Python\3.14.3\x64\Lib\asyncio\base_events.py#x1B[0m:719: in run_until_complete
    #x1B[0m#x1B[94mreturn#x1B[39;49;00m future.result()#x1B[90m#x1B[39;49;00m
           ^^^^^^^^^^^^^^^#x1B[90m#x1B[39;49;00m
        future     = <Task finished name='Task-1080' coro=<test_get_request_with_251308_compressed_chunked_payload.<locals>.run_client_benc...nse payload is not completed: <ContentLengthError: 400, message='Not enough data to satisfy content length header.'>")>
        new_task   = True
        self       = <ProactorEventLoop running=False closed=False debug=False>
#x1B[1m#x1B[31mtests\test_benchmarks_client.py#x1B[0m:266: in run_client_benchmark
    #x1B[0m#x1B[94mawait#x1B[39;49;00m resp.read()#x1B[90m#x1B[39;49;00m
        aiohttp_client = <function aiohttp_client.<locals>.go at 0x0000014201606A30>
        app        = <Application 0x1420138fa00>
        client     = <aiohttp.test_utils.TestClient object at 0x000001420250CA50>
        resp       = <ClientResponse(http://127.0.0.1:58857/) [200 OK]>
<CIMultiDictProxy('Content-Length': '250588', 'Content-Encoding': '...': 'application/octet-stream', 'Date': 'Wed, 01 Apr 2026 03:24:56 GMT', 'Server': 'Python/3.14 aiohttp/4.0.0a2.dev0')>

#x1B[1m#x1B[31maiohttp\client_reqrep.py#x1B[0m:592: in read
    #x1B[0m#x1B[96mself#x1B[39;49;00m._body = #x1B[94mawait#x1B[39;49;00m #x1B[96mself#x1B[39;49;00m.content.read()#x1B[90m#x1B[39;49;00m
                 ^^^^^^^^^^^^^^^^^^^^^^^^^#x1B[90m#x1B[39;49;00m
        self       = <ClientResponse(http://127.0.0.1:58857/) [200 OK]>
<CIMultiDictProxy('Content-Length': '250588', 'Content-Encoding': '...': 'application/octet-stream', 'Date': 'Wed, 01 Apr 2026 03:24:56 GMT', 'Server': 'Python/3.14 aiohttp/4.0.0a2.dev0')>

#x1B[1m#x1B[31maiohttp\streams.py#x1B[0m:418: in read
    #x1B[0mblock = #x1B[94mawait#x1B[39;49;00m #x1B[96mself#x1B[39;49;00m.readany()#x1B[90m#x1B[39;49;00m
            ^^^^^^^^^^^^^^^^^^^^#x1B[90m#x1B[39;49;00m
        block      = b'ihgfedcba`_^]\\[ZYXWVUTSRQPONMLKJIHGFEDCBA@?>=<;:9876543210/.-,+*)(\'&%$#"! \x1f\x1e\x1d\x1c\x1b\x1a\x19\x18\x17\x16...\x07\x06\x05\x04\x03\x02\x01\x00\x01\x02\x03\x04\x05\x06\x07\x08\t\n\x0b\x0c\r\x0e\x0f\x10\x11\x12\x13\x14\x15\x16\x17'
        blocks     = [b'\x00\x01\x00\x01\x02\x01\x00\x01\x02\x03\x02\x01\x00\x01\x02\x03\x04\x03\x02\x01\x00\x01\x02\x03\x04\x05\x04\x03\x0...x07\x06\x05\x04\x03\x02\x01\x00\x01\x02\x03\x04\x05\x06\x07\x08\t\n\x0b\x0c\r\x0e\x0f\x10\x11\x12\x13\x14\x15\x16\x17']
        n          = -1
        self       = <StreamReader e=ClientPayloadError("Response payload is not completed: <ContentLengthError: 400, message='Not enough data to satisfy content length header.'>")>
#x1B[1m#x1B[31maiohttp\streams.py#x1B[0m:440: in readany
    #x1B[0m#x1B[94mawait#x1B[39;49;00m #x1B[96mself#x1B[39;49;00m._wait(#x1B[33m"#x1B[39;49;00m#x1B[33mreadany#x1B[39;49;00m#x1B[33m"#x1B[39;49;00m)#x1B[90m#x1B[39;49;00m
        self       = <StreamReader e=ClientPayloadError("Response payload is not completed: <ContentLengthError: 400, message='Not enough data to satisfy content length header.'>")>
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _

self = <StreamReader e=ClientPayloadError("Response payload is not completed: <ContentLengthError: 400, message='Not enough data to satisfy content length header.'>")>
func_name = 'readany'

    #x1B[0m#x1B[94masync#x1B[39;49;00m #x1B[94mdef#x1B[39;49;00m#x1B[90m #x1B[39;49;00m#x1B[92m_wait#x1B[39;49;00m(#x1B[96mself#x1B[39;49;00m, func_name: #x1B[96mstr#x1B[39;49;00m) -> #x1B[94mNone#x1B[39;49;00m:#x1B[90m#x1B[39;49;00m
        #x1B[94mif#x1B[39;49;00m #x1B[95mnot#x1B[39;49;00m #x1B[96mself#x1B[39;49;00m._protocol.connected:#x1B[90m#x1B[39;49;00m
            #x1B[94mraise#x1B[39;49;00m #x1B[96mRuntimeError#x1B[39;49;00m(#x1B[33m"#x1B[39;49;00m#x1B[33mConnection closed.#x1B[39;49;00m#x1B[33m"#x1B[39;49;00m)#x1B[90m#x1B[39;49;00m
    #x1B[90m#x1B[39;49;00m
        #x1B[90m# StreamReader uses a future to link the protocol feed_data() method#x1B[39;49;00m#x1B[90m#x1B[39;49;00m
        #x1B[90m# to a read coroutine. Running two read coroutines at the same time#x1B[39;49;00m#x1B[90m#x1B[39;49;00m
        #x1B[90m# would have an unexpected behaviour. It would not possible to know#x1B[39;49;00m#x1B[90m#x1B[39;49;00m
        #x1B[90m# which coroutine would get the next data.#x1B[39;49;00m#x1B[90m#x1B[39;49;00m
        #x1B[94mif#x1B[39;49;00m #x1B[96mself#x1B[39;49;00m._waiter #x1B[95mis#x1B[39;49;00m #x1B[95mnot#x1B[39;49;00m #x1B[94mNone#x1B[39;49;00m:#x1B[90m#x1B[39;49;00m
            #x1B[94mraise#x1B[39;49;00m #x1B[96mRuntimeError#x1B[39;49;00m(#x1B[90m#x1B[39;49;00m
                #x1B[33m"#x1B[39;49;00m#x1B[33m%s#x1B[39;49;00m#x1B[33m() called while another coroutine is #x1B[39;49;00m#x1B[33m"#x1B[39;49;00m#x1B[90m#x1B[39;49;00m
                #x1B[33m"#x1B[39;49;00m#x1B[33malready waiting for incoming data#x1B[39;49;00m#x1B[33m"#x1B[39;49;00m % func_name#x1B[90m#x1B[39;49;00m
            )#x1B[90m#x1B[39;49;00m
    #x1B[90m#x1B[39;49;00m
        waiter = #x1B[96mself#x1B[39;49;00m._waiter = #x1B[96mself#x1B[39;49;00m._loop.create_future()#x1B[90m#x1B[39;49;00m
        #x1B[94mtry#x1B[39;49;00m:#x1B[90m#x1B[39;49;00m
            #x1B[94mwith#x1B[39;49;00m #x1B[96mself#x1B[39;49;00m._timer:#x1B[90m#x1B[39;49;00m
>               #x1B[94mawait#x1B[39;49;00m waiter#x1B[90m#x1B[39;49;00m
#x1B[1m#x1B[31mE               aiohttp.client_exceptions.ClientPayloadError: Response payload is not completed: <ContentLengthError: 400, message='Not enough data to satisfy content length header.'>#x1B[0m

func_name  = 'readany'
self       = <StreamReader e=ClientPayloadError("Response payload is not completed: <ContentLengthError: 400, message='Not enough data to satisfy content length header.'>")>
waiter     = <Future finished exception=ClientPayloadError("Response payload is not completed: <ContentLengthError: 400, message='Not enough data to satisfy content length header.'>")>

#x1B[1m#x1B[31maiohttp\streams.py#x1B[0m:358: ClientPayloadError

To view more test analytics, go to the Test Analytics Dashboard
📋 Got 3 mins? Take this short survey to help us improve Test Analytics.

@codspeed-hq
Copy link
Copy Markdown

codspeed-hq bot commented Jan 15, 2026

Merging this PR will degrade performance by 43.97%

⚡ 1 improved benchmark
❌ 5 regressed benchmarks
✅ 53 untouched benchmarks

⚠️ Please fix the performance issues or acknowledge them on CodSpeed.

Performance Changes

Benchmark BASE HEAD Efficiency
test_get_request_with_251308_compressed_chunked_payload[zlib_ng.zlib_ng-pyloop] 208.2 ms 245 ms -15.03%
test_get_request_with_251308_compressed_chunked_payload[isal.isal_zlib-pyloop] 64.2 ms 114.5 ms -43.97%
test_ten_streamed_responses_iter_chunks[pyloop] 16.3 ms 18.2 ms -10.83%
test_ten_streamed_responses_iter_chunked_4096[pyloop] 28.2 ms 33.1 ms -14.86%
test_ten_streamed_responses_iter_chunked_65536[pyloop] 23 ms 25.1 ms -8.22%
test_read_large_binary_websocket_messages[pyloop] 8,941.8 µs 48.9 µs ×180

Comparing Dreamsorcerer-patch-5 (cb380ef) with master (bced1bc)

Open in CodSpeed

@Dreamsorcerer Dreamsorcerer changed the title Test chunk splits after pause Allow decompression to continue after exceeding max_length Jan 15, 2026
@Dreamsorcerer
Copy link
Copy Markdown
Member Author

I'm pulling out the .read_chunk(decode=True). If someone wants to work on implementing that in a separate PR, the test is:

    @pytest.mark.skipif(sys.version_info < (3, 11), reason="wbits not available")
    async def test_read_chunk_with_content_encoding_deflate(self) -> None:
        content = b"A" * 1_000_000  # Large enough to exceed max_length.
        compressed = ZLibBackend.compress(content, wbits=-ZLibBackend.MAX_WBITS)

        h = CIMultiDictProxy(CIMultiDict({CONTENT_ENCODING: "deflate"}))
        with Stream(compressed + b"\r\n--:--") as stream:
            obj = aiohttp.BodyPartReader(BOUNDARY, h, stream)
            result = b""
            while chunk := await obj.read_chunk(decode=True):
                result += chunk
        assert result == content

@ZhaoMJ
Copy link
Copy Markdown
Contributor

ZhaoMJ commented Mar 26, 2026

Is this ready to be merged? We're running into Decompressed data exceeds the configured limit ClientPayloadError.

@aio-libs aio-libs deleted a comment from cashpilotthrive-hue Mar 26, 2026
@Dreamsorcerer
Copy link
Copy Markdown
Member Author

Is this ready to be merged? We're running into Decompressed data exceeds the configured limit ClientPayloadError.

tests/test_benchmarks_client.py::test_get_request_with_251308_compressed_chunked_payload is still failing on Windows. I've not figured out why yet. If you want to help debug and figure out why that is failing, then we'll be able to finish off this PR quickly.
https://github.com/aio-libs/aiohttp/actions/runs/23598065321/job/68720269928?pr=11966#step:12:9701

@Dreamsorcerer Dreamsorcerer force-pushed the Dreamsorcerer-patch-5 branch 5 times, most recently from e3eeb81 to b420704 Compare March 30, 2026 18:58
@Dreamsorcerer Dreamsorcerer force-pushed the Dreamsorcerer-patch-5 branch 3 times, most recently from a558cbc to 7f4e807 Compare March 30, 2026 23:38
@bdraco
Copy link
Copy Markdown
Member

bdraco commented Apr 1, 2026

looks like the fundamental issue is that we can have data still in the buffer that isn't read yet when the connection closed and the reader gets killed at connection close leaving it stuck

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

backport-3.13 Trigger automatic backporting to the 3.13 release branch by Patchback robot backport-3.14 Trigger automatic backporting to the 3.14 release branch by Patchback robot bot:chronographer:skip This PR does not need to include a change note

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants