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..fc0c36fab 100644 --- a/src/anyio/_core/_fileio.py +++ b/src/anyio/_core/_fileio.py @@ -28,6 +28,7 @@ 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 @@ -114,7 +115,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..3cadb1a22 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_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() + await f.aclose() + + assert f.closed + class TestPath: @pytest.fixture