Skip to content

Commit df4ca2c

Browse files
committed
ongoing
1 parent e56f6de commit df4ca2c

File tree

2 files changed

+79
-4
lines changed

2 files changed

+79
-4
lines changed

services/web/server/src/simcore_service_webserver/projects/_projects_service.py

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1593,6 +1593,31 @@ async def _open_project() -> bool:
15931593
len(sessions_with_project)
15941594
>= max_number_of_user_sessions_per_project
15951595
):
1596+
# we need to check has an inactive session in which case we can steal the project
1597+
this_user_other_sessions = [
1598+
s
1599+
for s in sessions_with_project
1600+
if s.user_id == user_id and s != user_session
1601+
]
1602+
for session in this_user_other_sessions:
1603+
with managed_resource(
1604+
session.user_id, session.client_session_id, app
1605+
) as other_user_session:
1606+
if await other_user_session.get_socket_id() is None:
1607+
# this user has an inactive session, we can steal the project
1608+
_logger.debug(
1609+
"stealing project %s from user %s/%s",
1610+
project_uuid,
1611+
session.user_id,
1612+
session.client_session_id,
1613+
)
1614+
await user_session.add(
1615+
PROJECT_ID_KEY, f"{project_uuid}"
1616+
)
1617+
await other_user_session.remove(PROJECT_ID_KEY)
1618+
1619+
return True
1620+
15961621
raise ProjectTooManyUserSessionsError(
15971622
max_num_sessions=max_number_of_user_sessions_per_project,
15981623
user_id=user_id,

services/web/server/tests/unit/with_dbs/02/test_projects_states_handlers.py

Lines changed: 54 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
import asyncio
99
import contextlib
1010
import logging
11+
import secrets
1112
from collections.abc import AsyncIterator, Awaitable, Callable, Iterator
1213
from copy import deepcopy
1314
from datetime import UTC, datetime, timedelta
@@ -146,7 +147,7 @@ class _SocketHandlers(TypedDict):
146147
@pytest.fixture
147148
async def create_socketio_connection_with_handlers(
148149
create_socketio_connection: Callable[
149-
[TestClient | None], Awaitable[tuple[socketio.AsyncClient, str]]
150+
[str | None, TestClient | None], Awaitable[tuple[socketio.AsyncClient, str]]
150151
],
151152
mocker: MockerFixture,
152153
) -> Callable[
@@ -1536,14 +1537,14 @@ async def test_open_shared_project_multiple_users(
15361537
)
15371538
other_users.append((user_i, client_i, client_i_tab_id, sio_i, sio_i_handlers))
15381539

1539-
# create an additional user, opening the project again shall raise
1540+
#
1541+
# TEST more user sessions cannot be opened: create an additional user, opening the project again shall raise
15401542
client_n = client_on_running_server_factory()
15411543

15421544
user_n = await exit_stack.enter_async_context(
15431545
LoggedUser(client_n, {"role": logged_user["role"]})
15441546
)
15451547
assert user_n
1546-
15471548
(
15481549
sio_n,
15491550
client_n_tab_id,
@@ -1558,7 +1559,56 @@ async def test_open_shared_project_multiple_users(
15581559
sio_n_handlers[SOCKET_IO_PROJECT_UPDATED_EVENT], shared_project, []
15591560
)
15601561

1561-
# close project from a random user shall trigger an event for all the other users
1562+
#
1563+
# TEST refreshing tab of some user shall work
1564+
#
1565+
refreshing_user_index = secrets.randbelow(len(other_users))
1566+
user_x, client_x, _, closed_sio_x, _ = other_users.pop(refreshing_user_index)
1567+
# close tab of user x
1568+
await closed_sio_x.disconnect()
1569+
await asyncio.sleep(5) # wait for the disconnect to be processed
1570+
(
1571+
new_sio_x,
1572+
new_client_x_tab_id,
1573+
new_sio_x_handlers,
1574+
) = await create_socketio_connection_with_handlers(client_x)
1575+
await _assert_project_state_updated(
1576+
new_sio_x_handlers[SOCKET_IO_PROJECT_UPDATED_EVENT],
1577+
shared_project,
1578+
[],
1579+
)
1580+
1581+
await _open_project(client_x, new_client_x_tab_id, shared_project, expected.ok)
1582+
1583+
await _assert_project_state_updated(
1584+
new_sio_x_handlers[SOCKET_IO_PROJECT_UPDATED_EVENT],
1585+
shared_project,
1586+
[opened_project_state],
1587+
)
1588+
1589+
# this triggers the new opening of the same users
1590+
for _user_j, client_j, _, _sio_j, sio_j_handlers in other_users:
1591+
# check already opened by other users which should also notify
1592+
await _assert_project_state_updated(
1593+
sio_j_handlers[SOCKET_IO_PROJECT_UPDATED_EVENT],
1594+
shared_project,
1595+
[opened_project_state],
1596+
)
1597+
await _state_project(
1598+
client_j, shared_project, expected.ok, opened_project_state
1599+
)
1600+
# put back the refreshed user in the list of other users
1601+
other_users.append(
1602+
(user_x, client_x, new_client_x_tab_id, new_sio_x, new_sio_x_handlers)
1603+
)
1604+
await _assert_project_state_updated(
1605+
sio_base_handlers[SOCKET_IO_PROJECT_UPDATED_EVENT],
1606+
shared_project,
1607+
[opened_project_state]
1608+
* 2, # NOTE: 2 calls since base user is part of the primary group and the all group
1609+
)
1610+
1611+
# close project from base user shall trigger an event for all the other users
15621612
await _close_project(
15631613
base_client, base_client_tab_id, shared_project, expected.no_content
15641614
)

0 commit comments

Comments
 (0)