https://github.com/python/cpython/commit/aa0aa314addfec922fb243605c0a636700dc4966
commit: aa0aa314addfec922fb243605c0a636700dc4966
branch: 3.13
author: Kumar Aditya <[email protected]>
committer: kumaraditya303 <[email protected]>
date: 2026-03-01T00:23:27+05:30
summary:

[3.13] gh-142352: Fix asyncio start_tls() to transfer buffered data from 
StreamReader (GH-142354) (#145364)

[3.13] gh-142352: Fix `asyncio` `start_tls()` to transfer buffered data from 
StreamReader (GH-142354)
(cherry picked from commit 0598f4a8999b96409e0a2bf9c480afc76a876860)

Co-authored-by: Maksym Kasimov <[email protected]>

files:
A Misc/NEWS.d/next/Library/2025-12-06-16-14-18.gh-issue-142352.pW5HLX88.rst
M Lib/asyncio/base_events.py
M Lib/test/test_asyncio/test_streams.py

diff --git a/Lib/asyncio/base_events.py b/Lib/asyncio/base_events.py
index 356fc3d7913ed3..84cad10636feef 100644
--- a/Lib/asyncio/base_events.py
+++ b/Lib/asyncio/base_events.py
@@ -1348,6 +1348,17 @@ async def start_tls(self, transport, protocol, 
sslcontext, *,
         # have a chance to get called before "ssl_protocol.connection_made()".
         transport.pause_reading()
 
+        # gh-142352: move buffered StreamReader data to SSLProtocol
+        if server_side:
+            from .streams import StreamReaderProtocol
+            if isinstance(protocol, StreamReaderProtocol):
+                stream_reader = getattr(protocol, '_stream_reader', None)
+                if stream_reader is not None:
+                    buffer = stream_reader._buffer
+                    if buffer:
+                        ssl_protocol._incoming.write(buffer)
+                        buffer.clear()
+
         transport.set_protocol(ssl_protocol)
         conmade_cb = self.call_soon(ssl_protocol.connection_made, transport)
         resume_cb = self.call_soon(transport.resume_reading)
diff --git a/Lib/test/test_asyncio/test_streams.py 
b/Lib/test/test_asyncio/test_streams.py
index 3040ca55fae0e9..f688c12649e543 100644
--- a/Lib/test/test_asyncio/test_streams.py
+++ b/Lib/test/test_asyncio/test_streams.py
@@ -868,6 +868,48 @@ def test_read_all_from_pipe_reader(self):
         data = self.loop.run_until_complete(reader.read(-1))
         self.assertEqual(data, b'data')
 
+    @unittest.skipIf(ssl is None, 'No ssl module')
+    def test_start_tls_buffered_data(self):
+        # gh-142352: test start_tls() with buffered data
+
+        async def server_handler(client_reader, client_writer):
+            # Wait for TLS ClientHello to be buffered before start_tls().
+            await client_reader._wait_for_data('test_start_tls_buffered_data'),
+            self.assertTrue(client_reader._buffer)
+            await 
client_writer.start_tls(test_utils.simple_server_sslcontext())
+
+            line = await client_reader.readline()
+            self.assertEqual(line, b"ping\n")
+            client_writer.write(b"pong\n")
+            await client_writer.drain()
+            client_writer.close()
+            await client_writer.wait_closed()
+
+        async def client(addr):
+            reader, writer = await asyncio.open_connection(*addr)
+            await writer.start_tls(test_utils.simple_client_sslcontext())
+
+            writer.write(b"ping\n")
+            await writer.drain()
+            line = await reader.readline()
+            self.assertEqual(line, b"pong\n")
+            writer.close()
+            await writer.wait_closed()
+
+        async def run_test():
+            server = await asyncio.start_server(
+                server_handler, socket_helper.HOSTv4, 0)
+            server_addr = server.sockets[0].getsockname()
+
+            await client(server_addr)
+            server.close()
+            await server.wait_closed()
+
+        messages = []
+        self.loop.set_exception_handler(lambda loop, ctx: messages.append(ctx))
+        self.loop.run_until_complete(run_test())
+        self.assertEqual(messages, [])
+
     def test_streamreader_constructor_without_loop(self):
         with self.assertRaisesRegex(RuntimeError, 'no current event loop'):
             asyncio.StreamReader()
diff --git 
a/Misc/NEWS.d/next/Library/2025-12-06-16-14-18.gh-issue-142352.pW5HLX88.rst 
b/Misc/NEWS.d/next/Library/2025-12-06-16-14-18.gh-issue-142352.pW5HLX88.rst
new file mode 100644
index 00000000000000..13e38b118175b4
--- /dev/null
+++ b/Misc/NEWS.d/next/Library/2025-12-06-16-14-18.gh-issue-142352.pW5HLX88.rst
@@ -0,0 +1,4 @@
+Fix :meth:`asyncio.StreamWriter.start_tls` to transfer buffered data from
+:class:`~asyncio.StreamReader` to the SSL layer, preventing data loss when
+upgrading a connection to TLS mid-stream (e.g., when implementing PROXY
+protocol support).

_______________________________________________
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