diff --git a/api_server.py b/api_server.py index dc5e312..0ee4560 100644 --- a/api_server.py +++ b/api_server.py @@ -25,6 +25,8 @@ asyncio.set_event_loop_policy(uvloop.EventLoopPolicy()) from starlette.middleware.base import BaseHTTPMiddleware from starlette.background import BackgroundTask import json_repair +import signal +import sys import magic from pyrogram import errors @@ -66,6 +68,7 @@ Config = get_settings() @asynccontextmanager async def lifespan(_: FastAPI): setup_logging(Config["log_level"]) + base_cache_dir = os.path.abspath("./data/cache") os.makedirs(base_cache_dir, exist_ok=True) # Create cache directory @@ -105,14 +108,29 @@ if __name__ == "__main__": # Log uvloop status logger.info(" uvloop: enabled (asyncio speedup active)") - - uvicorn.run( - "api_server:app", - host=Config["api_host"], - port=Config["api_port"], - reload=True, - loop="uvloop" - ) + + # Добавляем обработчик сигнала SIGTERM для корректного завершения + def handle_sigterm(signum, frame): + logger.warning("Received SIGTERM signal, shutting down gracefully") + sys.exit(0) + + signal.signal(signal.SIGTERM, handle_sigterm) + + try: + uvicorn.run( + "api_server:app", + host=Config["api_host"], + port=Config["api_port"], + reload=True, + loop="uvloop" + ) + except OSError as e: + if "[Errno 98] Address already in use" in str(e): + logger.critical(f"Port {Config['api_port']} is already in use. Exiting with code 1 to trigger Docker restart.") + sys.exit(1) + else: + logger.critical(f"Failed to start server: {str(e)}") + sys.exit(1) async def find_file_id_in_message(message: Message, file_unique_id: str) -> Union[str, None]: """Find file_id by checking all possible media types in message""" diff --git a/telegram_client.py b/telegram_client.py index e9b7e11..3e21651 100644 --- a/telegram_client.py +++ b/telegram_client.py @@ -72,34 +72,24 @@ class TelegramClient: raise def _restart_app(self): - """Restarts the application""" - logger.warning("connection_handler: restarting application") + """Restarts the application by exiting with non-zero code to trigger Docker container restart""" + logger.warning("connection_handler: restarting application by exit with error code") try: - # Properly shutdown client before restart + # Properly shutdown client before exit loop = asyncio.get_event_loop() if loop.is_running(): loop.create_task(self.stop()) - # Добавляем задержку, чтобы предыдущий процесс успел завершиться - # и освободить порт и другие ресурсы logger.info("connection_handler: waiting for resources to be freed before restart") - time.sleep(5) # Увеличиваем до 5 секунд + time.sleep(2) - # Принудительно завершаем текущий процесс для освобождения порта - logger.info("connection_handler: killing current process before restart") - pid = os.getpid() - # Используем fork и exec для более надежного перезапуска - if os.fork() > 0: - # Родительский процесс - sys.exit(0) - else: - # Дочерний процесс - time.sleep(1) # Даем родителю умереть - os.execv(sys.executable, [sys.executable] + sys.argv) + # Выходим с ненулевым кодом, чтобы Docker перезапустил контейнер + logger.critical("connection_handler: forcefully exiting process with code 1") + sys.exit(1) except Exception as e: logger.error(f"connection_handler: error during restart: {str(e)}") - # Emergency termination in case of restart failure - os.kill(os.getpid(), signal.SIGTERM) + # Emergency termination + os._exit(1) async def stop(self): if self.client.is_connected: