124 lines
5.1 KiB
Python
124 lines
5.1 KiB
Python
# app/main.py
|
|
import asyncio
|
|
from contextlib import asynccontextmanager
|
|
|
|
from fastapi import FastAPI
|
|
|
|
from app.utils.logger import get_logger
|
|
from app.utils.nvMapPwScraper import NvMapPwScraper
|
|
from config import ssulbox_settings
|
|
|
|
logger = get_logger("core")
|
|
|
|
|
|
@asynccontextmanager
|
|
async def lifespan(app: FastAPI):
|
|
"""FastAPI 애플리케이션 생명주기 관리"""
|
|
# Startup - 애플리케이션 시작 시
|
|
logger.info("Starting up...")
|
|
|
|
try:
|
|
from config import prj_settings
|
|
|
|
# DEBUG 모드일 때만 데이터베이스 테이블 자동 생성
|
|
if prj_settings.DEBUG:
|
|
from app.database.session import create_db_tables
|
|
|
|
await create_db_tables()
|
|
logger.info("Database tables created (DEBUG mode)")
|
|
|
|
# dashboard 테이블 초기화 및 기존 데이터 마이그레이션 (모든 환경)
|
|
from app.dashboard.migration import init_dashboard_table
|
|
await init_dashboard_table()
|
|
|
|
# 썰박스 스키마는 앱이 만들지 않는다 — **DB 변경은 전부 수동**이 방침이다.
|
|
# 배포 전에 docs/database-schema/migration_2026-07-30_ssulbox.sql 을 직접 실행할 것.
|
|
# (자동 마이그레이션 ensure_ssulbox_schema() 는 2026-07-30 제거)
|
|
if ssulbox_settings.SSULBOX_ENABLED:
|
|
# 고아 잡 스윕 — 이전 프로세스가 죽으며 남긴 queued/running 을 환불·정리.
|
|
# 이건 DDL 이 아니라 데이터 정리(환불 UPDATE)라 앱 책임으로 남긴다.
|
|
# 기동 직후 인메모리 잡은 0개이므로 비터미널 잡은 전부 고아다.
|
|
# ⚠️ 이 불변식은 단일 워커 전제다(--workers 를 늘리면 정상 잡을 오판한다).
|
|
try:
|
|
from app.database.session import BackgroundSessionLocal
|
|
from app.ssulbox.services import task_service
|
|
|
|
async with BackgroundSessionLocal() as session:
|
|
swept = await task_service.sweep_orphans(session)
|
|
await session.commit()
|
|
if swept:
|
|
logger.info(f"[ssulbox] 고아 잡 {swept}건 환불·정리")
|
|
except Exception as e:
|
|
logger.error(
|
|
f"[ssulbox] 고아 스윕 실패 (다음 기동 시 재시도): "
|
|
f"{type(e).__name__}: {e}"
|
|
)
|
|
|
|
# P2V 고아 잡 스윕 — 검수 방치·크래시로 남은 선차감분을 환불하고,
|
|
# archiving 에 갇힌 잡을 재시도 가능 상태로 되돌린다 (설계 R-1·R-3).
|
|
# 스키마는 앱이 만들지 않는다 — migration_2026-08-25_p2v.sql 수동 실행.
|
|
from config import p2v_settings
|
|
|
|
if p2v_settings.P2V_ENABLED:
|
|
try:
|
|
from app.database.session import BackgroundSessionLocal
|
|
from app.p2v.services import f1_service, f2_service
|
|
|
|
async with BackgroundSessionLocal() as session:
|
|
swept = await f1_service.sweep_orphans(session)
|
|
swept += await f2_service.sweep_orphans(session)
|
|
await session.commit()
|
|
if swept:
|
|
logger.info(f"[p2v] 고아 잡 {swept}건 환불·정리")
|
|
except Exception as e:
|
|
logger.error(
|
|
f"[p2v] 고아 스윕 실패 (다음 기동 시 재시도): "
|
|
f"{type(e).__name__}: {e}"
|
|
)
|
|
|
|
await NvMapPwScraper.initiate_scraper()
|
|
except asyncio.TimeoutError:
|
|
logger.error("Database initialization timed out")
|
|
# 타임아웃 시 앱 시작 중단하려면 raise, 계속하려면 pass
|
|
raise
|
|
except Exception as e:
|
|
logger.error(f"Database initialization failed: {e}")
|
|
# 에러 시 앱 시작 중단하려면 raise, 계속하려면 pass
|
|
raise
|
|
|
|
yield # 애플리케이션 실행 중
|
|
|
|
# Shutdown - 애플리케이션 종료 시
|
|
logger.info("Shutting down...")
|
|
|
|
# 썰박스 신규 잡 큐잉 차단. dispose_engine() 전에 해야 워커 스레드가
|
|
# 죽은 커넥션에 접근하지 않는다. 진행 중이던 잡은 다음 기동의 스윕이 환불한다.
|
|
if ssulbox_settings.SSULBOX_ENABLED:
|
|
try:
|
|
from app.ssulbox.worker import job_manager
|
|
|
|
job_manager.shutdown()
|
|
except Exception as e:
|
|
logger.warning(f"[ssulbox] 종료 처리 실패: {e}")
|
|
|
|
# 공유 HTTP 클라이언트 종료
|
|
from app.utils.creatomate import close_shared_client
|
|
from app.utils.upload_blob_as_request import close_shared_blob_client
|
|
from app.p2v.services.client import close_client as close_p2v_client
|
|
|
|
await close_shared_client()
|
|
await close_shared_blob_client()
|
|
await close_p2v_client()
|
|
|
|
from app.database.like_cache import close_like_cache
|
|
await close_like_cache()
|
|
|
|
# 데이터베이스 엔진 종료
|
|
from app.database.session import dispose_engine
|
|
|
|
await dispose_engine()
|
|
|
|
|
|
# FastAPI 앱 생성 (lifespan 적용)
|
|
app = FastAPI(title="CastAD", lifespan=lifespan)
|