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:
+26
-8
@@ -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
@@ -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:
|
||||
|
||||
Reference in New Issue
Block a user