diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 14fb1d6..224bcfa 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -8,6 +8,7 @@ on: jobs: build: runs-on: ubuntu-latest + timeout-minutes: 10 steps: - uses: actions/checkout@v4 - uses: actions/setup-python@v5 diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml index 2bba038..0b02ad2 100644 --- a/.github/workflows/lint.yml +++ b/.github/workflows/lint.yml @@ -8,13 +8,14 @@ on: jobs: ruff: runs-on: ubuntu-latest + timeout-minutes: 5 steps: - uses: actions/checkout@v4 - uses: actions/setup-python@v5 with: python-version: "3.11" cache: pip - - name: Install dependencies - run: pip install -e ".[dev]" + - name: Install ruff + run: pip install ruff - name: Run ruff run: ruff check . diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 8f7119f..e828a54 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -8,6 +8,7 @@ on: jobs: pytest: runs-on: ubuntu-latest + timeout-minutes: 15 strategy: matrix: python-version: ["3.11", "3.12"] @@ -21,5 +22,6 @@ jobs: run: | python -m pip install --upgrade pip python -m pip install -e ".[dev]" + python -m pip install pytest-timeout - name: Run pytest - run: pytest -v + run: pytest -v --timeout=30 diff --git a/EvoScientist/channels/middleware.py b/EvoScientist/channels/middleware.py index 4daee42..f56877d 100644 --- a/EvoScientist/channels/middleware.py +++ b/EvoScientist/channels/middleware.py @@ -31,13 +31,18 @@ _logger = logging.getLogger(__name__) async def _cancel_task(task: asyncio.Task) -> None: """Cancel an asyncio task and await its completion. - Suppresses ``CancelledError`` so callers don't need to handle it. + Suppresses ``CancelledError`` from the cancelled *task* but re-raises + if the **current** task was itself cancelled (to avoid swallowing an + outer cancellation signal — required for correct behaviour on + Python 3.12+ where ``_must_cancel`` no longer auto-re-delivers). """ task.cancel() try: await task except asyncio.CancelledError: - pass + current = asyncio.current_task() + if current is not None and current.cancelling() > 0: + raise except Exception: pass # Already logged elsewhere; prevent unhandled propagation diff --git a/EvoScientist/channels/mixins.py b/EvoScientist/channels/mixins.py index c538aca..6a0c2a8 100644 --- a/EvoScientist/channels/mixins.py +++ b/EvoScientist/channels/mixins.py @@ -252,7 +252,9 @@ class WebSocketMixin: try: await task except asyncio.CancelledError: - pass + current = asyncio.current_task() + if current is not None and current.cancelling() > 0: + raise except Exception: pass @@ -315,6 +317,8 @@ class PollingMixin: try: await task except asyncio.CancelledError: - pass + current = asyncio.current_task() + if current is not None and current.cancelling() > 0: + raise except Exception: pass