https://github.com/python/cpython/commit/7738d7c1d4464847d648be4b682fe5f734d87537
commit: 7738d7c1d4464847d648be4b682fe5f734d87537
branch: 3.15
author: Miss Islington (bot) <[email protected]>
committer: hugovk <[email protected]>
date: 2026-09-07T15:34:16+03:00
summary:

[3.15] gh-156512: Fix asyncio calling connection_lost() twice from 
resume_writing() (GH-156657) (#157031)

Co-authored-by: Timofei Ivankov <[email protected]>

files:
A Misc/NEWS.d/next/Library/2026-08-30-13-25-53.gh-issue-156512.tVTOIV.rst
M Lib/asyncio/selector_events.py
M Lib/test/test_asyncio/test_selector_events.py

diff --git a/Lib/asyncio/selector_events.py b/Lib/asyncio/selector_events.py
index 54e9564aa1f2b3e..326d64025f36857 100644
--- a/Lib/asyncio/selector_events.py
+++ b/Lib/asyncio/selector_events.py
@@ -1124,7 +1124,9 @@ def _write_sendmsg(self):
                 self._loop._remove_writer(self._sock_fd)
                 if self._empty_waiter is not None:
                     self._empty_waiter.set_result(None)
-                if self._closing:
+                # gh-156512: don't let _call_connection_lost be called twice
+                if self._closing and not self._conn_lost:
+                    self._conn_lost += 1
                     self._call_connection_lost(None)
                 elif self._eof:
                     self._sock.shutdown(socket.SHUT_WR)
@@ -1170,7 +1172,9 @@ def _write_send(self):
                 self._loop._remove_writer(self._sock_fd)
                 if self._empty_waiter is not None:
                     self._empty_waiter.set_result(None)
-                if self._closing:
+                # gh-156512: don't let _call_connection_lost be called twice
+                if self._closing and not self._conn_lost:
+                    self._conn_lost += 1
                     self._call_connection_lost(None)
                 elif self._eof:
                     self._sock.shutdown(socket.SHUT_WR)
diff --git a/Lib/test/test_asyncio/test_selector_events.py 
b/Lib/test/test_asyncio/test_selector_events.py
index 4bb5d4fb816a9a6..27b3af888b74385 100644
--- a/Lib/test/test_asyncio/test_selector_events.py
+++ b/Lib/test/test_asyncio/test_selector_events.py
@@ -1122,6 +1122,49 @@ def _resume_writing():
         self.assertEqual(transport.get_write_buffer_size(), 0)
         self.assertTrue(self.protocol.connection_lost.called)
 
+    def test_write_ready_resume_writing_closes(self):
+        # gh-156512: closing from resume_writing() must not lose the 
connection twice
+        self.sock.send.return_value = 2
+
+        def _resume_writing():
+            transport.close()
+
+        self.protocol.resume_writing.side_effect = _resume_writing
+        self.loop.call_exception_handler = mock.Mock()
+
+        transport = self.socket_transport()
+        transport.set_write_buffer_limits(high=1, low=0)
+        transport.write(b'data')
+
+        self.loop.writers[7]._run()
+        test_utils.run_briefly(self.loop)
+
+        self.assertEqual(self.protocol.connection_lost.call_count, 1)
+        self.loop.call_exception_handler.assert_not_called()
+
+    @unittest.skipUnless(selector_events._HAS_SENDMSG, 'no sendmsg')
+    def test_write_sendmsg_resume_writing_closes(self):
+        # gh-156512: same as above, for the sendmsg write path
+        self.sock.send.return_value = 2
+        self.sock.sendmsg.return_value = 2
+
+        def _resume_writing():
+            transport.close()
+
+        self.protocol.resume_writing.side_effect = _resume_writing
+        self.loop.call_exception_handler = mock.Mock()
+
+        transport = self.socket_transport(sendmsg=True)
+        transport.set_write_buffer_limits(high=1, low=0)
+        transport.write(b'data')
+
+        self.loop.writers[7]._run()
+        test_utils.run_briefly(self.loop)
+
+        self.assertEqual(self.protocol.connection_lost.call_count, 1)
+        self.loop.call_exception_handler.assert_not_called()
+
+
 class SelectorSocketTransportBufferedProtocolTests(test_utils.TestCase):
 
     def setUp(self):
diff --git 
a/Misc/NEWS.d/next/Library/2026-08-30-13-25-53.gh-issue-156512.tVTOIV.rst 
b/Misc/NEWS.d/next/Library/2026-08-30-13-25-53.gh-issue-156512.tVTOIV.rst
new file mode 100644
index 000000000000000..16d6b7bdeae9859
--- /dev/null
+++ b/Misc/NEWS.d/next/Library/2026-08-30-13-25-53.gh-issue-156512.tVTOIV.rst
@@ -0,0 +1,2 @@
+Fix :mod:`asyncio` losing a connection twice when ``resume_writing()``
+closes 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]

Reply via email to