Skip to content

Commit 631e7f3

Browse files
committed
Merge branch 'master' into is7961/upgrade-gc-periodic-task
2 parents 9d02f48 + 98caa3a commit 631e7f3

File tree

15 files changed

+389
-156
lines changed

15 files changed

+389
-156
lines changed

packages/celery-library/src/celery_library/signals.py

Lines changed: 4 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44

55
from celery import Celery # type: ignore[import-untyped]
66
from celery.worker.worker import WorkController # type: ignore[import-untyped]
7-
from servicelib.celery.app_server import STARTUP_TIMEOUT, BaseAppServer
7+
from servicelib.celery.app_server import BaseAppServer
88
from servicelib.logging_utils import log_context
99
from settings_library.celery import CelerySettings
1010

@@ -26,8 +26,6 @@ def _init(startup_complete_event: threading.Event) -> None:
2626
loop = asyncio.new_event_loop()
2727
asyncio.set_event_loop(loop)
2828

29-
shutdown_event = asyncio.Event()
30-
3129
async def _setup_task_manager():
3230
assert sender.app # nosec
3331
assert isinstance(sender.app, Celery) # nosec
@@ -42,9 +40,7 @@ async def _setup_task_manager():
4240
app_server.event_loop = loop
4341

4442
loop.run_until_complete(_setup_task_manager())
45-
loop.run_until_complete(
46-
app_server.startup(startup_complete_event, shutdown_event)
47-
)
43+
loop.run_until_complete(app_server.lifespan(startup_complete_event))
4844

4945
thread = threading.Thread(
5046
group=None,
@@ -55,12 +51,12 @@ async def _setup_task_manager():
5551
)
5652
thread.start()
5753

58-
startup_complete_event.wait(STARTUP_TIMEOUT * 1.1)
54+
startup_complete_event.wait()
5955

6056

6157
def on_worker_shutdown(sender, **_kwargs) -> None:
6258
with log_context(_logger, logging.INFO, "Worker shutdown"):
6359
assert isinstance(sender.app, Celery)
6460
app_server = get_app_server(sender.app)
6561

66-
app_server.event_loop.run_until_complete(app_server.shutdown())
62+
app_server.shutdown_event.set()

packages/celery-library/tests/conftest.py

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
# pylint: disable=unused-argument
33

44
import datetime
5+
import threading
56
from collections.abc import AsyncIterator, Callable
67
from functools import partial
78
from typing import Any
@@ -30,10 +31,7 @@
3031

3132

3233
class FakeAppServer(BaseAppServer):
33-
async def on_startup(self) -> None:
34-
pass
35-
36-
async def on_shutdown(self) -> None:
34+
async def lifespan(self, startup_completed_event: threading.Event) -> None:
3735
pass
3836

3937

packages/pytest-simcore/src/pytest_simcore/simcore_services.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@
4040
"whoami",
4141
"sto-worker",
4242
"sto-worker-cpu-bound",
43+
"traefik-configuration-placeholder",
4344
}
4445
# TODO: unify healthcheck policies see https://github.com/ITISFoundation/osparc-simcore/pull/2281
4546
DEFAULT_SERVICE_HEALTHCHECK_ENTRYPOINT: Final[str] = "/v0/"
Lines changed: 9 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -1,21 +1,18 @@
11
import asyncio
2-
import datetime
32
import threading
43
from abc import ABC, abstractmethod
54
from asyncio import AbstractEventLoop
6-
from typing import Final, Generic, TypeVar
5+
from typing import Generic, TypeVar
76

87
from servicelib.celery.task_manager import TaskManager
98

10-
STARTUP_TIMEOUT: Final[float] = datetime.timedelta(minutes=1).total_seconds()
11-
129
T = TypeVar("T")
1310

1411

1512
class BaseAppServer(ABC, Generic[T]):
1613
def __init__(self, app: T) -> None:
1714
self._app: T = app
18-
self._shutdown_event: asyncio.Event | None = None
15+
self._shutdown_event: asyncio.Event = asyncio.Event()
1916

2017
@property
2118
def app(self) -> T:
@@ -29,6 +26,10 @@ def event_loop(self) -> AbstractEventLoop:
2926
def event_loop(self, loop: AbstractEventLoop) -> None:
3027
self._event_loop = loop
3128

29+
@property
30+
def shutdown_event(self) -> asyncio.Event:
31+
return self._shutdown_event
32+
3233
@property
3334
def task_manager(self) -> TaskManager:
3435
return self._task_manager
@@ -38,23 +39,8 @@ def task_manager(self, manager: TaskManager) -> None:
3839
self._task_manager = manager
3940

