implement graceful shutdown in api_server: add SIGTERM handler for proper termination and enhance error handling for server startup issues

This commit is contained in:
vvzvlad
2025-06-10 14:06:56 +03:00
parent 2c9ebf139f
commit d6c3725b02
2 changed files with 35 additions and 27 deletions
+26 -8
View File
@@ -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"""
+9 -19
View File
@@ -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: