diff --git a/services/web/server/src/simcore_service_webserver/socketio/server.py b/services/web/server/src/simcore_service_webserver/socketio/server.py index 2efe35b96e3c..6ec40a6e365a 100644 --- a/services/web/server/src/simcore_service_webserver/socketio/server.py +++ b/services/web/server/src/simcore_service_webserver/socketio/server.py @@ -1,4 +1,6 @@ +import asyncio import logging +from typing import AsyncIterator from aiohttp import web from socketio import AsyncServer @@ -13,6 +15,27 @@ def get_socket_server(app: web.Application) -> AsyncServer: return app[APP_CLIENT_SOCKET_SERVER_KEY] +async def _socketio_server_cleanup_ctx(_app: web.Application) -> AsyncIterator[None]: + yield + # NOTE: this is ugly. It seems though that python-enginio does not + # cleanup its background tasks properly. + # https://github.com/miguelgrinberg/python-socketio/discussions/1092 + current_tasks = asyncio.tasks.all_tasks() + cancelled_tasks = [] + for task in current_tasks: + coro = task.get_coro() + if any( + coro_name in coro.__qualname__ # type: ignore + for coro_name in [ + "AsyncServer._service_task", + "AsyncSocket.schedule_ping", + ] + ): + task.cancel() + cancelled_tasks.append(task) + await asyncio.gather(*cancelled_tasks, return_exceptions=True) + + def setup_socketio_server(app: web.Application): if app.get(APP_CLIENT_SOCKET_SERVER_KEY) is None: # SEE https://github.com/miguelgrinberg/python-socketio/blob/v4.6.1/docs/server.rst#aiohttp @@ -26,5 +49,6 @@ def setup_socketio_server(app: web.Application): sio.attach(app) app[APP_CLIENT_SOCKET_SERVER_KEY] = sio + app.cleanup_ctx.append(_socketio_server_cleanup_ctx) return get_socket_server(app) diff --git a/services/web/server/tests/unit/with_dbs/03/garbage_collector/test_resource_manager.py b/services/web/server/tests/unit/with_dbs/03/garbage_collector/test_resource_manager.py index ff9382de9280..a376d0e54fb8 100644 --- a/services/web/server/tests/unit/with_dbs/03/garbage_collector/test_resource_manager.py +++ b/services/web/server/tests/unit/with_dbs/03/garbage_collector/test_resource_manager.py @@ -355,6 +355,27 @@ async def test_websocket_multiple_connections( ) +@pytest.mark.skip( + reason="this test is here to show warnings when closing " + "the socketio server and could be useful as a proof" + "see https://github.com/miguelgrinberg/python-socketio/discussions/1092" + "and simcore_service_webserver.socketio.server _socketio_server_cleanup_ctx" +) +@pytest.mark.parametrize( + "user_role", + [ + (UserRole.TESTER), + ], +) +async def test_asyncio_task_pending_on_close( + client: TestClient, + logged_user: dict[str, Any], + socketio_client_factory: Callable, +): + sio = await socketio_client_factory() + # this test generates warnings on its own + + @pytest.mark.parametrize( "user_role,expected", [