TestClient slows with multiple calls to websocket_connect #2570
-
I noticed an anomalous slow down in a test in which multiple I've pared back the application and test to this: import unittest
import starlette.applications
import starlette.websockets
import starlette.routing
import starlette.testclient
async def websocket_endpoint(websocket):
await websocket.accept()
try:
while True:
await websocket.receive_json()
except starlette.websockets.WebSocketDisconnect as e:
print(e)
finally:
await websocket.close()
app = starlette.applications.Starlette(debug=True,
routes=[starlette.routing.WebSocketRoute('/ws', websocket_endpoint)],
on_startup=[])
class TestWebsocket(unittest.TestCase):
def test_websocket(self):
client = starlette.testclient.TestClient(app)
with (client.websocket_connect("/ws"),
client.websocket_connect("/ws")):
self.assertTrue(True) In the case where I noticed the problem there is a dramatic increase in execution time between a test with one call to
If I remove the second call to
Specifics of the environment:
I've been unable to identify what makes the machine notable compared to others that don't demonstrate the same severity. In order to observe an increase in execution time on other machines I've had to include 20 calls to I spent some time trying to investigate the source of the slowness and turned up mostly time spent on futex, I think linked to the queues backing send and receive in the TestClient. I was able to at least partly confirm issues with the scheduling by pinning the test to a single CPU (where the machine has 8 total), no particular CPU matters for this case:
Trying to identify potential sources of CPU contention or difficulty I turned up starlette.testclient.WebSocketTestSession._asgi_receive which is testing the receive queue and conditionally invoking I don't totally grasp the interaction causing a problem here but suspecting the loop was spinning on the lock backing the Queue I tried modifying import anyio
async def _asgi_receive(self):
while self._receive_queue.empty():
await anyio.sleep(0.001) # avoid special-coding on asyncio.sleep(0)
return self._receive_queue.get()
starlette.testclient.WebSocketTestSession._asgi_receive = _asgi_receive And observed an improvement in execution speed to a level I expect:
As mentioned, this effect issue seems less pronounced on other machines but observable with tens of calls to A search of the discussion history turned up Testing interaction of WebSockets with TestClient imposes thread safety? which did not seem directly applicable and has been noted to no longer be a problem with recent releases. I am filing this under "Potential Issue" because I am unable to identify why one machine in particular is so susceptible to the problem and I have two different solutions available. I suspect this can mostly serve as documentation in case anyone else tries searching for the problem like I did. |
Beta Was this translation helpful? Give feedback.
Replies: 1 comment 1 reply
-
Hi! We've noticed the exact same issue here: slowdowns on websocket tests that are particularly noticeable on one specific machine ( in our case I can verify everything here, though I too haven't been able to identify exactly why particular machines are more susceptible than others. Are the machines you've experienced less/no slowdown on all Windows machines by chance? That has certainly been our experience. To my knowledge, I've opened a PR (#2597) with my fix, but unfortunately only found your discussion after the fact. Thank you for the write up! |
Beta Was this translation helpful? Give feedback.
Hi! We've noticed the exact same issue here: slowdowns on websocket tests that are particularly noticeable on one specific machine ( in our case
Linux 5.15.0-94-generic #104-Ubuntu
). All of our websocket tests are performed with two websockets, so I did not think quantity of websocket connections would matter, but it clearly does.I can verify everything here, though I too haven't been able to identify exactly why particular machines are more susceptible than others. Are the machines you've experienced less/no slowdown on all Windows machines by chance? That has certainly been our experience.
To my knowledge,
asyncio.sleep(0)
is often interpreted as 'yield to another task', but there is n…