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())