Skip to content

host_conftest pooled async engine causes 'Future attached to a different loop' across tests #76

Description

@antosubash

Summary

host_conftest.py's shared app fixture builds a single async engine that's reused across tests. asyncpg connections are bound to the event loop in which they're first acquired. With pytest-asyncio's default function-scoped event loops, a connection acquired in test N becomes stale in test N+1's loop, producing:

RuntimeError: Future <Future pending> attached to a different loop

Symptoms: tests that hit the DB pass individually but fail in a batch.

Workaround currently used

Every module's tests/conftest.py overrides the framework's db fixture with a per-test NullPool engine:

import pytest_asyncio
from sqlalchemy.ext.asyncio import async_sessionmaker, create_async_engine
from sqlalchemy.pool import NullPool

@pytest_asyncio.fixture
async def db():
    engine = create_async_engine(TEST_URL, poolclass=NullPool)
    factory = async_sessionmaker(engine, expire_on_commit=False)
    async with factory() as session:
        try:
            yield session
        finally:
            await session.rollback()
    await engine.dispose()

NullPool avoids connection pooling entirely so every test gets a fresh connection in its own loop.

Suggested fix

In host_conftest.py, ship the db fixture as NullPool-backed by default:

@pytest_asyncio.fixture
async def db(app):
    # Don't share app.state.sm.db.session_factory — that's pooled.
    # Use a per-test engine to match pytest-asyncio's per-test loops.
    engine = create_async_engine(_TEST_DATABASE_URL, poolclass=NullPool)
    factory = async_sessionmaker(engine, expire_on_commit=False)
    async with factory() as session:
        try:
            yield session
        finally:
            await session.rollback()
    await engine.dispose()

Or document the override prominently and ship a helper users can call from their conftest.

Impact

Every multi-test module needs a NullPool conftest override. We carry this in 4 module conftests already (lacowiki_legends, lacowiki_sampling, lacowiki_validation, and lacowiki_datasets would need it too if it had >1 async test).

Environment

  • pytest-asyncio>=0.24 (function-scoped event loops by default)
  • asyncpg (loop-bound connections)
  • sqlalchemy>=2.0 async engine

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions