|
14 | 14 | import os |
15 | 15 | import signal |
16 | 16 | import sys |
| 17 | +import threading |
17 | 18 | from collections.abc import Callable |
18 | 19 | from contextlib import AsyncExitStack, suppress |
19 | 20 | from pathlib import Path |
| 21 | +from types import SimpleNamespace |
20 | 22 | from typing import TextIO, cast |
21 | 23 |
|
22 | 24 | import anyio |
23 | 25 | import anyio.abc |
| 26 | +import anyio.from_thread |
24 | 27 | import anyio.lowlevel |
| 28 | +import anyio.to_thread |
25 | 29 | import pytest |
26 | 30 | import trio |
27 | 31 | import trio.testing |
@@ -572,6 +576,45 @@ async def test_a_command_that_cannot_be_execed_raises_enoent() -> None: |
572 | 576 | assert exc_info.value.errno == errno.ENOENT |
573 | 577 |
|
574 | 578 |
|
| 579 | +@pytest.mark.anyio |
| 580 | +async def test_cancellation_during_windows_command_resolution_returns_before_resolution_finishes( |
| 581 | + monkeypatch: pytest.MonkeyPatch, |
| 582 | +) -> None: |
| 583 | + """Cancelling `stdio_client` does not wait for blocked Windows command resolution.""" |
| 584 | + resolution_started = anyio.Event() |
| 585 | + resolution_release = threading.Event() |
| 586 | + resolution_finished = threading.Event() |
| 587 | + |
| 588 | + def blocking_resolver(command: str) -> str: |
| 589 | + anyio.from_thread.run_sync(resolution_started.set) |
| 590 | + resolution_release.wait() |
| 591 | + resolution_finished.set() |
| 592 | + return command |
| 593 | + |
| 594 | + monkeypatch.setattr(stdio, "sys", SimpleNamespace(platform="win32")) |
| 595 | + monkeypatch.setattr(stdio, "get_windows_executable_command", blocking_resolver) |
| 596 | + |
| 597 | + cancel_scope = anyio.CancelScope() |
| 598 | + client_stopped = anyio.Event() |
| 599 | + |
| 600 | + async def run_client() -> None: |
| 601 | + with cancel_scope: |
| 602 | + async with AsyncExitStack() as stack: |
| 603 | + await stack.enter_async_context(stdio_client(FAKE_PARAMS)) |
| 604 | + client_stopped.set() |
| 605 | + |
| 606 | + with anyio.fail_after(5): |
| 607 | + async with anyio.create_task_group() as tg: |
| 608 | + tg.start_soon(run_client) |
| 609 | + await resolution_started.wait() |
| 610 | + cancel_scope.cancel() |
| 611 | + try: |
| 612 | + await client_stopped.wait() |
| 613 | + finally: |
| 614 | + resolution_release.set() |
| 615 | + await anyio.to_thread.run_sync(resolution_finished.wait) |
| 616 | + |
| 617 | + |
575 | 618 | @pytest.mark.anyio |
576 | 619 | async def test_cancellation_during_spawn_leaks_no_streams(monkeypatch: pytest.MonkeyPatch) -> None: |
577 | 620 | """Cancellation while the spawn is still in flight must not leak the internal streams. |
|
0 commit comments