Skip to content

fix(core): create Ryuk once when containers start concurrently - #1146

Open
akshit-khandelwal47 wants to merge 1 commit into
testcontainers:mainfrom
akshit-khandelwal47:fix/reaper-concurrent-start
Open

akshit-khandelwal47 wants to merge 1 commit into
testcontainers:mainfrom
akshit-khandelwal47:fix/reaper-concurrent-start

Conversation

@akshit-khandelwal47

Copy link
Copy Markdown

Fixes #746

Reaper.get_instance() checked and set Reaper._instance without a lock. Starting Ryuk takes a few seconds, so when two threads start containers at the same time, both see no instance, both create testcontainers-ryuk-<SESSION_ID>, and the second create fails with a 409 Conflict. The repro in the issue (two PostgresContainers from a ThreadPoolExecutor) hits it.

This adds the lock suggested in the issue, with a re-check inside it so threads that waited on the lock pick up the instance the first thread created. The fast path, where Ryuk is already running, still takes no lock.

The lock is an RLock on purpose. With TESTCONTAINERS_HUB_IMAGE_NAME_PREFIX set, starting Ryuk's own container calls get_instance() again on the same thread (#1085, PR #1120). Today that ends in a RecursionError; a plain Lock would turn it into a hang.

Testing:

  • New test in tests/core/test_ryuk.py starts 8 threads on a barrier against a slow fake _create_instance and asserts Ryuk is created once. It needs no Docker. It fails on main (assert 8 == 1) and also fails if the lock is kept but the inner re-check removed.
  • pre-commit run on the changed files (ruff, ruff-format, mypy) passes.
  • pytest -m "not long_running" tests/core locally on colima: 485 passed, 30 failed. The same 30 fail on main on this machine (no docker compose plugin, plus test_run_uses_found_network), none of them Reaper tests.

Reaper.get_instance() checked and set the class-level instance without a
lock, so two threads starting containers at the same time could both see
no instance and both try to create testcontainers-ryuk-<session>. The
second create failed with a 409 Conflict.

Guard creation with a double-checked RLock. It is reentrant so that a
nested get_instance() on the same thread, while Ryuk itself is starting,
behaves as it did before instead of deadlocking.

Fixes testcontainers#746

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Bug: Starting multiple containers at once in parallel causes Conflict by starting ryuk container multiple times due to race condition

1 participant