From c3ee6a756495ccc016f8e15e9a3abddb7b6874dd Mon Sep 17 00:00:00 2001 From: eccedentesiast37 <168896007+eccedentesiast37@users.noreply.github.com> Date: Mon, 6 Jul 2026 01:02:37 +0530 Subject: [PATCH 1/4] docs: warn that simulate_ws context exit can hang if responder never returns --- falcon/testing/client.py | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/falcon/testing/client.py b/falcon/testing/client.py index 3259be5f8..4ee9d9990 100644 --- a/falcon/testing/client.py +++ b/falcon/testing/client.py @@ -1219,12 +1219,24 @@ def simulate_ws(self, path: str = '/', **kwargs: Any) -> _WSContextManager: This method returns an async context manager that can be used to obtain a managed :class:`falcon.testing.ASGIWebSocketSimulator` instance. + Exiting the context will simulate a close on the WebSocket (if not already closed) and await the completion of the task that is running the simulated ASGI request. - + + .. warning:: + If your ``on_websocket`` responder does not return (for example, + it runs an infinite loop without ever breaking out on + disconnect), exiting this context manager will hang + indefinitely while awaiting that task, regardless of any + ``timeout`` passed to :meth:`~falcon.testing.ASGIWebSocketSimulator.wait_ready`. + Ensure your responder returns promptly after the client + disconnects, or explicitly break out of any long-running + loops when :attr:`~falcon.testing.ASGIWebSocketSimulator.closed` + becomes ``True``. + In the following example, a series of WebSocket TEXT events are - received from the ASGI app:: + received from the ASGI app:: async with conductor.simulate_ws('/events') as ws: while some_condition: From 1ab12c79d1d21f42e68e72e4565f0a8864b0695d Mon Sep 17 00:00:00 2001 From: eccedentesiast37 <168896007+eccedentesiast37@users.noreply.github.com> Date: Mon, 6 Jul 2026 01:13:52 +0530 Subject: [PATCH 2/4] Add towncrier news fragment for simulate_ws docs warning --- docs/_newsfragments/2585.misc.rst | 7 +++++++ 1 file changed, 7 insertions(+) create mode 100644 docs/_newsfragments/2585.misc.rst diff --git a/docs/_newsfragments/2585.misc.rst b/docs/_newsfragments/2585.misc.rst new file mode 100644 index 000000000..4d7e7950c --- /dev/null +++ b/docs/_newsfragments/2585.misc.rst @@ -0,0 +1,7 @@ +Documented that exiting the context manager returned by +:meth:`~falcon.testing.ASGIConductor.simulate_ws` awaits completion of the +background task running the simulated ASGI request. If the +``on_websocket`` responder never returns (for example, an infinite loop +that does not break out when the connection is closed), this await will +hang indefinitely, regardless of any ``timeout`` passed to +:meth:`~falcon.testing.ASGIWebSocketSimulator.wait_ready`. \ No newline at end of file From f59349c9accef126ce429931e7a5dc8d47e93d53 Mon Sep 17 00:00:00 2001 From: eccedentesiast37 <168896007+eccedentesiast37@users.noreply.github.com> Date: Mon, 6 Jul 2026 01:33:11 +0530 Subject: [PATCH 3/4] Fix ruff formatting issues (trailing whitespace, line length) --- falcon/testing/client.py | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/falcon/testing/client.py b/falcon/testing/client.py index 4ee9d9990..d2c4bcb62 100644 --- a/falcon/testing/client.py +++ b/falcon/testing/client.py @@ -1217,26 +1217,27 @@ def simulate_ws(self, path: str = '/', **kwargs: Any) -> _WSContextManager: All keyword arguments are passed through to :meth:`falcon.testing.create_scope_ws`. - This method returns an async context manager that can be used to obtain + This method returns an async context manager that can be used to obtain a managed :class:`falcon.testing.ASGIWebSocketSimulator` instance. Exiting the context will simulate a close on the WebSocket (if not already closed) and await the completion of the task that is running the simulated ASGI request. - + .. warning:: If your ``on_websocket`` responder does not return (for example, it runs an infinite loop without ever breaking out on disconnect), exiting this context manager will hang indefinitely while awaiting that task, regardless of any - ``timeout`` passed to :meth:`~falcon.testing.ASGIWebSocketSimulator.wait_ready`. + ``timeout`` passed to + :meth:`~falcon.testing.ASGIWebSocketSimulator.wait_ready`. Ensure your responder returns promptly after the client disconnects, or explicitly break out of any long-running loops when :attr:`~falcon.testing.ASGIWebSocketSimulator.closed` becomes ``True``. - + In the following example, a series of WebSocket TEXT events are - received from the ASGI app:: + received from the ASGI app:: async with conductor.simulate_ws('/events') as ws: while some_condition: From e80f3902abd408fd2c8cb9e0dde1dd7f34825572 Mon Sep 17 00:00:00 2001 From: eccedentesiast37 <168896007+eccedentesiast37@users.noreply.github.com> Date: Tue, 14 Jul 2026 12:06:40 +0530 Subject: [PATCH 4/4] fix: correct pre-existing indentation typo caught by ruff format --- falcon/testing/client.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/falcon/testing/client.py b/falcon/testing/client.py index d2c4bcb62..53f0a6bc9 100644 --- a/falcon/testing/client.py +++ b/falcon/testing/client.py @@ -1217,7 +1217,7 @@ def simulate_ws(self, path: str = '/', **kwargs: Any) -> _WSContextManager: All keyword arguments are passed through to :meth:`falcon.testing.create_scope_ws`. - This method returns an async context manager that can be used to obtain + This method returns an async context manager that can be used to obtain a managed :class:`falcon.testing.ASGIWebSocketSimulator` instance. Exiting the context will simulate a close on the WebSocket (if not