From 415551dd6a9c52c285b875fdcca2f77f761316cb Mon Sep 17 00:00:00 2001
From: Guflly <145608489+Guflly@users.noreply.github.com>
Date: Mon, 27 Jul 2026 00:06:31 -0700
Subject: [PATCH 1/7] Fix UDP socket close hangs on Windows
---
docs/versionhistory.rst | 2 ++
src/anyio/_backends/_asyncio.py | 4 ++++
tests/test_sockets.py | 16 ++++++++++++++++
3 files changed, 22 insertions(+)
diff --git a/docs/versionhistory.rst b/docs/versionhistory.rst
index b5b8d01b9..e0205b40c 100644
--- a/docs/versionhistory.rst
+++ b/docs/versionhistory.rst
@@ -17,6 +17,8 @@ This library adheres to `Semantic Versioning 2.0 `_.
module name. (The default name for a task spawned with ``TaskGroup.start_soon`` or
``TaskGroup.start`` typically includes the module name.)
(`#1234 `_; PR by @gschaffner)
+- Fixed UDP socket closing hanging on Windows if a datagram send was still in flight
+ (`#1237 `_; PR by @Guflly)
- Fixed free-threading compatibility issues arising from the fact that on Python 3.14
free-threading builds, newly created threads inherit the current context by default,
causing AnyIO to behave erroneously in relation to ``start_blocking_portal()`` and
diff --git a/src/anyio/_backends/_asyncio.py b/src/anyio/_backends/_asyncio.py
index 4fc1f0c64..9b91f5472 100644
--- a/src/anyio/_backends/_asyncio.py
+++ b/src/anyio/_backends/_asyncio.py
@@ -1687,6 +1687,8 @@ async def aclose(self) -> None:
self._closed = True
if not self._transport.is_closing():
self._transport.close()
+ await sleep(0)
+ self._transport.abort()
await self._protocol.closed_event.wait()
@@ -1737,6 +1739,8 @@ async def aclose(self) -> None:
self._closed = True
if not self._transport.is_closing():
self._transport.close()
+ await sleep(0)
+ self._transport.abort()
await self._protocol.closed_event.wait()
diff --git a/tests/test_sockets.py b/tests/test_sockets.py
index b456b4079..40c5ede6c 100644
--- a/tests/test_sockets.py
+++ b/tests/test_sockets.py
@@ -1743,6 +1743,14 @@ async def test_aclose_waits_for_fd_release(
udp = await UDPSocket.from_socket(sock)
await udp.aclose()
+ @pytest.mark.skipif(sys.platform != "win32", reason="Windows only")
+ @pytest.mark.parametrize("anyio_backend", asyncio_params)
+ async def test_aclose_during_send(self) -> None:
+ udp = await create_udp_socket(local_host="127.0.0.1")
+ await udp.sendto(b"x", "127.0.0.1", 9999)
+ with fail_after(1):
+ await udp.aclose()
+
async def test_extra_attributes(self, family: AnyIPAddressFamily) -> None:
async with await create_udp_socket(
family=family, local_host="localhost"
@@ -1914,6 +1922,14 @@ async def test_aclose_waits_for_fd_release(
finally:
peer.close()
+ @pytest.mark.skipif(sys.platform != "win32", reason="Windows only")
+ @pytest.mark.parametrize("anyio_backend", asyncio_params)
+ async def test_aclose_during_send(self) -> None:
+ udp = await create_connected_udp_socket("127.0.0.1", 9999)
+ await udp.send(b"x")
+ with fail_after(1):
+ await udp.aclose()
+
async def test_extra_attributes(self, family: AnyIPAddressFamily) -> None:
async with await create_connected_udp_socket(
"localhost", 5000, family=family
From 57cb4a92861992b6ce120979a4acc8b4148f551e Mon Sep 17 00:00:00 2001
From: Guflly <145608489+Guflly@users.noreply.github.com>
Date: Mon, 27 Jul 2026 14:07:27 -0700
Subject: [PATCH 2/7] Fix forced UDP socket closure
---
src/anyio/_backends/_asyncio.py | 14 ++++++++++----
tests/test_sockets.py | 6 +++++-
2 files changed, 15 insertions(+), 5 deletions(-)
diff --git a/src/anyio/_backends/_asyncio.py b/src/anyio/_backends/_asyncio.py
index 9b91f5472..8e2bbddd0 100644
--- a/src/anyio/_backends/_asyncio.py
+++ b/src/anyio/_backends/_asyncio.py
@@ -1687,8 +1687,11 @@ async def aclose(self) -> None:
self._closed = True
if not self._transport.is_closing():
self._transport.close()
- await sleep(0)
- self._transport.abort()
+ try:
+ await sleep(0)
+ finally:
+ self._transport.abort()
+ await AsyncIOBackend.cancel_shielded_checkpoint()
await self._protocol.closed_event.wait()
@@ -1739,8 +1742,11 @@ async def aclose(self) -> None:
self._closed = True
if not self._transport.is_closing():
self._transport.close()
- await sleep(0)
- self._transport.abort()
+ try:
+ await sleep(0)
+ finally:
+ self._transport.abort()
+ await AsyncIOBackend.cancel_shielded_checkpoint()
await self._protocol.closed_event.wait()
diff --git a/tests/test_sockets.py b/tests/test_sockets.py
index 40c5ede6c..64a3fab28 100644
--- a/tests/test_sockets.py
+++ b/tests/test_sockets.py
@@ -34,6 +34,7 @@
from pytest import FixtureRequest
from pytest_mock.plugin import MockerFixture
+import anyio
from anyio import (
BrokenResourceError,
BusyResourceError,
@@ -1747,9 +1748,12 @@ async def test_aclose_waits_for_fd_release(
@pytest.mark.parametrize("anyio_backend", asyncio_params)
async def test_aclose_during_send(self) -> None:
udp = await create_udp_socket(local_host="127.0.0.1")
+ sock = udp.extra(SocketAttribute.raw_socket)
await udp.sendto(b"x", "127.0.0.1", 9999)
with fail_after(1):
- await udp.aclose()
+ await anyio.aclose_forcefully(udp)
+
+ assert sock.fileno() == -1
async def test_extra_attributes(self, family: AnyIPAddressFamily) -> None:
async with await create_udp_socket(
From 51e137ebd28ebc09c2ccd1e0d7814df0f68d352a Mon Sep 17 00:00:00 2001
From: maz
Date: Fri, 31 Jul 2026 14:20:04 -0700
Subject: [PATCH 3/7] Fix forced TCP socket closure
---
src/anyio/_backends/_asyncio.py | 7 +++++--
tests/test_sockets.py | 10 ++++++++++
2 files changed, 15 insertions(+), 2 deletions(-)
diff --git a/src/anyio/_backends/_asyncio.py b/src/anyio/_backends/_asyncio.py
index 8e2bbddd0..3745f99ce 100644
--- a/src/anyio/_backends/_asyncio.py
+++ b/src/anyio/_backends/_asyncio.py
@@ -1403,8 +1403,11 @@ async def aclose(self) -> None:
pass
self._transport.close()
- await sleep(0)
- self._transport.abort()
+ try:
+ await sleep(0)
+ finally:
+ self._transport.abort()
+ await AsyncIOBackend.cancel_shielded_checkpoint()
class _RawSocketMixin:
diff --git a/tests/test_sockets.py b/tests/test_sockets.py
index 64a3fab28..5c06602f9 100644
--- a/tests/test_sockets.py
+++ b/tests/test_sockets.py
@@ -519,6 +519,16 @@ async def test_send_after_close(self, server_addr: tuple[str, int]) -> None:
with pytest.raises(ClosedResourceError):
await stream.send(b"foo")
+ @pytest.mark.parametrize("anyio_backend", asyncio_params)
+ async def test_aclose_forcefully(
+ self, server_addr: tuple[str, int]
+ ) -> None:
+ stream = await connect_tcp(*server_addr)
+ sock = stream.extra(SocketAttribute.raw_socket)
+ await stream.send(b"x")
+ await anyio.aclose_forcefully(stream)
+ assert sock.fileno() == -1
+
async def test_receive_after_peer_closed(
self, family: AnyIPAddressFamily, request: FixtureRequest
) -> None:
From 7928b000c7b762fd820fb6aeecf58e0fc15a64a8 Mon Sep 17 00:00:00 2001
From: "pre-commit-ci[bot]"
<66853113+pre-commit-ci[bot]@users.noreply.github.com>
Date: Fri, 31 Jul 2026 21:20:29 +0000
Subject: [PATCH 4/7] [pre-commit.ci] auto fixes from pre-commit.com hooks
for more information, see https://pre-commit.ci
---
tests/test_sockets.py | 4 +---
1 file changed, 1 insertion(+), 3 deletions(-)
diff --git a/tests/test_sockets.py b/tests/test_sockets.py
index 5c06602f9..dbdb7d31f 100644
--- a/tests/test_sockets.py
+++ b/tests/test_sockets.py
@@ -520,9 +520,7 @@ async def test_send_after_close(self, server_addr: tuple[str, int]) -> None:
await stream.send(b"foo")
@pytest.mark.parametrize("anyio_backend", asyncio_params)
- async def test_aclose_forcefully(
- self, server_addr: tuple[str, int]
- ) -> None:
+ async def test_aclose_forcefully(self, server_addr: tuple[str, int]) -> None:
stream = await connect_tcp(*server_addr)
sock = stream.extra(SocketAttribute.raw_socket)
await stream.send(b"x")
From b3664471d471da8e9f574e74eaac87f8c7ef67e8 Mon Sep 17 00:00:00 2001
From: hansu650 <2788086371@qq.com>
Date: Thu, 13 Aug 2026 10:57:51 +0800
Subject: [PATCH 5/7] Fix concurrent force-close on asyncio socket streams
---
docs/versionhistory.rst | 3 +++
src/anyio/_backends/_asyncio.py | 18 +++++++++++-------
tests/test_sockets.py | 18 ++++++++++++++++++
3 files changed, 32 insertions(+), 7 deletions(-)
diff --git a/docs/versionhistory.rst b/docs/versionhistory.rst
index fa05a88e7..b2fee3bc0 100644
--- a/docs/versionhistory.rst
+++ b/docs/versionhistory.rst
@@ -52,6 +52,9 @@ This library adheres to `Semantic Versioning 2.0 `_.
which triggers ``PytestRemovedIn10Warning`` on ``pytest>=9.2`` and crashes pytest at
startup when ``filterwarnings = error`` is configured
(`#1271 `_; PR by @matthewfeickert)
+- Fixed concurrent ``aclose_forcefully()`` calls on asyncio socket streams returning
+ before the underlying socket was closed
+ (`#1273 `_; PR by @hansu650)
**4.14.2**
diff --git a/src/anyio/_backends/_asyncio.py b/src/anyio/_backends/_asyncio.py
index d18eab2bb..b1658acff 100644
--- a/src/anyio/_backends/_asyncio.py
+++ b/src/anyio/_backends/_asyncio.py
@@ -1393,15 +1393,19 @@ async def send_eof(self) -> None:
async def aclose(self) -> None:
self._closed = True
- if not self._transport.is_closing():
- try:
- self._transport.write_eof()
- except OSError:
- pass
+ try:
+ if not self._transport.is_closing():
+ try:
+ self._transport.write_eof()
+ except OSError:
+ pass
- self._transport.close()
- await sleep(0)
+ self._transport.close()
+ await sleep(0)
+ finally:
self._transport.abort()
+ with CancelScope(shield=True):
+ await sleep(0)
class _RawSocketMixin:
diff --git a/tests/test_sockets.py b/tests/test_sockets.py
index f47567627..683d4f9e1 100644
--- a/tests/test_sockets.py
+++ b/tests/test_sockets.py
@@ -43,6 +43,7 @@
TCPConnectable,
TypedAttributeLookupError,
UNIXConnectable,
+ aclose_forcefully,
as_connectable,
connect_tcp,
connect_unix,
@@ -507,6 +508,23 @@ async def interrupt() -> None:
with pytest.raises(ClosedResourceError):
await stream.receive()
+ async def test_concurrent_aclose_forcefully_waits_for_socket_close(
+ self, server_addr: tuple[str, int]
+ ) -> None:
+ stream = await connect_tcp(*server_addr)
+ raw_socket = stream.extra(SocketAttribute.raw_socket)
+ file_descriptors: list[int] = []
+
+ async def close_stream() -> None:
+ await aclose_forcefully(stream)
+ file_descriptors.append(raw_socket.fileno())
+
+ async with create_task_group() as task_group:
+ task_group.start_soon(close_stream)
+ task_group.start_soon(close_stream)
+
+ assert file_descriptors == [-1, -1]
+
async def test_receive_after_close(self, server_addr: tuple[str, int]) -> None:
stream = await connect_tcp(*server_addr)
await stream.aclose()
From d4a21afd972b1d72923172f220923d80029c523e Mon Sep 17 00:00:00 2001
From: Thomas Grainger
Date: Thu, 13 Aug 2026 10:41:14 +0100
Subject: [PATCH 6/7] Add concurrent aclose_forcefully() tests for UDP sockets
and Process
Extends the TCP regression test from #1277 to UDPSocket, ConnectedUDPSocket
and Process.
The plain variants pass on master too, so they are guards rather than
regression tests. The pending-send variants target Windows specifically:
_ProactorDatagramTransport.sendto() always buffers and starts an overlapped
operation, so transport.close() declines to schedule connection_lost while
_write_fut is pending, deferring it to an IOCP completion on a later loop
iteration. That should expose UDPSocket.aclose()'s unshielded wait on
closed_event, which is invisible on selector loops.
Pushed to run the Windows CI matrix; not intended for upstream as-is.
Co-Authored-By: Claude Opus 5 (1M context)
---
tests/test_sockets.py | 99 ++++++++++++++++++++++++++++++++++++++
tests/test_subprocesses.py | 18 +++++++
2 files changed, 117 insertions(+)
diff --git a/tests/test_sockets.py b/tests/test_sockets.py
index 683d4f9e1..95da0707d 100644
--- a/tests/test_sockets.py
+++ b/tests/test_sockets.py
@@ -1902,6 +1902,52 @@ async def close_when_blocked() -> None:
with pytest.raises(ClosedResourceError):
await udp.receive()
+ async def test_concurrent_aclose_forcefully_waits_for_socket_close(self) -> None:
+ udp = await create_udp_socket(
+ family=AddressFamily.AF_INET, local_host="localhost"
+ )
+ raw_socket = udp.extra(SocketAttribute.raw_socket)
+ file_descriptors: list[int] = []
+
+ async def close_socket() -> None:
+ await aclose_forcefully(udp)
+ file_descriptors.append(raw_socket.fileno())
+
+ async with create_task_group() as task_group:
+ task_group.start_soon(close_socket)
+ task_group.start_soon(close_socket)
+
+ assert file_descriptors == [-1, -1]
+
+ async def test_concurrent_aclose_forcefully_with_pending_send(self) -> None:
+ """The same, but with sends still in flight when the socket is closed.
+
+ On Windows' proactor loop, ``sendto()`` always buffers and starts an
+ overlapped operation, so ``transport.close()`` declines to schedule
+ ``connection_lost`` until the IOCP completion lands on a later event loop
+ iteration. That removes the single-callback margin that otherwise hides
+ ``aclose()``'s unshielded wait on the close event.
+ """
+ udp = await create_udp_socket(
+ family=AddressFamily.AF_INET, local_host="localhost"
+ )
+ host, port = cast(tuple[str, int], udp.extra(SocketAttribute.local_address))
+ raw_socket = udp.extra(SocketAttribute.raw_socket)
+ file_descriptors: list[int] = []
+
+ async def close_socket() -> None:
+ await aclose_forcefully(udp)
+ file_descriptors.append(raw_socket.fileno())
+
+ for _ in range(10):
+ await udp.sendto(b"x" * 1400, host, port)
+
+ async with create_task_group() as task_group:
+ task_group.start_soon(close_socket)
+ task_group.start_soon(close_socket)
+
+ assert file_descriptors == [-1, -1]
+
async def test_receive_after_close(self) -> None:
udp = await create_udp_socket(
family=AddressFamily.AF_INET, local_host="localhost"
@@ -2085,6 +2131,59 @@ async def close_when_blocked() -> None:
with pytest.raises(ClosedResourceError):
await udp.receive()
+ async def test_concurrent_aclose_forcefully_waits_for_socket_close(self) -> None:
+ udp = await create_connected_udp_socket(
+ "localhost", 5000, local_host="localhost", family=AddressFamily.AF_INET
+ )
+ raw_socket = udp.extra(SocketAttribute.raw_socket)
+ file_descriptors: list[int] = []
+
+ async def close_socket() -> None:
+ await aclose_forcefully(udp)
+ file_descriptors.append(raw_socket.fileno())
+
+ async with create_task_group() as task_group:
+ task_group.start_soon(close_socket)
+ task_group.start_soon(close_socket)
+
+ assert file_descriptors == [-1, -1]
+
+ async def test_concurrent_aclose_forcefully_with_pending_send(self) -> None:
+ """The same, but with sends still in flight when the socket is closed.
+
+ See ``TestUDPSocket.test_concurrent_aclose_forcefully_with_pending_send``
+ for why an in-flight send matters. A real peer is bound here so that the
+ datagrams don't provoke an ICMP port unreachable, which would break the
+ connected socket instead.
+ """
+ peer = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
+ peer.bind(("127.0.0.1", 0))
+ try:
+ peer_host, peer_port = peer.getsockname()
+ udp = await create_connected_udp_socket(
+ peer_host,
+ peer_port,
+ local_host="localhost",
+ family=AddressFamily.AF_INET,
+ )
+ raw_socket = udp.extra(SocketAttribute.raw_socket)
+ file_descriptors: list[int] = []
+
+ async def close_socket() -> None:
+ await aclose_forcefully(udp)
+ file_descriptors.append(raw_socket.fileno())
+
+ for _ in range(10):
+ await udp.send(b"x" * 1400)
+
+ async with create_task_group() as task_group:
+ task_group.start_soon(close_socket)
+ task_group.start_soon(close_socket)
+
+ assert file_descriptors == [-1, -1]
+ finally:
+ peer.close()
+
async def test_receive_after_close(self, family: AnyIPAddressFamily) -> None:
udp = await create_connected_udp_socket(
"localhost", 5000, local_host="localhost", family=family
diff --git a/tests/test_subprocesses.py b/tests/test_subprocesses.py
index a87f8b450..8dd41f9e9 100644
--- a/tests/test_subprocesses.py
+++ b/tests/test_subprocesses.py
@@ -2,6 +2,7 @@
import os
import platform
+import signal
import sys
from collections.abc import Callable
from pathlib import Path
@@ -17,6 +18,7 @@
CancelScope,
ClosedResourceError,
EndOfStream,
+ aclose_forcefully,
create_task_group,
fail_after,
open_process,
@@ -440,6 +442,22 @@ async def test_close_while_reading() -> None:
process.terminate()
+async def test_concurrent_aclose_forcefully_waits_for_process_exit() -> None:
+ process = await open_process([sys.executable, "-c", "import time; time.sleep(3)"])
+ expected_returncode = 1 if sys.platform == "win32" else -signal.SIGKILL
+ returncodes: list[int | None] = []
+
+ async def close_process() -> None:
+ await aclose_forcefully(process)
+ returncodes.append(process.returncode)
+
+ async with create_task_group() as task_group:
+ task_group.start_soon(close_process)
+ task_group.start_soon(close_process)
+
+ assert returncodes == [expected_returncode, expected_returncode]
+
+
async def test_wait_returns_on_process_exit_with_open_stdout() -> None:
"""
wait() should return once the process exits, rather than waiting for the piped
From 847e02f0f18d9559299df7a1e5bebb7d18d74408 Mon Sep 17 00:00:00 2001
From: Thomas Grainger
Date: Thu, 13 Aug 2026 11:03:17 +0100
Subject: [PATCH 7/7] Fix the pending-send tests to actually leave a send in
flight
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
The previous versions queued sends and then entered a task group, but
start_soon() only schedules — the children first run when the host yields at
__aexit__, and one event loop iteration is all the proactor needs to reap the
overlapped send. close() then took the no-pending-write path and the tests
passed on Windows for the wrong reason.
anyio puts send()'s checkpoint before transport.sendto(), so the operation is
only still in flight if nothing yields afterwards. Drop the pointless 10x send
loop (each iteration yielded, so the buffer drained every time) and close
directly after a single send, as PR #1246's own reproducer does.
Adds a concurrent variant gated on an Event so the second closer only runs once
the first has yielded inside aclose(), which is where its unshielded wait on
closed_event is exposed. Neither PR covers that caller.
Co-Authored-By: Claude Opus 5 (1M context)
---
tests/test_sockets.py | 82 ++++++++++++++++++++++++++++---------------
1 file changed, 54 insertions(+), 28 deletions(-)
diff --git a/tests/test_sockets.py b/tests/test_sockets.py
index 6b5d54ba1..7a15ca37a 100644
--- a/tests/test_sockets.py
+++ b/tests/test_sockets.py
@@ -1939,32 +1939,66 @@ async def close_socket() -> None:
assert file_descriptors == [-1, -1]
- async def test_concurrent_aclose_forcefully_with_pending_send(self) -> None:
- """The same, but with sends still in flight when the socket is closed.
+ async def test_aclose_forcefully_with_pending_send(self) -> None:
+ """Force-close with an overlapped send still in flight.
- On Windows' proactor loop, ``sendto()`` always buffers and starts an
+ On Windows' proactor loop ``sendto()`` always buffers and starts an
overlapped operation, so ``transport.close()`` declines to schedule
- ``connection_lost`` until the IOCP completion lands on a later event loop
- iteration. That removes the single-callback margin that otherwise hides
- ``aclose()``'s unshielded wait on the close event.
+ ``connection_lost`` while ``_write_fut`` is pending
+ (``proactor_events.py:108``), and ``_loop_writing()`` then bails out at
+ ``if self._conn_lost: return`` — which ``close()`` just incremented — so
+ ``connection_lost`` is never called at all and the wait on
+ ``closed_event`` blocks forever.
+
+ There must be no checkpoint between the send and the close: anyio puts
+ ``send()``'s checkpoint *before* ``transport.sendto()``, so the overlapped
+ operation is only still in flight if nothing yields afterwards. A single
+ event loop iteration is enough for the proactor to reap it.
+ """
+ udp = await create_udp_socket(
+ family=AddressFamily.AF_INET, local_host="localhost"
+ )
+ host, port = cast(tuple[str, int], udp.extra(SocketAttribute.local_address))
+ raw_socket = udp.extra(SocketAttribute.raw_socket)
+
+ await udp.sendto(b"x", host, port)
+ with fail_after(5):
+ await aclose_forcefully(udp)
+
+ assert raw_socket.fileno() == -1
+
+ async def test_concurrent_aclose_forcefully_with_pending_send(self) -> None:
+ """A second force-close arriving while the first is still in progress.
+
+ ``aclose()`` waits on ``closed_event`` unshielded, and the second caller
+ skips the ``is_closing()`` block entirely, so it never aborts the
+ transport — it only waits. This checks that it still doesn't return
+ before the FD has been released.
"""
udp = await create_udp_socket(
family=AddressFamily.AF_INET, local_host="localhost"
)
host, port = cast(tuple[str, int], udp.extra(SocketAttribute.local_address))
raw_socket = udp.extra(SocketAttribute.raw_socket)
+ first_close_started = Event()
file_descriptors: list[int] = []
async def close_socket() -> None:
+ await first_close_started.wait()
await aclose_forcefully(udp)
file_descriptors.append(raw_socket.fileno())
- for _ in range(10):
- await udp.sendto(b"x" * 1400, host, port)
+ with fail_after(5):
+ async with create_task_group() as task_group:
+ task_group.start_soon(close_socket)
+ await wait_all_tasks_blocked()
- async with create_task_group() as task_group:
- task_group.start_soon(close_socket)
- task_group.start_soon(close_socket)
+ # No checkpoint from here on, so the send is still in flight and
+ # the second closer only runs once this one yields inside aclose().
+ await udp.sendto(b"x", host, port)
+ first_close_started.set()
+ await aclose_forcefully(udp)
+ file_descriptors.append(raw_socket.fileno())
assert file_descriptors == [-1, -1]
@@ -2176,13 +2210,13 @@ async def close_socket() -> None:
assert file_descriptors == [-1, -1]
- async def test_concurrent_aclose_forcefully_with_pending_send(self) -> None:
- """The same, but with sends still in flight when the socket is closed.
+ async def test_aclose_forcefully_with_pending_send(self) -> None:
+ """Force-close with an overlapped send still in flight.
- See ``TestUDPSocket.test_concurrent_aclose_forcefully_with_pending_send``
- for why an in-flight send matters. A real peer is bound here so that the
- datagrams don't provoke an ICMP port unreachable, which would break the
- connected socket instead.
+ See ``TestUDPSocket.test_aclose_forcefully_with_pending_send`` for why an
+ in-flight send matters and why nothing may yield between the send and the
+ close. A real peer is bound here so the datagram doesn't provoke an ICMP
+ port unreachable, which would break the connected socket instead.
"""
peer = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
peer.bind(("127.0.0.1", 0))
@@ -2195,20 +2229,12 @@ async def test_concurrent_aclose_forcefully_with_pending_send(self) -> None:
family=AddressFamily.AF_INET,
)
raw_socket = udp.extra(SocketAttribute.raw_socket)
- file_descriptors: list[int] = []
- async def close_socket() -> None:
+ await udp.send(b"x")
+ with fail_after(5):
await aclose_forcefully(udp)
- file_descriptors.append(raw_socket.fileno())
-
- for _ in range(10):
- await udp.send(b"x" * 1400)
-
- async with create_task_group() as task_group:
- task_group.start_soon(close_socket)
- task_group.start_soon(close_socket)
- assert file_descriptors == [-1, -1]
+ assert raw_socket.fileno() == -1
finally:
peer.close()