From d4008924d98f6b84e55c0851727525283156508a Mon Sep 17 00:00:00 2001 From: I Luk Kim Date: Sun, 29 Mar 2026 19:21:45 -0700 Subject: [PATCH] =?UTF-8?q?fix:=20isolated=20thread=EC=97=90=20=EB=8F=85?= =?UTF-8?q?=EB=A6=BD=EC=A0=81=EC=9D=B8=20DB=20=EC=97=94=EC=A7=84=20?= =?UTF-8?q?=EC=83=9D=EC=84=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit asyncpg 커넥션은 event loop에 바인딩됨. API의 AsyncSessionLocal을 다른 loop에서 사용하면 'Future attached to a different loop' 오류 발생. → build thread 내에서 전용 engine + session factory 생성. Co-Authored-By: Claude Sonnet 4.6 --- app/api/v1/endpoints/universe.py | 27 ++++++++++++++++++++++++++- 1 file changed, 26 insertions(+), 1 deletion(-) diff --git a/app/api/v1/endpoints/universe.py b/app/api/v1/endpoints/universe.py index ea9df5f..b44bf6c 100644 --- a/app/api/v1/endpoints/universe.py +++ b/app/api/v1/endpoints/universe.py @@ -196,10 +196,33 @@ async def _run_build_snapshots( _asyncio.set_event_loop(loop) async def _build(): + # Create a fresh engine + session factory bound to THIS event loop. + # asyncpg connections are loop-bound; reusing the API's AsyncSessionLocal + # in a different loop raises "Future attached to a different loop". + from sqlalchemy.ext.asyncio import ( + AsyncSession, + async_sessionmaker, + create_async_engine, + ) + from sqlalchemy.orm import sessionmaker as _sessionmaker + from app.core.config import settings + + _engine = create_async_engine( + settings.DATABASE_URL, + echo=False, + pool_size=2, + max_overflow=0, + pool_timeout=30, + pool_pre_ping=True, + connect_args={"server_settings": {"jit": "off"}}, + ) + _SessionLocal = _sessionmaker( + _engine, class_=AsyncSession, expire_on_commit=False + ) svc = UniverseService() try: result = await svc.build_snapshots( - AsyncSessionLocal, + _SessionLocal, tickers=tickers, start_date=start_date, end_date=end_date, @@ -208,6 +231,8 @@ async def _run_build_snapshots( logger.info(f"Universe background build complete: {result}") except Exception as e: logger.error(f"Universe background build failed: {e}") + finally: + await _engine.dispose() try: loop.run_until_complete(_build())