-
-
Notifications
You must be signed in to change notification settings - Fork 341
Open
Description
dependency-injector 4.48.3
Bug: When an async Singleton's returned Future is cancelled before the
underlying async factory completes, the _async_init_instance callback
still fires and calls future_result.set_result() on the already-cancelled
Future, raising asyncio.InvalidStateError.
We also need anyio to reproduce.
How it works:
- Call async singleton → DI returns future_result to caller
- Cancel future_result before slow_init() completes
- Wait for slow_init() finish in the background
- DI's _async_init_instance callback fires and calls set_result() on the already-cancelled future_result
→ InvalidStateError
"""Minimal reproduction of dependency-injector InvalidStateError.
Bug: When an async Singleton's returned Future is cancelled before the
underlying async factory completes, the `_async_init_instance` callback
still fires and calls `future_result.set_result()` on the already-cancelled
Future, raising `asyncio.InvalidStateError`.
Run with:
pytest test_invalid_state.py -v
"""
import asyncio
import pytest
from dependency_injector import containers, providers
async def slow_init():
"""Async resource that takes time to initialize."""
await asyncio.sleep(0.1) # Simulate slow async init
return object()
class Service:
"""Simple service class that takes a backend dependency."""
def __init__(self, backend):
self.backend = backend
class Container(containers.DeclarativeContainer):
backend = providers.Resource(slow_init)
# Singleton with async dependency -> triggers _async_init_instance internally
service = providers.Singleton(Service, backend=backend)
@pytest.fixture
def container_instance():
"""Fresh container for each test."""
return Container()
@pytest.fixture()
def anyio_backend():
return "asyncio"
@pytest.mark.anyio
class TestInvalidStateOnCancel:
async def test_cancel_before_init_completes(self, container_instance):
future = container_instance.service()
await asyncio.sleep(0.01)
future.cancel()
await asyncio.sleep(0.2)Output:
FAILED [100%]
tests/test_.py:44 (TestInvalidStateOnCancel.test_cancel_before_init_completes)
pyfuncitem = <Function test_cancel_before_init_completes>
@pytest.hookimpl(tryfirst=True)
def pytest_pyfunc_call(pyfuncitem: Any) -> bool | None:
def run_with_hypothesis(**kwargs: Any) -> None:
with get_runner(backend_name, backend_options) as runner:
runner.run_test(original_func, kwargs)
backend = pyfuncitem.funcargs.get("anyio_backend")
if backend:
backend_name, backend_options = extract_backend_and_options(backend)
if hasattr(pyfuncitem.obj, "hypothesis"):
# Wrap the inner test function unless it's already wrapped
original_func = pyfuncitem.obj.hypothesis.inner_test
if original_func.__qualname__ != run_with_hypothesis.__qualname__:
if iscoroutinefunction(original_func):
pyfuncitem.obj.hypothesis.inner_test = run_with_hypothesis
return None
if iscoroutinefunction(pyfuncitem.obj):
funcargs = pyfuncitem.funcargs
testargs = {arg: funcargs[arg] for arg in pyfuncitem._fixtureinfo.argnames}
with get_runner(backend_name, backend_options) as runner:
try:
> runner.run_test(pyfuncitem.obj, testargs)
../../../../../Library/Caches/pypoetry/virtualenvs/bla-e4AtMU9r-py3.12/lib/python3.12/site-packages/anyio/pytest_plugin.py:160:
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
../../../../../Library/Caches/pypoetry/virtualenvs/bla-e4AtMU9r-py3.12/lib/python3.12/site-packages/anyio/_backends/_asyncio.py:2279: in run_test
self._raise_async_exceptions()
../../../../../Library/Caches/pypoetry/virtualenvs/bla-e4AtMU9r-py3.12/lib/python3.12/site-packages/anyio/_backends/_asyncio.py:2183: in _raise_async_exceptions
raise exceptions[0]
/Library/Frameworks/Python.framework/Versions/3.12/lib/python3.12/asyncio/events.py:84: in _run
self._context.run(self._callback, *self._args)
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
> ???
E asyncio.exceptions.InvalidStateError: invalid state
src/dependency_injector/providers.pyx:2964: InvalidStateError
Metadata
Metadata
Assignees
Labels
No labels