Skip to content

Commit cd54920

Browse files
authored
🐛Garbage collector: possible fix to "spurious" shutdowns (#5768)
1 parent 00155d6 commit cd54920

File tree

21 files changed

+546
-436
lines changed

21 files changed

+546
-436
lines changed

packages/postgres-database/requirements/_base.in

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,5 +5,6 @@
55
--constraint ./constraints.txt
66

77
alembic
8+
pydantic
89
sqlalchemy[postgresql_psycopg2binary,postgresql_asyncpg] # SEE extras in https://github.com/sqlalchemy/sqlalchemy/blob/main/setup.cfg#L43
910
yarl

packages/postgres-database/requirements/_base.txt

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,8 +13,11 @@ multidict==6.0.4
1313
# via yarl
1414
psycopg2-binary==2.9.9
1515
# via sqlalchemy
16+
pydantic==1.10.15
1617
sqlalchemy==1.4.50
1718
# via alembic
1819
typing-extensions==4.8.0
19-
# via alembic
20+
# via
21+
# alembic
22+
# pydantic
2023
yarl==1.9.2

packages/postgres-database/src/simcore_postgres_database/utils_projects_nodes.py

Lines changed: 21 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,16 @@
11
import datetime
22
import uuid
3-
from dataclasses import asdict, dataclass, field, fields
3+
from dataclasses import dataclass
44
from typing import Any
55

66
import sqlalchemy
77
from aiopg.sa.connection import SAConnection
8-
from simcore_postgres_database.models.projects_node_to_pricing_unit import (
9-
projects_node_to_pricing_unit,
10-
)
8+
from pydantic import BaseModel, Field
119
from sqlalchemy.dialects.postgresql import insert as pg_insert
1210

1311
from .errors import ForeignKeyViolation, UniqueViolation
12+
from .models.projects_node_to_pricing_unit import projects_node_to_pricing_unit
1413
from .models.projects_nodes import projects_nodes
15-
from .utils_models import FromRowMixin
1614

1715

1816
#
@@ -38,23 +36,27 @@ class ProjectNodesDuplicateNode(BaseProjectNodesError):
3836
...
3937

4038

41-
@dataclass(frozen=True, slots=True, kw_only=True)
42-
class ProjectNodeCreate:
39+
class ProjectNodeCreate(BaseModel):
4340
node_id: uuid.UUID
44-
required_resources: dict[str, Any] = field(default_factory=dict)
41+
required_resources: dict[str, Any] = Field(default_factory=dict)
4542

46-
@staticmethod
47-
def get_field_names(*, exclude: set[str]) -> set[str]:
48-
return {f.name for f in fields(ProjectNodeCreate) if f.name not in exclude}
43+
@classmethod
44+
def get_field_names(cls, *, exclude: set[str]) -> set[str]:
45+
return {name for name in cls.__fields__ if name not in exclude}
46+
47+
class Config:
48+
frozen = True
4949

5050

51-
@dataclass(frozen=True, slots=True, kw_only=True)
52-
class ProjectNode(ProjectNodeCreate, FromRowMixin):
51+
class ProjectNode(ProjectNodeCreate):
5352
created: datetime.datetime
5453
modified: datetime.datetime
5554

55+
class Config(ProjectNodeCreate.Config):
56+
orm_mode = True
57+
5658

57-
@dataclass(frozen=True, slots=True, kw_only=True)
59+
@dataclass(frozen=True, kw_only=True)
5860
class ProjectNodesRepo:
5961
project_uuid: uuid.UUID
6062

@@ -82,7 +84,7 @@ async def add(
8284
[
8385
{
8486
"project_uuid": f"{self.project_uuid}",
85-
**asdict(node),
87+
**node.dict(),
8688
}
8789
for node in nodes
8890
]
@@ -101,7 +103,7 @@ async def add(
101103
assert result # nosec
102104
rows = await result.fetchall()
103105
assert rows is not None # nosec
104-
return [ProjectNode.from_row(r) for r in rows]
106+
return [ProjectNode.from_orm(r) for r in rows]
105107
except ForeignKeyViolation as exc:
106108
# this happens when the project does not exist, as we first check the node exists
107109
msg = f"Project {self.project_uuid} not found"
@@ -127,7 +129,7 @@ async def list(self, connection: SAConnection) -> list[ProjectNode]: # noqa: A0
127129
assert result # nosec
128130
rows = await result.fetchall()
129131
assert rows is not None # nosec
130-
return [ProjectNode.from_row(row) for row in rows]
132+
return [ProjectNode.from_orm(row) for row in rows]
131133

132134
async def get(self, connection: SAConnection, *, node_id: uuid.UUID) -> ProjectNode:
133135
"""get a node in the current project
@@ -152,7 +154,7 @@ async def get(self, connection: SAConnection, *, node_id: uuid.UUID) -> ProjectN
152154
msg = f"Node with {node_id} not found"
153155
raise ProjectNodesNodeNotFound(msg)
154156
assert row # nosec
155-
return ProjectNode.from_row(row)
157+
return ProjectNode.from_orm(row)
156158

157159
async def update(
158160
self, connection: SAConnection, *, node_id: uuid.UUID, **values
@@ -181,7 +183,7 @@ async def update(
181183
msg = f"Node with {node_id} not found"
182184
raise ProjectNodesNodeNotFound(msg)
183185
assert row # nosec
184-
return ProjectNode.from_row(row)
186+
return ProjectNode.from_orm(row)
185187

186188
async def delete(self, connection: SAConnection, *, node_id: uuid.UUID) -> None:
187189
"""delete a node in the current project

services/web/server/src/simcore_service_webserver/director_v2/_core_dynamic_services.py

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7,9 +7,10 @@
77
import logging
88

99
from aiohttp import web
10+
from models_library.api_schemas_directorv2.dynamic_services import DynamicServiceGet
1011
from models_library.projects import ProjectID
1112
from models_library.services import ServicePortKey
12-
from pydantic import BaseModel
13+
from pydantic import BaseModel, parse_obj_as
1314
from pydantic.types import NonNegativeFloat, PositiveInt
1415
from servicelib.logging_utils import log_decorator
1516
from yarl import URL
@@ -30,7 +31,7 @@ async def list_dynamic_services(
3031
app: web.Application,
3132
user_id: PositiveInt | None = None,
3233
project_id: str | None = None,
33-
) -> list[DataType]:
34+
) -> list[DynamicServiceGet]:
3435
params = _Params(user_id=user_id, project_id=project_id)
3536
params_dict = params.dict(exclude_none=True)
3637
settings: DirectorV2Settings = get_plugin_settings(app)
@@ -48,7 +49,7 @@ async def list_dynamic_services(
4849
if services is None:
4950
services = []
5051
assert isinstance(services, list) # nosec
51-
return services
52+
return parse_obj_as(list[DynamicServiceGet], services)
5253

5354

5455
# NOTE: ANE https://github.com/ITISFoundation/osparc-simcore/issues/3191

services/web/server/src/simcore_service_webserver/dynamic_scheduler/api.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -118,11 +118,11 @@ async def stop_dynamic_services_in_project(
118118
services_to_stop = [
119119
stop_dynamic_service(
120120
app=app,
121-
node_id=service["service_uuid"],
121+
node_id=service.node_uuid,
122122
simcore_user_agent=simcore_user_agent,
123123
save_state=save_state,
124124
progress=progress_bar.sub_progress(
125-
1, description=service["service_uuid"]
125+
1, description=f"{service.node_uuid}"
126126
),
127127
)
128128
for service in running_dynamic_services

services/web/server/src/simcore_service_webserver/garbage_collector/_core.py

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -58,8 +58,4 @@ async def collect_garbage(app: web.Application):
5858
# the projects are closed or the user was disconencted.
5959
# This will close and remove all these services from
6060
# the cluster, thus freeing important resources.
61-
62-
# Temporary disabling GC to until the dynamic service
63-
# safe function is invoked by the GC. This will avoid
64-
# data loss for current users.
6561
await remove_orphaned_services(registry, app)

0 commit comments

Comments
 (0)