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

Starlette 0.13.3 hangs with StreamingResponse & httpx client #908

Closed
JayH5 opened this issue Apr 21, 2020 · 2 comments
Closed

Starlette 0.13.3 hangs with StreamingResponse & httpx client #908

JayH5 opened this issue Apr 21, 2020 · 2 comments
Labels
bug Something isn't working

Comments

@JayH5
Copy link
Member

JayH5 commented Apr 21, 2020

When using a StreamingResponse and, by extension, any custom middleware (any subclass of BaseHTTPMiddleware) with Starlette 0.13.3, with the async httpx client directly connected to the app (i.e. httpx.AsyncClient(app=...)), Starlette responses never complete. This did not happen with Starlette 0.13.2.

Minimal-ish test case:
With starlette==0.13.3, httpx==0.12.1, and pytest + pytest-asyncio:

import httpx
import pytest
from starlette.applications import Starlette
from starlette.responses import StreamingResponse
from starlette.routing import Route


@pytest.fixture
def app():
    def data():
        yield b"data"

    async def stream(request):
        return StreamingResponse(data())

    return Starlette(routes=[Route("/stream", stream)])


@pytest.fixture
async def client(app):
    async with httpx.AsyncClient(app=app) as client:
        yield client


@pytest.mark.asyncio
async def test_middleware(client):
    response = await client.get("http://example.com/stream")
    assert response.status_code == 200

Expected behaviour:
The test passes

Actual behaviour:
The await client.get() call never returns/completes.

Stacktrace:
If you hit ctrl-c while the test is stuck you get a long stacktrace. But the important bits I believe are:

File "/Users/jamie/.virtualenvs/tempenv-51153646622a/lib/python3.7/site-packages/starlette/responses.py", line 202, in listen_for_disconnect
    message = await receive()
  File "/Users/jamie/.virtualenvs/tempenv-51153646622a/lib/python3.7/site-packages/httpx/_dispatch/asgi.py", line 78, in receive
    body = await request_body_chunks.__anext__()

Suspected cause:
Starlette calls both the receive() (to check for disconnects) and send() (to send the response) concurrently in its StreamingResponse type (since #839). It tries to wait for either of those two options to complete (using asyncio.wait({tasks}, return_when=asyncio.FIRST_COMPLETED)). Because the ASGI dispatch in httpx is generally effectively synchronous, both send() and receive() may never return control to the event loop when called which means that if either task that is waited on does not complete, then asyncio.wait() will not return and the program will remain stuck in an infinite loop inside the StreamingResponse.listen_for_disconnect method.

@JayH5
Copy link
Member Author

JayH5 commented Apr 23, 2020

I think this will actually impact anything that involves a StreamingResponse. Confirmed this applies to any StreamingResponse. Updated the issue title and description.

@JayH5 JayH5 changed the title Starlette 0.13.3 hangs with custom middleware & httpx client Starlette 0.13.3 hangs with StreamingResponse & httpx client Apr 26, 2020
@florimondmanca florimondmanca added the bug Something isn't working label Apr 30, 2020
JayH5 added a commit to JayH5/starlette that referenced this issue May 1, 2020
@JayH5
Copy link
Member Author

JayH5 commented May 12, 2020

Fixed on the client side in encode/httpx#919

@JayH5 JayH5 closed this as completed May 12, 2020
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
bug Something isn't working
Projects
None yet
Development

Successfully merging a pull request may close this issue.

2 participants