4041
@abstractmethod
41-
async def on_startup(self) -> None:
42-
raise NotImplementedError
43-
44-
async def startup(
45-
self, completed_event: threading.Event, shutdown_event: asyncio.Event
42+
async def lifespan(
43+
self,
44+
startup_completed_event: threading.Event,
4645
) -> None:
47-
self._shutdown_event = shutdown_event
48-
completed_event.set()
49-
await self.on_startup()
50-
await self._shutdown_event.wait()
51-
52-
@abstractmethod
53-
async def on_shutdown(self) -> None:
5446
raise NotImplementedError
55-
56-
async def shutdown(self) -> None:
57-
if self._shutdown_event is not None:
58-
self._shutdown_event.set()
59-
60-
await self.on_shutdown()
Lines changed: 13 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,29 +1,29 @@
1-
from datetime import timedelta
1+
import datetime
2+
import logging
3+
import threading
24
from typing import Final
35

46
from asgi_lifespan import LifespanManager
57
from fastapi import FastAPI
68

79
from ...celery.app_server import BaseAppServer
810

9-
_SHUTDOWN_TIMEOUT: Final[float] = timedelta(seconds=10).total_seconds()
10-
_STARTUP_TIMEOUT: Final[float] = timedelta(minutes=1).total_seconds()
11+
_SHUTDOWN_TIMEOUT: Final[float] = datetime.timedelta(seconds=10).total_seconds()
12+
13+
_logger = logging.getLogger(__name__)
1114

1215

1316
class FastAPIAppServer(BaseAppServer[FastAPI]):
1417
def __init__(self, app: FastAPI):
1518
super().__init__(app)
1619
self._lifespan_manager: LifespanManager | None = None
1720

18-
async def on_startup(self) -> None:
19-
self._lifespan_manager = LifespanManager(
21+
async def lifespan(self, startup_completed_event: threading.Event) -> None:
22+
async with LifespanManager(
2023
self.app,
21-
startup_timeout=_STARTUP_TIMEOUT,
24+
startup_timeout=None, # waits for full app initialization (DB migrations, etc.)
2225
shutdown_timeout=_SHUTDOWN_TIMEOUT,
23-
)
24-
await self._lifespan_manager.__aenter__()
25-
26-
async def on_shutdown(self) -> None:
27-
if self._lifespan_manager is None:
28-
return
29-
await self._lifespan_manager.__aexit__(None, None, None)
26+
):
27+
_logger.info("fastapi app initialized")
28+
startup_completed_event.set()
29+
await self.shutdown_event.wait() # NOTE: wait here until shutdown is requested

services/docker-compose.yml

Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,7 @@ services:
5757
- traefik.http.services.${SWARM_STACK_NAME}_api-server.loadbalancer.healthcheck.path=/
5858
- traefik.http.services.${SWARM_STACK_NAME}_api-server.loadbalancer.healthcheck.interval=2000ms
5959
- traefik.http.services.${SWARM_STACK_NAME}_api-server.loadbalancer.healthcheck.timeout=1000ms
60+
# NOTE: keep in sync with fallback router (rule and entrypoint)
6061
- traefik.http.routers.${SWARM_STACK_NAME}_api-server.rule=(Path(`/`) || Path(`/v0`) || PathPrefix(`/v0/`) || Path(`/api/v0/openapi.json`))
6162
- traefik.http.routers.${SWARM_STACK_NAME}_api-server.entrypoints=simcore_api
6263
- traefik.http.routers.${SWARM_STACK_NAME}_api-server.priority=3
@@ -628,6 +629,7 @@ services:
628629
- traefik.http.services.${SWARM_STACK_NAME}_static_webserver.loadbalancer.healthcheck.interval=2000ms
629630
- traefik.http.services.${SWARM_STACK_NAME}_static_webserver.loadbalancer.healthcheck.timeout=1000ms
630631
- traefik.http.middlewares.${SWARM_STACK_NAME}_static_webserver_retry.retry.attempts=2
632+
# NOTE: keep in sync with fallback router (rule and entrypoint)
631633
- traefik.http.routers.${SWARM_STACK_NAME}_static_webserver.rule=(Path(`/osparc`) || Path(`/s4l`) || Path(`/s4llite`) || Path(`/s4lacad`) || Path(`/s4lengine`) || Path(`/s4ldesktop`) || Path(`/s4ldesktopacad`) || Path(`/tis`) || Path(`/tiplite`) || Path(`/transpiled`) || Path(`/resource`) || PathPrefix(`/osparc/`) || PathPrefix(`/s4l/`) || PathPrefix(`/s4llite/`) || PathPrefix(`/s4lacad/`) || PathPrefix(`/s4lengine/`) || PathPrefix(`/s4ldesktop/`) || PathPrefix(`/s4ldesktopacad/`) || PathPrefix(`/tis/`) || PathPrefix(`/tiplite/`) || PathPrefix(`/transpiled/`) || PathPrefix(`/resource/`))
632634
- traefik.http.routers.${SWARM_STACK_NAME}_static_webserver.service=${SWARM_STACK_NAME}_static_webserver
633635
- traefik.http.routers.${SWARM_STACK_NAME}_static_webserver.entrypoints=http
@@ -871,6 +873,7 @@ services:
871873
# NOTE: stickyness must remain only for specific endpoints, see https://github.com/ITISFoundation/osparc-simcore/pull/4180
872874
- traefik.http.middlewares.${SWARM_STACK_NAME}_webserver_retry.retry.attempts=2
873875
- traefik.http.routers.${SWARM_STACK_NAME}_webserver.service=${SWARM_STACK_NAME}_webserver
876+
# NOTE: keep in sync with fallback router (rule and entrypoint)
874877
- traefik.http.routers.${SWARM_STACK_NAME}_webserver.rule=(Path(`/`) || Path(`/v0`) || Path(`/socket.io/`) || Path(`/static-frontend-data.json`) || PathRegexp(`^/study/(?P<study_uuid>\b[0-9a-f]{8}\b-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-\b[0-9a-f]{12}\b)`) || Path(`/view`) || Path(`/#/view`) || Path(`/#/error`) || PathPrefix(`/v0/`))
875878
- traefik.http.routers.${SWARM_STACK_NAME}_webserver.entrypoints=http
876879
- traefik.http.routers.${SWARM_STACK_NAME}_webserver.priority=6
@@ -1456,6 +1459,62 @@ services:
14561459
- default
14571460
- interactive_services_subnet # for legacy dynamic services
14581461

1462+
# use to define fallback routes for simcore services
1463+
# if docker healthcheck fails, container's traefik configuration is removed
1464+
# leading to 404 https://github.com/traefik/traefik/issues/7842
1465+
#
1466+
# use fallback routes to return proper 503 (instead of 404)
1467+
# this service must be running at all times
1468+
traefik-configuration-placeholder:
1469+
image: busybox:1.35.0
1470+
command: sleep infinity
1471+
networks:
1472+
- default
1473+
deploy:
1474+
labels:
1475+
# route to internal traefik
1476+
- traefik.enable=true
1477+
- io.simcore.zone=${TRAEFIK_SIMCORE_ZONE}
1478+
1479+
### Fallback for api-server
1480+
- traefik.http.routers.${SWARM_STACK_NAME}_api-server_fallback.rule=(Path(`/`) || Path(`/v0`) || PathPrefix(`/v0/`) || Path(`/api/v0/openapi.json`))
1481+
- traefik.http.routers.${SWARM_STACK_NAME}_api-server_fallback.service=${SWARM_STACK_NAME}_api-server_fallback
1482+
- traefik.http.routers.${SWARM_STACK_NAME}_api-server_fallback.entrypoints=simcore_api
1483+
- traefik.http.routers.${SWARM_STACK_NAME}_api-server_fallback.priority=1
1484+
# always fail and return 503 via unhealthy loadbalancer healthcheck
1485+
- traefik.http.services.${SWARM_STACK_NAME}_api-server_fallback.loadbalancer.server.port=0 # port is required (otherwise traefik service is not created)
1486+
- traefik.http.services.${SWARM_STACK_NAME}_api-server_fallback.loadbalancer.healthcheck.path=/some/invalid/path/to/generate/a/503
1487+
- traefik.http.services.${SWARM_STACK_NAME}_api-server_fallback.loadbalancer.healthcheck.interval=10s
1488+
- traefik.http.services.${SWARM_STACK_NAME}_api-server_fallback.loadbalancer.healthcheck.timeout=1ms
1489+
1490+
### Fallback for webserver
1491+
- traefik.http.routers.${SWARM_STACK_NAME}_webserver_fallback.service=${SWARM_STACK_NAME}_webserver_fallback
1492+
- traefik.http.routers.${SWARM_STACK_NAME}_webserver_fallback.rule=(Path(`/`) || Path(`/v0`) || Path(`/socket.io/`) || Path(`/static-frontend-data.json`) || PathRegexp(`^/study/(?P<study_uuid>\b[0-9a-f]{8}\b-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-\b[0-9a-f]{12}\b)`) || Path(`/view`) || Path(`/#/view`) || Path(`/#/error`) || PathPrefix(`/v0/`))
1493+
- traefik.http.routers.${SWARM_STACK_NAME}_webserver_fallback.entrypoints=http
1494+
- traefik.http.routers.${SWARM_STACK_NAME}_webserver_fallback.priority=1
1495+
# always fail and return 503 via unhealthy loadbalancer healthcheck
1496+
- traefik.http.services.${SWARM_STACK_NAME}_webserver_fallback.loadbalancer.server.port=0
1497+
- traefik.http.services.${SWARM_STACK_NAME}_webserver_fallback.loadbalancer.healthcheck.path=/v0/
1498+
- traefik.http.services.${SWARM_STACK_NAME}_webserver_fallback.loadbalancer.healthcheck.interval=10s
1499+
- traefik.http.services.${SWARM_STACK_NAME}_webserver_fallback.loadbalancer.healthcheck.timeout=1ms
1500+
1501+
### Fallback for static-webserver
1502+
- traefik.http.routers.${SWARM_STACK_NAME}_static_webserver_fallback.rule=(Path(`/osparc`) || Path(`/s4l`) || Path(`/s4llite`) || Path(`/s4lacad`) || Path(`/s4lengine`) || Path(`/s4ldesktop`) || Path(`/s4ldesktopacad`) || Path(`/tis`) || Path(`/tiplite`) || Path(`/transpiled`) || Path(`/resource`) || PathPrefix(`/osparc/`) || PathPrefix(`/s4l/`) || PathPrefix(`/s4llite/`) || PathPrefix(`/s4lacad/`) || PathPrefix(`/s4lengine/`) || PathPrefix(`/s4ldesktop/`) || PathPrefix(`/s4ldesktopacad/`) || PathPrefix(`/tis/`) || PathPrefix(`/tiplite/`) || PathPrefix(`/transpiled/`) || PathPrefix(`/resource/`))
1503+
- traefik.http.routers.${SWARM_STACK_NAME}_static_webserver_fallback.service=${SWARM_STACK_NAME}_static_webserver_fallback
1504+
- traefik.http.routers.${SWARM_STACK_NAME}_static_webserver_fallback.entrypoints=http
1505+
- traefik.http.routers.${SWARM_STACK_NAME}_static_webserver_fallback.priority=1
1506+
# always fail and return 503 via unhealthy loadbalancer healthcheck
1507+
- traefik.http.services.${SWARM_STACK_NAME}_static_webserver_fallback.loadbalancer.server.port=0
1508+
- traefik.http.services.${SWARM_STACK_NAME}_static_webserver_fallback.loadbalancer.healthcheck.path=/some/invalid/path/to/generate/a/503
1509+
- traefik.http.services.${SWARM_STACK_NAME}_static_webserver_fallback.loadbalancer.healthcheck.interval=10s
1510+
- traefik.http.services.${SWARM_STACK_NAME}_static_webserver_fallback.loadbalancer.healthcheck.timeout=1ms
1511+
healthcheck:
1512+
test: command -v sleep
1513+
interval: 10s
1514+
timeout: 1s
1515+
start_period: 1s
1516+
retries: 3
1517+
14591518
volumes:
14601519
postgres_data:
14611520
name: ${SWARM_STACK_NAME}_postgres_data

services/static-webserver/client/source/class/osparc/conversation/AddMessage.js

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -36,8 +36,8 @@ qx.Class.define("osparc.conversation.AddMessage", {
3636
},
3737

3838
events: {
39-
"commentAdded": "qx.event.type.Data",
40-
"messageEdited": "qx.event.type.Data",
39+
"messageAdded": "qx.event.type.Data",
40+
"messageUpdated": "qx.event.type.Data",
4141
},
4242

4343
members: {
@@ -227,7 +227,7 @@ qx.Class.define("osparc.conversation.AddMessage", {
227227
if (content) {
228228
osparc.study.Conversations.addMessage(this.__studyData["uuid"], this.__conversationId, content)
229229
.then(data => {
230-
this.fireDataEvent("commentAdded", data);
230+
this.fireDataEvent("messageAdded", data);
231231
commentField.getChildControl("text-area").setValue("");
232232
});
233233
}
@@ -239,7 +239,7 @@ qx.Class.define("osparc.conversation.AddMessage", {
239239
if (content) {
240240
osparc.study.Conversations.editMessage(this.__studyData["uuid"], this.__conversationId, this.__message["messageId"], content)
241241
.then(data => {
242-
this.fireDataEvent("messageEdited", data);
242+
this.fireDataEvent("messageUpdated", data);
243243
commentField.getChildControl("text-area").setValue("");
244244
});
245245
}
@@ -249,7 +249,7 @@ qx.Class.define("osparc.conversation.AddMessage", {
249249
if (userGid) {
250250
osparc.study.Conversations.notifyUser(this.__studyData["uuid"], this.__conversationId, userGid)
251251
.then(data => {
252-
this.fireDataEvent("commentAdded", data);
252+
this.fireDataEvent("messageAdded", data);
253253
const potentialCollaborators = osparc.store.Groups.getInstance().getPotentialCollaborators();
254254
if (userGid in potentialCollaborators) {
255255
if ("getUserId" in potentialCollaborators[userGid]) {

0 commit comments

Comments
 (0)