-
Notifications
You must be signed in to change notification settings - Fork 42
Description
In one of my test suites using pytest-recording, I sometimes get one of two exceptions in vcr.patch.ConnectionRemover.__exit__():
> for pool, connections in self._connection_pool_to_connections.items():
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
E RuntimeError: dictionary changed size during iteration
or
> for connection in connections:
^^^^^^^^^^^
E RuntimeError: Set changed size during iteration
More complete pytest output for one of them is included below.
This code clearly works for a lot of pytest-recording users, so there's got to be something special about the tests or the code under test in my case. I think this is what it is:
Some of the code exercised by the tests pushes work items onto a queue. A background thread picks them up and processes them, and that involves making HTTP requests.
If I call .join() on the queue right after pushing the work item, the problem goes away. I imagine that without the join, some connection handling done in the background thread can end up running concurrently with the ConnectionRemover.__exit__() call, causing the error.
Versions:
vcrpy 7.0.0
pytest 8.4.2
pytest-recording 0.13.4
pytest-xdist 3.8.0
python 3.13.7
Redacted pytest output:
================================================================ ERRORS =================================================================
___________________________________________ ERROR at teardown of test_XXX ___________________________________________
[gw3] darwin -- Python 3.13.7 /XXX/.venv/bin/python
request = <SubRequest 'vcr' for <Function XXX>>, vcr_markers = [Mark(name='vcr', args=(), kwargs={})]
vcr_cassette_dir = '/XXX/XXX'
record_mode = 'new_episodes', disable_recording = False, pytestconfig = <_pytest.config.Config object at 0x10579d6a0>
@pytest.fixture(autouse=True) # type: ignore
def vcr(
request: SubRequest,
vcr_markers: List[Mark],
vcr_cassette_dir: str,
record_mode: str,
disable_recording: bool,
pytestconfig: Config,
) -> Iterator[Optional["Cassette"]]:
"""Install a cassette if a test is marked with `pytest.mark.vcr`."""
if disable_recording:
yield None
elif vcr_markers:
from ._vcr import use_cassette
config = request.getfixturevalue("vcr_config")
default_cassette = request.getfixturevalue("default_cassette_name")
> with use_cassette(
default_cassette,
vcr_cassette_dir,
record_mode,
vcr_markers,
config,
pytestconfig,
) as cassette:
.venv/lib/python3.13/site-packages/pytest_recording/plugin.py:158:
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
.venv/lib/python3.13/site-packages/vcr/cassette.py:98: in __exit__
next(self.__finish, None)
.venv/lib/python3.13/site-packages/vcr/cassette.py:57: in _patch_generator
with contextlib.ExitStack() as exit_stack:
^^^^^^^^^^^^^^^^^^^^^^
../../.local/share/uv/python/cpython-3.13.7-macos-aarch64-none/lib/python3.13/contextlib.py:619: in __exit__
raise exc
../../.local/share/uv/python/cpython-3.13.7-macos-aarch64-none/lib/python3.13/contextlib.py:604: in __exit__
if cb(*exc_details):
^^^^^^^^^^^^^^^^
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
self = <vcr.patch.ConnectionRemover object at 0x11019d130>, args = (None, None, None)
pool = <urllib3.connectionpool.HTTPConnectionPool object at 0x110414c80>
connections = {<vcr.patch.VCRRequestsHTTPConnection/XXX/XXX.yaml object at 0x10fb1e350>, <vcr.patch.VCRRequestsHTTPConnection/XXX/XXX.yaml object at 0x10fa902f0>}
readd_connections = [None, None, None, None, None, None, ...]
def __exit__(self, *args):
> for pool, connections in self._connection_pool_to_connections.items():
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
E RuntimeError: dictionary changed size during iteration
.venv/lib/python3.13/site-packages/vcr/patch.py:380: RuntimeError
--------------------------------------------------------- Captured stderr call ----------------------------------------------------------
XXX
----------------------------------------------------------- Captured log call -----------------------------------------------------------
XXX
======================================================== short test summary info ========================================================
ERROR XXX - RuntimeError: dictionary changed size during iteration
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! xdist.dsession.Interrupted: stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
========================================== 187 passed, 2 skipped, 1 xfailed, 1 error in 11.92s ==========================================
(This pytest run was with pytest -n5 using pytest-xdist. I later discovered that concurrent test execution is not required to trigger the exception.)