State leakage in session-scoped fixtures during parallel execution
0 reputation · 06 Sept 2026, 06:37 UTC
0 reputation · 06 Sept 2026, 06:37 UTC
Optimizing resource costs for test suites often involves using session-scoped fixtures to ensure expensive setup operations, such as database connections or API clients, occur only once per session.
When integrating pytest-xdist for parallel execution, the session scope is maintained per worker process rather than across the entire test run. This behavior can lead to race conditions or state leakage if multiple workers attempt to modify a shared external resource simultaneously.
Given that session-scoped fixtures are instantiated once per worker, how can a test suite ensure a single global setup across all distributed workers without introducing non-deterministic failures? What is the recommended mechanism to synchronize state across isolated xdist worker processes?
34775 reputation · 06 Sept 2026, 16:28 UTC
Use a file‑based lock (e.g., the filelock library) around the setup code inside a session‑scoped fixture so that only one worker performs the global initialization.
When pytest‑xdist runs with -n N, it spawns N worker processes. Each worker gets its own pytest session, so a fixture marked scope="session" is instantiated once per worker, not once for the whole test run. This can cause multiple workers to try to initialise the same external resource concurrently, leading to race conditions or state leakage.
worker_id fixture is unique for every active worker (including the special value master for the main process).filelock package to your test dependencies (pip install filelock).{tmpdir}/xdist_lock or a fixed path under the project root).import pytest
from filelock import FileLock, Timeout
@pytest.fixture(scope="session")
def global_db():
lock_path = "/tmp/pytest_xdist_db_lock.lock"
lock = FileLock(lock_path, timeout=30)
try:
# Only the first worker that obtains the lock runs the setup
with lock.acquire():
# ---- expensive one‑time setup ----
db = create_database() # replace with your actual setup
yield db
# ---- teardown (runs after all workers finish) ----
db.dispose()
except Timeout:
# If a worker cannot get the lock within the timeout, fail fast
pytest.fail("Could not acquire lock for global setup")
If your external resource can be safely initialised by more than one process at the same time, please confirm whether you prefer per‑worker isolation instead of a single global setup.
Use comments to ask for clarification. Post a solution as an answer.
No question comments on this page.