From d28e094dc4d43159be5c479015daa7f603ffe0d5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alex=20Gr=C3=B6nholm?= Date: Sun, 6 Sep 2026 22:35:14 +0300 Subject: [PATCH 1/3] Added cancel shielding to AsyncFile.aclose() --- docs/versionhistory.rst | 2 ++ src/anyio/_core/_fileio.py | 5 +++-- tests/test_fileio.py | 17 ++++++++++++++++- 3 files changed, 21 insertions(+), 3 deletions(-) diff --git a/docs/versionhistory.rst b/docs/versionhistory.rst index 2f9b724da..b8e21dc24 100644 --- a/docs/versionhistory.rst +++ b/docs/versionhistory.rst @@ -13,6 +13,8 @@ This library adheres to `Semantic Versioning 2.0 `_. ``Could not create 2 listeners with a consistent port`` when an ephemeral port is requested and IPv6 is enabled and the dual-stack path is not available or a specific local host name was given +- Fixed ``AsyncFile`` not shielding against cancellation while closing + (`#1314 `_) **4.15.1** diff --git a/src/anyio/_core/_fileio.py b/src/anyio/_core/_fileio.py index 498eef41d..c41e8bc49 100644 --- a/src/anyio/_core/_fileio.py +++ b/src/anyio/_core/_fileio.py @@ -25,7 +25,7 @@ overload, ) -from .. import to_thread +from .. import CancelScope, to_thread from ..abc import AsyncResource from ._synchronization import CapacityLimiter @@ -114,7 +114,8 @@ async def __aiter__(self) -> AsyncIterator[AnyStr]: break async def aclose(self) -> None: - return await to_thread.run_sync(self._fp.close, limiter=self._limiter) + with CancelScope(shield=True): + await to_thread.run_sync(self._fp.close, limiter=self._limiter) async def read(self, size: int = -1) -> AnyStr: return await to_thread.run_sync(self._fp.read, size, limiter=self._limiter) diff --git a/tests/test_fileio.py b/tests/test_fileio.py index 2b7f95699..801d0ec45 100644 --- a/tests/test_fileio.py +++ b/tests/test_fileio.py @@ -12,7 +12,14 @@ from _pytest.fixtures import FixtureRequest from _pytest.tmpdir import TempPathFactory -from anyio import AsyncFile, CapacityLimiter, Path, open_file, wrap_file +from anyio import ( + AsyncFile, + CancelScope, + CapacityLimiter, + Path, + open_file, + wrap_file, +) @pytest.fixture(params=[False, True]) @@ -86,6 +93,14 @@ async def test_wrap_file( assert path.read_text() == "dummydata" + async def test_shieled_aclose(self, tmp_path: pathlib.Path) -> None: + async with await open_file(tmp_path / "foo", "wb") as f: + with CancelScope() as scope: + scope.cancel() + await f.aclose() + + assert f.closed + class TestPath: @pytest.fixture From 134c9a0c7a30029778b11ee3e2ea1fe272d8a3e0 Mon Sep 17 00:00:00 2001 From: EmmanuelNiyonshuti Date: Mon, 7 Sep 2026 17:32:14 +0200 Subject: [PATCH 2/3] update the test --- tests/test_fileio.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/test_fileio.py b/tests/test_fileio.py index 801d0ec45..3cadb1a22 100644 --- a/tests/test_fileio.py +++ b/tests/test_fileio.py @@ -93,7 +93,7 @@ async def test_wrap_file( assert path.read_text() == "dummydata" - async def test_shieled_aclose(self, tmp_path: pathlib.Path) -> None: + async def test_shielded_aclose(self, tmp_path: pathlib.Path) -> None: async with await open_file(tmp_path / "foo", "wb") as f: with CancelScope() as scope: scope.cancel() From b3ec4d350007a80a00658a34e71d48730f2d82c9 Mon Sep 17 00:00:00 2001 From: EmmanuelNiyonshuti Date: Mon, 7 Sep 2026 17:35:43 +0200 Subject: [PATCH 3/3] Fix broken import --- src/anyio/_core/_fileio.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/anyio/_core/_fileio.py b/src/anyio/_core/_fileio.py index c41e8bc49..fc0c36fab 100644 --- a/src/anyio/_core/_fileio.py +++ b/src/anyio/_core/_fileio.py @@ -25,9 +25,10 @@ overload, ) -from .. import CancelScope, to_thread +from .. import to_thread from ..abc import AsyncResource from ._synchronization import CapacityLimiter +from ._tasks import CancelScope if sys.version_info >= (3, 11): from typing import Self