From 1cdd242a25d18886b7e036d061665d5a3ef220c6 Mon Sep 17 00:00:00 2001 From: kn1g78 Date: Sat, 27 Jun 2026 23:20:01 +0800 Subject: [PATCH 1/2] asyncio: update StreamReader transport after StreamWriter.start_tls Keep StreamReader in sync with the upgraded transport after StreamWriter.start_tls(), and add a regression test for the transport replacement. --- Lib/asyncio/streams.py | 7 +++++++ Lib/test/test_asyncio/test_streams.py | 28 +++++++++++++++++++++++++++ 2 files changed, 35 insertions(+) diff --git a/Lib/asyncio/streams.py b/Lib/asyncio/streams.py index a28c11e928f806..d4867424098b69 100644 --- a/Lib/asyncio/streams.py +++ b/Lib/asyncio/streams.py @@ -216,6 +216,9 @@ def _stream_reader(self): def _replace_transport(self, transport): self._transport = transport self._over_ssl = transport.get_extra_info('sslcontext') is not None + reader = self._stream_reader + if reader is not None: + reader._replace_transport(transport) def connection_made(self, transport): if self._reject_connection: @@ -475,6 +478,10 @@ def set_transport(self, transport): assert self._transport is None, 'Transport already set' self._transport = transport + def _replace_transport(self, transport): + assert self._transport is not None, 'Transport not set' + self._transport = transport + def _maybe_resume_transport(self): if self._paused and len(self._buffer) <= self._limit: self._paused = False diff --git a/Lib/test/test_asyncio/test_streams.py b/Lib/test/test_asyncio/test_streams.py index fb774895a7e52a..911087a128f971 100644 --- a/Lib/test/test_asyncio/test_streams.py +++ b/Lib/test/test_asyncio/test_streams.py @@ -861,6 +861,34 @@ async def run_test(): self.loop.run_until_complete(run_test()) self.assertEqual(messages, []) + def test_streamwriter_start_tls_updates_reader_transport(self): + reader = asyncio.StreamReader(loop=self.loop) + protocol = asyncio.StreamReaderProtocol(reader, loop=self.loop) + old_transport = mock.Mock() + old_transport.get_extra_info.return_value = None + old_transport.is_closing.return_value = False + protocol.connection_made(old_transport) + + writer = asyncio.StreamWriter(old_transport, protocol, reader, self.loop) + + ssl_context = mock.sentinel.ssl_context + new_transport = mock.Mock() + new_transport.get_extra_info.return_value = ssl_context + self.loop.start_tls = mock.AsyncMock(return_value=new_transport) + + self.loop.run_until_complete(writer.start_tls(ssl_context)) + + self.loop.start_tls.assert_awaited_once_with( + old_transport, protocol, ssl_context, + server_side=False, server_hostname=None, + ssl_handshake_timeout=None, + ssl_shutdown_timeout=None, + ) + self.assertIs(writer.transport, new_transport) + self.assertIs(protocol._transport, new_transport) + self.assertIs(reader._transport, new_transport) + self.assertTrue(protocol._over_ssl) + def test_streamreader_constructor_without_loop(self): with self.assertRaisesRegex(RuntimeError, 'no current event loop'): asyncio.StreamReader() From 6d2274358b2857c5414b61220cef67eb4d82d2f7 Mon Sep 17 00:00:00 2001 From: kn1g78 Date: Sun, 28 Jun 2026 18:51:53 +0800 Subject: [PATCH 2/2] gh-152431: add news entry for asyncio start_tls fix --- .../next/Library/2026-06-28-00-00-00.gh-issue-152431.Ja3K9m.rst | 2 ++ 1 file changed, 2 insertions(+) create mode 100644 Misc/NEWS.d/next/Library/2026-06-28-00-00-00.gh-issue-152431.Ja3K9m.rst diff --git a/Misc/NEWS.d/next/Library/2026-06-28-00-00-00.gh-issue-152431.Ja3K9m.rst b/Misc/NEWS.d/next/Library/2026-06-28-00-00-00.gh-issue-152431.Ja3K9m.rst new file mode 100644 index 00000000000000..bda2dfd7cd9d89 --- /dev/null +++ b/Misc/NEWS.d/next/Library/2026-06-28-00-00-00.gh-issue-152431.Ja3K9m.rst @@ -0,0 +1,2 @@ +Fix ``asyncio.StreamWriter.start_tls()`` to keep the linked +``StreamReader`` transport in sync with the upgraded transport.