Lancetnik / FastDepends

FastDepends - FastAPI Dependency Injection system extracted from FastAPI and cleared of all HTTP logic. Async and sync modes are both supported.
https://lancetnik.github.io/FastDepends/
MIT License
245 stars 8 forks source link

Incomplete Execution of Yielded Dependencies Upon Abnormal Termination #86

Closed ff4m1r closed 3 months ago

ff4m1r commented 3 months ago

Python version: 3.12.2

When a dependency is yielded within a function decorated with @inject, it is expected that the entire dependency coroutine would execute to completion before any subsequent code is executed. However, in scenarios where the application is terminated abruptly, the coroutine seems to be interrupted before it completes its execution.

To illustrate the issue, I've provided a minimal example below:

import asyncio
from typing import AsyncIterator

from fast_depends import Depends, inject

async def sample_dep() -> AsyncIterator[int]:
    print("before")
    yield 10
    print("after")

@inject
async def a_random_function(numb: int = Depends(sample_dep)) -> None:
    print(numb + 10)
    await asyncio.sleep(2)

async def main() -> None:
    try:
        await a_random_function()
    except asyncio.CancelledError:
        print("cancelled")

if __name__ == "__main__":
    asyncio.run(main())

In the provided example, the text "after" is expected to be printed after the value 10 is yielded from the sample_dep coroutine. However, if the application is terminated abruptly, such as by a keyboard interrupt, the "after" text is not printed, indicating that the coroutine was not allowed to complete its execution.

I believe this behavior may lead to unexpected results and could potentially cause issues in real-world applications where the completion of dependencies is crucial (like database or broker connections)

Could you please investigate this behavior and provide guidance on how to ensure that yielded dependencies are executed to completion even in scenarios of abnormal termination?

Appreciate your assistance with this matter and your contributions to this awesome library. Thank you.

Lancetnik commented 3 months ago

You should catch any execution exception in the yeild

async def sample_dep() -> AsyncIterator[int]:
    print("before")
    try:
        yield 10
    finally:
        print("after")