diff --git a/app.py b/app.py index 93cc8b79..3bb9dcc9 100644 --- a/app.py +++ b/app.py @@ -426,32 +426,36 @@ async def lifespan(app: FastAPI): yield - # --- Stop Managers --- - eventManager.stop() - - # --- Stop Feature Containers (Plug&Play) --- + # --- Shutdown sequence (protected against CancelledError) --- try: - mainModules = loadFeatureMainModules() - for featureName, module in mainModules.items(): - if hasattr(module, "onStop"): - try: - await module.onStop(eventUser) - logger.info(f"Feature '{featureName}' stopped") - except Exception as e: - logger.error(f"Feature '{featureName}' failed to stop: {e}") - except Exception as e: - logger.warning(f"Could not shutdown feature containers: {e}") + # 1. Stop scheduler first (removes all pending cron/interval jobs) + eventManager.stop() - # --- Close all PostgreSQL connection pools --- - # Must run LAST: feature `onStop` hooks may still issue DB calls during - # shutdown. Once we tear down the pools, no more borrows are possible. - try: - from modules.connectors.connectorDbPostgre import closeAllPools - closeAllPools() - except Exception as e: - logger.warning(f"Closing DB connection pools failed: {e}") + # 2. Stop Feature Containers (Plug&Play) + try: + mainModules = loadFeatureMainModules() + for featureName, module in mainModules.items(): + if hasattr(module, "onStop"): + try: + await module.onStop(eventUser) + logger.info(f"Feature '{featureName}' stopped") + except Exception as e: + logger.error(f"Feature '{featureName}' failed to stop: {e}") + except Exception as e: + logger.warning(f"Could not shutdown feature containers: {e}") - logger.info("Application has been shut down") + # 3. Close all PostgreSQL connection pools (LAST -- features may still + # issue DB calls during their onStop hooks) + try: + from modules.connectors.connectorDbPostgre import closeAllPools + closeAllPools() + except Exception as e: + logger.warning(f"Closing DB connection pools failed: {e}") + + logger.info("Application has been shut down") + + except asyncio.CancelledError: + logger.info("Shutdown interrupted (CancelledError) -- resources released") # Custom function to generate readable operation IDs for Swagger UI diff --git a/modules/shared/eventManagement.py b/modules/shared/eventManagement.py index ebbf2131..1edd53be 100644 --- a/modules/shared/eventManagement.py +++ b/modules/shared/eventManagement.py @@ -55,6 +55,7 @@ class EventManagement: def stop(self) -> None: if self._scheduler and self._scheduler.running: try: + self._scheduler.remove_all_jobs() self._scheduler.shutdown(wait=False) logger.info("EventManagement scheduler stopped") except Exception as exc: