Skip to content

Commit ea50b06

Browse files
refactor project lock
1 parent 0a2b1ba commit ea50b06

File tree

3 files changed

+15
-56
lines changed

3 files changed

+15
-56
lines changed

packages/service-library/src/servicelib/project_lock.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
from typing import Final
77

88
import redis
9+
import redis.exceptions
910
from models_library.projects import ProjectID
1011
from models_library.projects_access import Owner
1112
from models_library.projects_state import ProjectLocked, ProjectStatus

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

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,9 @@
22

33
from typing import Any
44

5-
import redis.exceptions
65
from models_library.projects import ProjectID
76
from models_library.users import UserID
7+
from servicelib.project_lock import ProjectLockError
88

99
from ..errors import WebServerBaseError
1010

@@ -104,9 +104,6 @@ def __init__(self, *, project_uuid: str | None, **ctx):
104104
self.project_uuid = project_uuid
105105

106106

107-
ProjectLockError = redis.exceptions.LockError
108-
109-
110107
class ProjectStartsTooManyDynamicNodesError(BaseProjectError):
111108
msg_template = "The maximal amount of concurrently running dynamic services was reached. Please manually stop a service and retry."
112109

@@ -224,3 +221,7 @@ class InvalidInputValue(WebServerBaseError):
224221

225222
class ProjectGroupNotFoundError(BaseProjectError):
226223
msg_template = "Project group not found. {reason}"
224+
225+
226+
assert ProjectLockError # nosec
227+
__all__: tuple[str, ...] = ("ProjectLockError",)

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

Lines changed: 9 additions & 52 deletions
Original file line numberDiff line numberDiff line change
@@ -1,34 +1,21 @@
1-
import datetime
21
import logging
3-
from asyncio.log import logger
42
from collections.abc import AsyncIterator
53
from contextlib import asynccontextmanager
6-
from typing import Final
74

8-
import redis
95
from aiohttp import web
106
from models_library.projects import ProjectID
117
from models_library.projects_access import Owner
128
from models_library.projects_state import ProjectLocked, ProjectStatus
13-
from redis.asyncio.lock import Lock
14-
from servicelib.background_task import periodic_task
15-
from servicelib.logging_utils import log_context
9+
10+
# from .exceptions import ProjectLockError
11+
from servicelib.project_lock import PROJECT_LOCK_TIMEOUT, PROJECT_REDIS_LOCK_KEY
12+
from servicelib.project_lock import lock_project as common_lock_project
1613

1714
from ..redis import get_redis_lock_manager_client
1815
from ..users.api import FullNameDict
19-
from .exceptions import ProjectLockError
2016

2117
_logger = logging.getLogger(__name__)
2218

23-
PROJECT_REDIS_LOCK_KEY: str = "project_lock:{}"
24-
PROJECT_LOCK_TIMEOUT: Final[datetime.timedelta] = datetime.timedelta(seconds=10)
25-
ProjectLock = Lock
26-
27-
28-
async def _auto_extend_project_lock(project_lock: Lock) -> None:
29-
# NOTE: the background task already catches anything that might raise here
30-
await project_lock.reacquire()
31-
3219

3320
@asynccontextmanager
3421
async def lock_project(
@@ -48,42 +35,12 @@ async def lock_project(
4835
PROJECT_REDIS_LOCK_KEY.format(project_uuid),
4936
timeout=PROJECT_LOCK_TIMEOUT.total_seconds(),
5037
)
51-
try:
52-
if not await redis_lock.acquire(
53-
blocking=False,
54-
token=ProjectLocked(
55-
value=True,
56-
owner=Owner(user_id=user_id, **user_fullname), # type: ignore[arg-type]
57-
status=status,
58-
).json(),
59-
):
60-
msg = f"Lock for project {project_uuid!r} user {user_id!r} could not be acquired"
61-
raise ProjectLockError(msg)
62-
63-
with log_context(
64-
_logger,
65-
logging.DEBUG,
66-
msg=f"with lock for {user_id=}:{user_fullname=}:{project_uuid=}:{status=}",
67-
):
68-
async with periodic_task(
69-
_auto_extend_project_lock,
70-
interval=0.6 * PROJECT_LOCK_TIMEOUT,
71-
task_name=f"{PROJECT_REDIS_LOCK_KEY.format(project_uuid)}_lock_auto_extend",
72-
project_lock=redis_lock,
73-
):
74-
yield
38+
owner = Owner(user_id=user_id, **user_fullname) # type: ignore[arg-type]
7539

76-
finally:
77-
# let's ensure we release that stuff
78-
try:
79-
if await redis_lock.owned():
80-
await redis_lock.release()
81-
except (redis.exceptions.LockError, redis.exceptions.LockNotOwnedError) as exc:
82-
logger.warning(
83-
"releasing %s unexpectedly raised an exception: %s",
84-
f"{redis_lock=!r}",
85-
f"{exc}",
86-
)
40+
async with common_lock_project(
41+
redis_lock, project_uuid=project_uuid, status=status, owner=owner
42+
):
43+
yield
8744

8845

8946
async def is_project_locked(

0 commit comments

Comments
 (0)