Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Improve asyncio support to avoid hangs of asyncio.sleep() #470

Merged
merged 2 commits into from
Feb 22, 2023

Conversation

marcinsulikowski
Copy link
Contributor

@marcinsulikowski marcinsulikowski commented Sep 14, 2022

Clean up asyncio tests

We finish the cleanup of test_asyncio.py done in c60dd14
and in 204647b by removing remnants of support of Pythons with no
asyncio module because all Python interpreters supported by freezegun
already support asyncio.

Avoid asyncio.sleep() hanging forever when time is frozen

The following code:

async def test():
    with freeze_time("2020-01-01"):
        await asyncio.sleep(0.01)

hangs forever since FreezeGun 1.1.0 because 1.1.0 started patching
time.monotonic() (see #369) which is used internally by asyncio
event loops to schedule code for execution in the future. This breaks
many projects that uses FreezeGun to test asynchronous code.

We fix this by changing freeze_time to patch asyncio event loop's
time() method in a way that it uses real monotonic time instead of the
frozen one. Note that we couldn't achieve this by adding asyncio to
DEFAULT_IGNORE_LIST in freezegun/config.py because any running async
code has functions from the asyncio module on its stack -- adding
asyncio to the ignore list would just disable freezing time in any
async code. This is why we patch one method of a specific class instead.

This change not only fixes asyncio.sleep() but also things like
asyncio.get_running_loop().call_later (for scheduling task execution
in the future) which in turn makes things like timeouts work in async
code while time is frozen. This may not be desired because some users
may expect that execution of events scheduled to happen in the future
can be controlled using FreezeGun. However, it's not easy to distinguish
between things that users would like to see frozen time and those which
should not (like asyncio.sleep()) because all of them use the same
clock. Therefore, we opt for making all asyncio internals not affected
by FreezeGun.

We also add more tests that verify how FreezeGun interacts with asyncio
code, including tests that cover the scenario described in #437 which we
aim to fix.

Closes #401
Closes #437

We finish the cleanup of `test_asyncio.py` done in c60dd14
and in 204647b by removing remnants of support of Pythons with no
`asyncio` module because all Python interpreters supported by freezegun
already support `asyncio`.
The following code:

    async def test():
        with freeze_time("2020-01-01"):
            await asyncio.sleep(0.01)

hangs forever since FreezeGun 1.1.0 because 1.1.0 started patching
`time.monotonic()` (see spulec#369) which is used internally by `asyncio`
event loops to schedule code for execution in the future. This breaks
many projects that uses FreezeGun to test asynchronous code.

We fix this by changing `freeze_time` to patch asyncio event loop's
`time()` method in a way that it uses real monotonic time instead of the
frozen one. Note that we couldn't achieve this by adding `asyncio` to
`DEFAULT_IGNORE_LIST` in `freezegun/config.py` because any running async
code has functions from the `asyncio` module on its stack -- adding
`asyncio` to the ignore list would just disable freezing time in any
async code. This is why we patch one method of a specific class instead.

This change not only fixes `asyncio.sleep()` but also things like
`asyncio.get_running_loop().call_later` (for scheduling task execution
in the future) which in turn makes things like timeouts work in async
code while time is frozen. This may not be desired because some users
may expect that execution of events scheduled to happen in the future
can be controlled using FreezeGun. However, it's not easy to distinguish
between things that users would like to see frozen time and those which
should not (like `asyncio.sleep()`) because all of them use the same
clock. Therefore, we opt for making all `asyncio` internals not affected
by FreezeGun.

We also add more tests that verify how FreezeGun interacts with asyncio
code, including tests that cover the scenario described in spulec#437 which we
aim to fix.

Closes spulec#401
Closes spulec#437
@marcinsulikowski
Copy link
Contributor Author

I rebased this pull request on top of the current master branch and resolved all the conflicts. @boxed, what do you think about it?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
2 participants