Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Large payloads lead to BrokenPipe inside a request context manager #8191

Open
1 task done
jamescw19 opened this issue Feb 26, 2024 · 1 comment
Open
1 task done
Labels

Comments

@jamescw19
Copy link

Describe the bug

Making a request which has a very large payload (e.g. 50MB) usually receives a 413 error from the server it is calling. This means raise_for_status() returns a ClientResponseError as expected, which can be handled by downstream code.
This works as expected with a simple await session.post(...), but inside an async with session.post(...) block, attempts to raise the status error cause a ClientOSError with a broken pipe.

To Reproduce

import json
import sys
from aiohttp import ClientSession
from aiohttp.client_exceptions import ClientResponseError

async with ClientSession(base_url='https://www.google.com') as sess:
    large_data = [
        *[[0.000000,0.000000 + i * 0.00001] for i in range(0, 10000000)],
    ]
    payload = {"data": large_data}
    assert sys.getsizeof(json.dumps(payload)) > 1024*500
    sess = ClientSession(base_url='https://www.google.com')
    # This works
    try:
        res = await sess.request('post', '/zsdfj', json=payload)
        res.raise_for_status()
    except ClientResponseError:
        print("Request failed")

    # This fails with a BrokenPipe
    try:
        async with sess.request('post', '/zsdfj', json=payload) as res:
            res.raise_for_status()
    except ClientResponseError:
        print("Request in context manager failed")

Expected behavior

Both requests should result in a ClientReponseError.

Logs/tracebacks

Traceback (most recent call last):
  File "/opt/conda/envs/py38/lib/python3.8/site-packages/aiohttp/client_reqrep.py", line 644, in write_bytes
    await writer.write_eof()
  File "/opt/conda/envs/py38/lib/python3.8/site-packages/aiohttp/http_writer.py", line 158, in write_eof
    await self.drain()
  File "/opt/conda/envs/py38/lib/python3.8/site-packages/aiohttp/http_writer.py", line 171, in drain
    await self._protocol._drain_helper()
  File "/opt/conda/envs/py38/lib/python3.8/site-packages/aiohttp/base_protocol.py", line 90, in _drain_helper
    await asyncio.shield(waiter)
aiohttp.client_exceptions.ClientOSError: [Errno 32] Broken pipe
---------------------------------------------------------------------------
ClientOSError                             Traceback (most recent call last)
Cell In[22], line 24
     22 try:
     23     async with sess.request('post', '/zsdfj', json=payload) as res:
---> 24         res.raise_for_status()
     25 except ClientResponseError:
     26     print("Request in context manager failed")

File /opt/conda/envs/py38/lib/python3.8/site-packages/aiohttp/client.py:1213, in _RequestContextManager.__aexit__(self, exc_type, exc, tb)
   1201 async def __aexit__(
   1202     self,
   1203     exc_type: Optional[Type[BaseException]],
   (...)
   1210     # explicitly.  Otherwise connection error handling should kick in
   1211     # and close/recycle the connection as required.
   1212     self._resp.release()
-> 1213     await self._resp.wait_for_close()

File /opt/conda/envs/py38/lib/python3.8/site-packages/aiohttp/client_reqrep.py:1094, in ClientResponse.wait_for_close(self)
   1092 async def wait_for_close(self) -> None:
   1093     if self._writer is not None:
-> 1094         await self._writer
   1095     self.release()

File /opt/conda/envs/py38/lib/python3.8/site-packages/aiohttp/client_reqrep.py:644, in ClientRequest.write_bytes(self, writer, conn)
    642         protocol.set_exception(new_exc)
    643 except asyncio.CancelledError:
--> 644     await writer.write_eof()
    645 except Exception as exc:
    646     protocol.set_exception(exc)

File /opt/conda/envs/py38/lib/python3.8/site-packages/aiohttp/http_writer.py:158, in StreamWriter.write_eof(self, chunk)
    155 if chunk:
    156     self._write(chunk)
--> 158 await self.drain()
    160 self._eof = True

File /opt/conda/envs/py38/lib/python3.8/site-packages/aiohttp/http_writer.py:171, in StreamWriter.drain(self)
    163 """Flush the write buffer.
    164 
    165 The intended use is to write
   (...)
    168   await w.drain()
    169 """
    170 if self._protocol.transport is not None:
--> 171     await self._protocol._drain_helper()

File /opt/conda/envs/py38/lib/python3.8/site-packages/aiohttp/base_protocol.py:90, in BaseProtocol._drain_helper(self)
     88     waiter = self._loop.create_future()
     89     self._drain_waiter = waiter
---> 90 await asyncio.shield(waiter)

ClientOSError: [Errno 32] Broken pipe

Python Version

$ python --version
Python 3.8.18

aiohttp Version

$ python -m pip show aiohttp
Name: aiohttp
Version: 3.9.3
Summary: Async http client/server framework (asyncio)
Home-page: https://github.com/aio-libs/aiohttp
Author: 
Author-email: 
License: Apache 2
Location: /opt/conda/envs/py38/lib/python3.8/site-packages
Requires: aiosignal, async-timeout, attrs, frozenlist, multidict, yarl
Required-by: hb-field-boundary-detection

multidict Version

$ python -m pip show multidict
Name: multidict
Version: 6.0.5
Summary: multidict implementation
Home-page: https://github.com/aio-libs/multidict
Author: Andrew Svetlov
Author-email: andrew.svetlov@gmail.com
License: Apache 2
Location: /opt/conda/envs/py38/lib/python3.8/site-packages
Requires: 
Required-by: aiohttp, yarl

yarl Version

$ python -m pip show yarl
Name: yarl
Version: 1.9.4
Summary: Yet another URL library
Home-page: https://github.com/aio-libs/yarl
Author: Andrew Svetlov
Author-email: andrew.svetlov@gmail.com
License: Apache-2.0
Location: /opt/conda/envs/py38/lib/python3.8/site-packages
Requires: idna, multidict
Required-by: aiohttp, vcrpy

OS

$ uname -a
Linux 5.10.0-28-cloud-amd64 #1 SMP Debian 5.10.209-2 (2024-01-31) x86_64 GNU/Linux
$ lsb_release -a
Distributor ID: Debian
Description:    Debian GNU/Linux 11 (bullseye)
Release:        11
Codename:       bullseye

Related component

Client

Additional context

No response

Code of Conduct

  • I agree to follow the aio-libs Code of Conduct
@jamescw19 jamescw19 added the bug label Feb 26, 2024
@Dreamsorcerer
Copy link
Member

I think the server has disconnected prematurely, causing that exception to happen while trying to close the connection cleanly. Might have to try and suppress the exception there in __aexit__()...

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

No branches or pull requests

2 participants