https://github.com/python/cpython/commit/409f324f1d64d427f672d4d2fad5051b914d85a9
commit: 409f324f1d64d427f672d4d2fad5051b914d85a9
branch: main
author: Bhuvi <[email protected]>
committer: kumaraditya303 <[email protected]>
date: 2026-07-24T19:23:51+05:30
summary:
gh-145030: Fix asyncio write pipe transport for named FIFOs on macOS (#154222)
files:
A Misc/NEWS.d/next/Library/2026-07-20-12-40-00.gh-issue-145030.TFJm6k.rst
M Lib/asyncio/unix_events.py
M Lib/test/test_asyncio/test_events.py
diff --git a/Lib/asyncio/unix_events.py b/Lib/asyncio/unix_events.py
index 1a7d534261ed72..bb98f0014f8176 100644
--- a/Lib/asyncio/unix_events.py
+++ b/Lib/asyncio/unix_events.py
@@ -640,7 +640,8 @@ def __init__(self, loop, pipe, protocol, waiter=None,
extra=None):
self._conn_lost = 0
self._closing = False # Set when close() or write_eof() called.
- mode = os.fstat(self._fileno).st_mode
+ pipe_stat = os.fstat(self._fileno)
+ mode = pipe_stat.st_mode
is_char = stat.S_ISCHR(mode)
is_fifo = stat.S_ISFIFO(mode)
is_socket = stat.S_ISSOCK(mode)
@@ -657,7 +658,19 @@ def __init__(self, loop, pipe, protocol, waiter=None,
extra=None):
# On AIX, the reader trick (to be notified when the read end of the
# socket is closed) only works for sockets. On other platforms it
# works for pipes and sockets. (Exception: OS X 10.4? Issue #19294.)
- if is_socket or (is_fifo and not sys.platform.startswith("aix")):
+ # On macOS, the trick misfires for named FIFOs (but not for pipes
+ # created with os.pipe(), which have st_nlink == 0): the write end
+ # polls as readable whenever unread data sits in the FIFO, and no
+ # event is delivered when the read end is closed, so it can only
+ # ever report a false disconnection (gh-145030). The same xnu
+ # behaviour applies on iOS/tvOS/watchOS (sys.platform is not
+ # "darwin" there).
+ is_named_fifo_on_apple = (
+ sys.platform in {"darwin", "ios", "tvos", "watchos"}
+ and is_fifo and pipe_stat.st_nlink > 0)
+ if is_socket or (is_fifo
+ and not sys.platform.startswith("aix")
+ and not is_named_fifo_on_apple):
# only start reading when connection_made() has been called
self._loop.call_soon(self._loop._add_reader,
self._fileno, self._read_ready)
diff --git a/Lib/test/test_asyncio/test_events.py
b/Lib/test/test_asyncio/test_events.py
index 91882f41d9428e..3fdfffdb213efc 100644
--- a/Lib/test/test_asyncio/test_events.py
+++ b/Lib/test/test_asyncio/test_events.py
@@ -1725,6 +1725,48 @@ def reader(data):
self.loop.run_until_complete(proto.done)
self.assertEqual('CLOSED', proto.state)
+ @unittest.skipUnless(sys.platform != 'win32',
+ "Don't support pipes for Windows")
+ @unittest.skipUnless(hasattr(os, 'mkfifo'), 'requires os.mkfifo()')
+ def test_write_named_fifo_unread_data(self):
+ # gh-145030: on macOS, the write end of a named FIFO polls as
+ # readable while unread data sits in the FIFO, which made the
+ # transport misinterpret the event as the reader hanging up
+ # and close itself.
+ path = os_helper.TESTFN
+ os.mkfifo(path)
+ self.assertNotEqual(os.stat(path).st_nlink, 0)
+ self.addCleanup(os_helper.unlink, path)
+ rfd = os.open(path, os.O_RDONLY | os.O_NONBLOCK)
+ self.addCleanup(os.close, rfd)
+ wfd = os.open(path, os.O_WRONLY | os.O_NONBLOCK)
+ pipeobj = io.open(wfd, 'wb', 1024)
+
+ proto = MyWritePipeProto(loop=self.loop)
+ connect = self.loop.connect_write_pipe(lambda: proto, pipeobj)
+ transport, p = self.loop.run_until_complete(connect)
+ self.assertIs(p, proto)
+ self.assertEqual('CONNECTED', proto.state)
+
+ transport.write(b'1')
+ # Iterate the event loop while the data stays unread in the FIFO;
+ # the transport must not detect a false disconnection.
+ for _ in range(10):
+ test_utils.run_briefly(self.loop)
+ self.assertEqual('CONNECTED', proto.state)
+ self.assertFalse(transport.is_closing())
+ self.assertEqual(b'1', os.read(rfd, 1024))
+
+ transport.write(b'2345')
+ for _ in range(10):
+ test_utils.run_briefly(self.loop)
+ self.assertEqual('CONNECTED', proto.state)
+ self.assertEqual(b'2345', os.read(rfd, 1024))
+
+ transport.close()
+ self.loop.run_until_complete(proto.done)
+ self.assertEqual('CLOSED', proto.state)
+
@unittest.skipUnless(sys.platform != 'win32',
"Don't support pipes for Windows")
def test_write_pipe_disconnect_on_close(self):
diff --git
a/Misc/NEWS.d/next/Library/2026-07-20-12-40-00.gh-issue-145030.TFJm6k.rst
b/Misc/NEWS.d/next/Library/2026-07-20-12-40-00.gh-issue-145030.TFJm6k.rst
new file mode 100644
index 00000000000000..2c6b07a4fa8b76
--- /dev/null
+++ b/Misc/NEWS.d/next/Library/2026-07-20-12-40-00.gh-issue-145030.TFJm6k.rst
@@ -0,0 +1,3 @@
+Fix :mod:`asyncio` write pipe transports for named FIFOs on macOS. Unread
+data sitting in the FIFO made the transport misinterpret a poll event as
+the reader disconnecting, wrongly closing the transport.
_______________________________________________
Python-checkins mailing list -- [email protected]
To unsubscribe send an email to [email protected]
https://mail.python.org/mailman3//lists/python-checkins.python.org
Member address: [email protected]