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

pytest hangs with trivial example #92

Open
r-owen opened this issue Dec 6, 2024 · 4 comments
Open

pytest hangs with trivial example #92

r-owen opened this issue Dec 6, 2024 · 4 comments
Labels
bug Something isn't working

Comments

@r-owen
Copy link

r-owen commented Dec 6, 2024

Describe the bug

I have been having a lot of problems with pytest hanging when trying to test a fastapi server.
I finally narrowed down one clear case (though I think I am facing others, as well): if the websocket endpoint returns without accepting the websocket then the test will hang.

To Reproduce

Steps to reproduce the behavior:

  1. Install fastapi and pytest-asyncio (as well as httpx-ws 0.62)
  2. Run the attached file using "pytest test_hang.py". I see it hang. When I ctrl-C three times I get the following (bits of it are printed after each of the three ctrl-C; the third is needed to get back to a prompt):
 pytest test_trivial_hang.py
/opt/miniconda3/envs/web/lib/python3.12/site-packages/pytest_asyncio/plugin.py:208: PytestDeprecationWarning: The configuration option "asyncio_default_fixture_loop_scope" is unset.
The event loop scope for asynchronous fixtures will default to the fixture caching scope. Future versions of pytest-asyncio will default the loop scope for asynchronous fixtures to function scope. Set the default fixture loop scope explicitly in order to avoid unexpected behavior in the future. Valid fixture loop scopes are: "function", "class", "module", "package", "session"

  warnings.warn(PytestDeprecationWarning(_DEFAULT_FIXTURE_LOOP_SCOPE_UNSET))
========================================================================================= test session starts ==========================================================================================
platform darwin -- Python 3.12.5, pytest-8.3.2, pluggy-1.5.0
rootdir: /Users/rowen/Library/Mobile Documents/com~apple~CloudDocs/Notes/Weaving/Software/minimal_websocket_example
plugins: asyncio-0.24.0, anyio-4.4.0, subtests-0.13.1
asyncio: mode=Mode.STRICT, default_loop_scope=None
collected 1 item                                                                                                                                                                                       

test_trivial_hang.py ^C

!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! KeyboardInterrupt !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
/opt/miniconda3/envs/web/lib/python3.12/selectors.py:566: KeyboardInterrupt
(to show a full traceback on KeyboardInterrupt use --full-trace)
======================================================================================== no tests ran in 2.19s =========================================================================================
^CException ignored in: <module 'threading' from '/opt/miniconda3/envs/web/lib/python3.12/threading.py'>
Traceback (most recent call last):
  File "/opt/miniconda3/envs/web/lib/python3.12/threading.py", line 1594, in _shutdown
    atexit_call()
  File "/opt/miniconda3/envs/web/lib/python3.12/concurrent/futures/thread.py", line 31, in _python_exit
    t.join()
  File "/opt/miniconda3/envs/web/lib/python3.12/threading.py", line 1149, in join
    self._wait_for_tstate_lock()
  File "/opt/miniconda3/envs/web/lib/python3.12/threading.py", line 1169, in _wait_for_tstate_lock
    if lock.acquire(block, timeout):
       ^^^^^^^^^^^^^^^^^^^^^^^^^^^^
KeyboardInterrupt: 
Task was destroyed but it is pending!
  1. Edit the file to set SHOW_HANG false. This time the test passes (no hang).

Expected behavior

I should be able to run tests using pytest without the test hanging.

Configuration

  • Python version: 3.12.5
  • httpx-ws version: 0.6.2
  • httpx version: 0.27.2
  • fastapi version: 0.115.6

Additional context

Add any other context about the problem here.
test_trivial_hang.py.zip

@r-owen r-owen added the bug Something isn't working label Dec 6, 2024
@frankie567
Copy link
Owner

frankie567 commented Dec 7, 2024

So, yes, if ws.accept() is not called, it hangs forever. Honestly, that's a bit extreme case: if you don't call ws.accept() or ws.close() in your handler then yes, it can't work. I'm not really sure that's something we should account for.


Runnable script with Ruff below

# /// script
# requires-python = ">=3.9"
# dependencies = [
#     "fastapi",
#     "httpx-ws",
# ]
# ///
import asyncio

from fastapi import FastAPI, WebSocket
from httpx import AsyncClient

import httpx_ws
from httpx_ws.transport import ASGIWebSocketTransport

app = FastAPI()

SHOW_HANG = True


@app.websocket("/ws")
async def websocket_endpoint(ws: WebSocket) -> None:
    if not SHOW_HANG:
        await ws.accept()
    return


async def test_basics() -> None:
    async with AsyncClient(
        transport=ASGIWebSocketTransport(app), base_url="http://test"
    ) as client:
        async with httpx_ws.aconnect_ws("http://test/ws", client) as ws:
            assert True


if __name__ == "__main__":
    asyncio.run(test_basics())

@GreyElaina
Copy link
Contributor

always hangs? 🤔
could we introduce a timeout for waiting accepting?

@r-owen
Copy link
Author

r-owen commented Dec 7, 2024

I admit it’s not a bug. But it can occur unexpectedly (as happened to me) and the messages printed after ctrl-c are not at all helpful. Even improving those would be a big help

@frankie567
Copy link
Owner

Yes, I see 🤔 Then yes, open to add a reasonable timeout on the first receive to avoid this.

message = await self.receive()

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

No branches or pull requests

3 participants