diff --git a/antarest/__init__.py b/antarest/__init__.py index f8b3bd0418..ed494cf4c5 100644 --- a/antarest/__init__.py +++ b/antarest/__init__.py @@ -19,9 +19,9 @@ # Standard project metadata -__version__ = "2.18.0" +__version__ = "2.18.1" __author__ = "RTE, Antares Web Team" -__date__ = "2024-11-29" +__date__ = "2024-12-02" # noinspection SpellCheckingInspection __credits__ = "(c) Réseau de Transport de l’Électricité (RTE)" diff --git a/antarest/eventbus/business/redis_eventbus.py b/antarest/eventbus/business/redis_eventbus.py index f3642bf994..8bbf6cbc38 100644 --- a/antarest/eventbus/business/redis_eventbus.py +++ b/antarest/eventbus/business/redis_eventbus.py @@ -20,6 +20,7 @@ logger = logging.getLogger(__name__) REDIS_STORE_KEY = "events" +MAX_EVENTS_LIST_SIZE = 1000 class RedisEventBus(IEventBusBackend): @@ -41,14 +42,23 @@ def pull_queue(self, queue: str) -> Optional[Event]: return None def get_events(self) -> List[Event]: + messages = [] try: - event = self.pubsub.get_message(ignore_subscribe_messages=True) - if event is not None: - return [Event.parse_raw(event["data"])] + while msg := self.pubsub.get_message(ignore_subscribe_messages=True): + messages.append(msg) + if len(messages) >= MAX_EVENTS_LIST_SIZE: + break except Exception: - logger.error("Failed to retrieve or parse event !", exc_info=True) + logger.error("Failed to retrieve events !", exc_info=True) - return [] + events = [] + for msg in messages: + try: + events.append(Event.model_validate_json(msg["data"])) + except Exception: + logger.error(f"Failed to parse event ! {msg}", exc_info=True) + + return events def clear_events(self) -> None: # Nothing to do diff --git a/antarest/eventbus/service.py b/antarest/eventbus/service.py index 201efb3be6..eadf75e8c8 100644 --- a/antarest/eventbus/service.py +++ b/antarest/eventbus/service.py @@ -24,6 +24,9 @@ logger = logging.getLogger(__name__) +EVENT_LOOP_REST_TIME = 0.2 + + class EventBusService(IEventBus): def __init__(self, backend: IEventBusBackend, autostart: bool = True) -> None: self.backend = backend @@ -76,18 +79,22 @@ def remove_listener(self, listener_id: str) -> None: async def _run_loop(self) -> None: while True: - time.sleep(0.2) try: - await self._on_events() + processed_events_count = await self._on_events() + # Give the loop some rest if it has nothing to do + if processed_events_count == 0: + await asyncio.sleep(EVENT_LOOP_REST_TIME) except Exception as e: logger.error("Unexpected error when processing events", exc_info=e) - async def _on_events(self) -> None: + async def _on_events(self) -> int: + processed_events_count = 0 with self.lock: for queue in self.consumers: if len(self.consumers[queue]) > 0: event = self.backend.pull_queue(queue) while event is not None: + processed_events_count += 1 try: await list(self.consumers[queue].values())[ random.randint(0, len(self.consumers[queue]) - 1) @@ -99,7 +106,9 @@ async def _on_events(self) -> None: ) event = self.backend.pull_queue(queue) - for e in self.backend.get_events(): + events = self.backend.get_events() + processed_events_count += len(events) + for e in events: if e.type in self.listeners: responses = await asyncio.gather( *[ @@ -115,6 +124,7 @@ async def _on_events(self) -> None: exc_info=res, ) self.backend.clear_events() + return processed_events_count def _async_loop(self, new_loop: bool = True) -> None: loop = asyncio.new_event_loop() if new_loop else asyncio.get_event_loop() diff --git a/docs/CHANGELOG.md b/docs/CHANGELOG.md index 00c33a8f5a..2a35203f73 100644 --- a/docs/CHANGELOG.md +++ b/docs/CHANGELOG.md @@ -1,8 +1,22 @@ Antares Web Changelog ===================== +v2.18.1 (2024-12-02) +-------------------- + +## What's Changed + +### Bug Fixes + +* **ui-tablemode**: style missing [`2257`](https://github.com/AntaresSimulatorTeam/AntaREST/pull/2257) +* **ui-studies**: multiple API calls on study list view [`2258`](https://github.com/AntaresSimulatorTeam/AntaREST/pull/2258) +* **events**: avoid slow processing of events [`2259`](https://github.com/AntaresSimulatorTeam/AntaREST/pull/2259) + +**Full Changelog**: https://github.com/AntaresSimulatorTeam/AntaREST/compare/v2.18.0...v2.18.1 + + v2.18.0 (2024-11-29) -------------------- +-------------------- ## What's Changed diff --git a/pyproject.toml b/pyproject.toml index 5e77bb439e..00613c0cc4 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -3,7 +3,7 @@ requires = ["setuptools"] [project] name = "AntaREST" -version = "2.18.0" +version = "2.18.1" authors = [{name="RTE, Antares Web Team", email="andrea.sgattoni@rte-france.com" }] description="Antares Server" readme = {file = "README.md", content-type = "text/markdown"} diff --git a/resources/application.yaml b/resources/application.yaml index c962d09015..21ec482177 100644 --- a/resources/application.yaml +++ b/resources/application.yaml @@ -52,3 +52,9 @@ logging: # True to get sqlalchemy logs debug: False + +# Uncomment these lines to use redis as a backend for the eventbus +# It is required to use redis when using this application on multiple workers in a preforked model like gunicorn for instance +#redis: +# host: localhost +# port: 6379 diff --git a/sonar-project.properties b/sonar-project.properties index c0897c1d14..92b73ee890 100644 --- a/sonar-project.properties +++ b/sonar-project.properties @@ -6,5 +6,5 @@ sonar.exclusions=antarest/gui.py,antarest/main.py sonar.python.coverage.reportPaths=coverage.xml sonar.python.version=3.11 sonar.javascript.lcov.reportPaths=webapp/coverage/lcov.info -sonar.projectVersion=2.18.0 +sonar.projectVersion=2.18.1 sonar.coverage.exclusions=antarest/gui.py,antarest/main.py,antarest/singleton_services.py,antarest/worker/archive_worker_service.py,webapp/**/*,,antarest/fastapi_jwt_auth/** \ No newline at end of file diff --git a/tests/eventbus/test_redis_event_bus.py b/tests/eventbus/test_redis_event_bus.py index 37f69e1fad..fa8722f742 100644 --- a/tests/eventbus/test_redis_event_bus.py +++ b/tests/eventbus/test_redis_event_bus.py @@ -29,8 +29,9 @@ def test_lifecycle(): payload="foo", permissions=PermissionInfo(public_mode=PublicMode.READ), ) + serialized = event.model_dump_json() - pubsub_mock.get_message.return_value = {"data": serialized} + pubsub_mock.get_message = Mock(side_effect=[{"data": serialized}, {"data": serialized}, None]) eventbus.push_event(event) redis_client.publish.assert_called_once_with("events", serialized) - assert eventbus.get_events() == [event] + assert eventbus.get_events() == [event, event] diff --git a/webapp/package-lock.json b/webapp/package-lock.json index a8fd1acc16..3ed8e546ee 100644 --- a/webapp/package-lock.json +++ b/webapp/package-lock.json @@ -1,12 +1,12 @@ { "name": "antares-web", - "version": "2.18.0", + "version": "2.18.1", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "antares-web", - "version": "2.18.0", + "version": "2.18.1", "dependencies": { "@emotion/react": "11.13.3", "@emotion/styled": "11.13.0", diff --git a/webapp/package.json b/webapp/package.json index a5b9d9dca6..1abf973a65 100644 --- a/webapp/package.json +++ b/webapp/package.json @@ -1,6 +1,6 @@ { "name": "antares-web", - "version": "2.18.0", + "version": "2.18.1", "private": true, "type": "module", "scripts": { diff --git a/webapp/src/components/App/Studies/StudyCard/index.tsx b/webapp/src/components/App/Studies/StudyCard/index.tsx index cb74b58819..89813e6271 100644 --- a/webapp/src/components/App/Studies/StudyCard/index.tsx +++ b/webapp/src/components/App/Studies/StudyCard/index.tsx @@ -405,30 +405,27 @@ const StudyCard = memo((props: Props) => { setOpenDialog={setOpenDialog} /> - - - {t("studies.question.delete")} - - - + {/* Keep conditional rendering for dialogs and not use only `open` property, because API calls are made on mount */} + {openDialog === "properties" && ( + + )} + {openDialog === "delete" && ( + + {t("studies.question.delete")} + + )} + {openDialog === "export" && ( + + )} + {openDialog === "move" && ( + + )} ); }, areEqual); diff --git a/webapp/src/components/common/Handsontable.tsx b/webapp/src/components/common/Handsontable.tsx index 0e6d6c3f8a..89f3ef7c0f 100644 --- a/webapp/src/components/common/Handsontable.tsx +++ b/webapp/src/components/common/Handsontable.tsx @@ -18,6 +18,7 @@ import { styled } from "@mui/material"; import { forwardRef } from "react"; import * as RA from "ramda-adjunct"; import { SECONDARY_MAIN_COLOR } from "../../theme"; +import "handsontable/dist/handsontable.min.css"; // Register Handsontable's modules registerAllModules();