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

fix: ensure all timers are cancelled when after staggered race finishes #136

Merged
merged 6 commits into from
Feb 7, 2025
Merged
Show file tree
Hide file tree
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,8 @@ prerelease = true
[tool.pytest.ini_options]
addopts = "-v -Wdefault --cov=aiohappyeyeballs --cov-report=term-missing:skip-covered"
pythonpath = ["src"]
asyncio_mode = "auto"
asyncio_default_fixture_loop_scope = "function"

[tool.coverage.run]
branch = true
Expand Down
4 changes: 2 additions & 2 deletions src/aiohappyeyeballs/_staggered.py
Original file line number Diff line number Diff line change
Expand Up @@ -156,9 +156,9 @@ async def run_one_coro(
# so we have no winner and all coroutines failed.
break

while tasks:
while tasks or start_next:
bdraco marked this conversation as resolved.
Show resolved Hide resolved
done = await _wait_one(
[*tasks, start_next] if start_next else tasks, loop
(*tasks, start_next) if start_next else tasks, loop
)
if done is start_next:
# The current task has failed or the timer has expired
Expand Down
30 changes: 30 additions & 0 deletions tests/conftest.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,10 @@
"""Configuration for the tests."""

import asyncio
import reprlib
import threading
from asyncio.events import AbstractEventLoop, TimerHandle
from contextlib import contextmanager
from typing import Generator

import pytest
Expand All @@ -16,6 +19,27 @@ def verify_threads_ended():
assert not threads


def get_scheduled_timer_handles(loop: AbstractEventLoop) -> list[TimerHandle]:
"""Return a list of scheduled TimerHandles."""
handles: list[TimerHandle] = loop._scheduled # type: ignore[attr-defined]
return handles


@contextmanager
def long_repr_strings() -> Generator[None]:
"""Increase reprlib maxstring and maxother to 300."""
arepr = reprlib.aRepr
original_maxstring = arepr.maxstring
original_maxother = arepr.maxother
arepr.maxstring = 300
arepr.maxother = 300
try:
yield
finally:
arepr.maxstring = original_maxstring
arepr.maxother = original_maxother


@pytest.fixture(autouse=True)
def verify_no_lingering_tasks(
event_loop: asyncio.AbstractEventLoop,
Expand All @@ -30,3 +54,9 @@ def verify_no_lingering_tasks(
task.cancel()
if tasks:
event_loop.run_until_complete(asyncio.wait(tasks))

for handle in get_scheduled_timer_handles(event_loop):
if not handle.cancelled():
with long_repr_strings():
pytest.fail(f"Lingering timer after test {handle!r}")
handle.cancel()
Loading