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

Handle asyncio.CancelledError when socket is closed without flushing and middleware in use #172

Merged

Conversation

stopdropandrew
Copy link
Contributor

With the release of python 3.11.7 and using Starlette's BaseHTTPMiddleware, when a socket closes before finishing reading a response, I've noticed an asyncio.CancelledError being raised. This does not occur on 3.11.6.

Repro

from fastapi.responses import PlainTextResponse
from fastapi import FastAPI, Request
from starlette.middleware.base import BaseHTTPMiddleware, RequestResponseEndpoint


class ExampleMiddleware(BaseHTTPMiddleware):
    async def dispatch(self, request: Request, call_next: RequestResponseEndpoint):
        return await call_next(request)


app = FastAPI()
app.add_middleware(ExampleMiddleware)


@app.get(
    "/health",
    response_class=PlainTextResponse,
)
def health():
    return "Healthy"

Script to exhibit behavior

import socket

connection = socket.create_connection(("127.0.0.1", 8000))
connection.sendall(b"GET /health HTTP/1.1\r\nHost: localhost\r\n\r\n")
print(connection.recv(20).decode("utf8"))
connection.shutdown(socket.SHUT_RDWR)
connection.close()

Error

Exception in callback StreamReaderProtocol.connection_made.<locals>.callback(<Task cancell...io/run.py:90>>) at /Users/agrim/.pyenv/versions/3.11.7/lib/python3.11/asyncio/streams.py:248
handle: <Handle StreamReaderProtocol.connection_made.<locals>.callback(<Task cancell...io/run.py:90>>) at /Users/agrim/.pyenv/versions/3.11.7/lib/python3.11/asyncio/streams.py:248>
Traceback (most recent call last):
  File "/Users/agrim/.pyenv/versions/3.11.7/lib/python3.11/asyncio/events.py", line 80, in _run
    self._context.run(self._callback, *self._args)
  File "/Users/agrim/.pyenv/versions/3.11.7/lib/python3.11/asyncio/streams.py", line 249, in callback
    exc = task.exception()
          ^^^^^^^^^^^^^^^^
  File "/Users/agrim/Library/Caches/pypoetry/virtualenvs/hypercorn-python-3-11-7-VsPAhN4z-py3.11/lib/python3.11/site-packages/hypercorn/asyncio/run.py", line 96, in _server_callback
    await TCPServer(app, loop, config, context, reader, writer)
  File "/Users/agrim/Library/Caches/pypoetry/virtualenvs/hypercorn-python-3-11-7-VsPAhN4z-py3.11/lib/python3.11/site-packages/hypercorn/asyncio/tcp_server.py", line 74, in run
    await self._close()
  File "/Users/agrim/Library/Caches/pypoetry/virtualenvs/hypercorn-python-3-11-7-VsPAhN4z-py3.11/lib/python3.11/site-packages/hypercorn/asyncio/tcp_server.py", line 117, in _close
    await self.writer.wait_closed()
  File "/Users/agrim/.pyenv/versions/3.11.7/lib/python3.11/asyncio/streams.py", line 361, in wait_closed
    await self._protocol._get_close_waiter(self)
asyncio.exceptions.CancelledError

Notes

  • I don't see this behavior with uvicorn. Also, if I switch to ASGIMiddleware I don't see this behavior.
  • I tried writing a test but don't have enough familiarity with hypercorn's tests.
  • I have a rudimentary idea of what's happening, but don't have enough familiarity with asyncio streams, starlette, and hypercorn interactions to know if this is the right solution. But it does stop the issue.

@shadchin
Copy link

I confirm that the patch helped me

@pgjones pgjones merged commit 1f874fc into pgjones:main Dec 27, 2023
11 of 13 checks passed
@pgjones
Copy link
Owner

pgjones commented Dec 27, 2023

Thanks

@stopdropandrew stopdropandrew deleted the fix-cancelled-error-when-using-middleware branch February 3, 2024 22:46
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants