https://github.com/python/cpython/commit/b0dda158aad00f0064e220f882792a5bd16ba0a0
commit: b0dda158aad00f0064e220f882792a5bd16ba0a0
branch: main
author: Serhiy Storchaka <[email protected]>
committer: terryjreedy <[email protected]>
date: 2026-09-22T21:32:25-04:00
summary:

gh-89544: Fix IDLE hang after Restart Shell while receiving output (#157644)

RPCClient.accept() reinitializes the SocketIO for the new connection,
but the receive buffer kept the partially received packet from the old
connection, so no message from the new user process was ever complete.

files:
A Misc/NEWS.d/next/IDLE/2026-09-17-01-00-00.gh-issue-89544.rstbuf.rst
M Lib/idlelib/idle_test/test_rpc.py
M Lib/idlelib/rpc.py

diff --git a/Lib/idlelib/idle_test/test_rpc.py 
b/Lib/idlelib/idle_test/test_rpc.py
index 81eff398c72f45..15c3ed14b8f6f3 100644
--- a/Lib/idlelib/idle_test/test_rpc.py
+++ b/Lib/idlelib/idle_test/test_rpc.py
@@ -1,9 +1,28 @@
 "Test rpc, coverage 20%."
 
 from idlelib import rpc
+import socket
+import struct
 import unittest
 
 
+class SocketIOTest(unittest.TestCase):
+
+    def test_reconnect_discards_partial_packet(self):
+        # gh-89544: RPCClient.accept() reinitializes the SocketIO after
+        # a restart; a partially received packet must not be kept.
+        old_sock, old_peer = socket.socketpair()
+        new_sock, new_peer = socket.socketpair()
+        with old_sock, old_peer, new_sock, new_peer:
+            sockio = rpc.SocketIO(old_sock)
+            old_peer.sendall(struct.pack('<i', 100) + b'x' * 10)
+            self.assertIsNone(sockio.pollpacket(1))
+            sockio.close()
+            rpc.SocketIO.__init__(sockio, new_sock)
+            new_peer.sendall(struct.pack('<i', 3) + b'abc')
+            self.assertEqual(sockio.pollpacket(1), b'abc')
+
+
 
 class CodePicklerTest(unittest.TestCase):
 
diff --git a/Lib/idlelib/rpc.py b/Lib/idlelib/rpc.py
index 3f0b2230dd185d..9af9863d74e0b6 100644
--- a/Lib/idlelib/rpc.py
+++ b/Lib/idlelib/rpc.py
@@ -139,6 +139,11 @@ def __init__(self, sock, objtable=None, debugging=None):
         self.objtable = objtable
         self.responses = {}
         self.cvars = {}
+        # Receive buffer state.  A new connection must not inherit a
+        # partially received packet from the old one (gh-89544).
+        self.buff = b''
+        self.bufneed = 4
+        self.bufstate = 0 # meaning: 0 => reading count; 1 => reading data
 
     def close(self):
         sock = self.sock
@@ -345,10 +350,6 @@ def putmessage(self, message):
                 raise OSError("socket no longer exists")
             s = s[n:]
 
-    buff = b''
-    bufneed = 4
-    bufstate = 0 # meaning: 0 => reading count; 1 => reading data
-
     def pollpacket(self, wait):
         self._stage0()
         if len(self.buff) < self.bufneed:
diff --git 
a/Misc/NEWS.d/next/IDLE/2026-09-17-01-00-00.gh-issue-89544.rstbuf.rst 
b/Misc/NEWS.d/next/IDLE/2026-09-17-01-00-00.gh-issue-89544.rstbuf.rst
new file mode 100644
index 00000000000000..4a4c885eff9681
--- /dev/null
+++ b/Misc/NEWS.d/next/IDLE/2026-09-17-01-00-00.gh-issue-89544.rstbuf.rst
@@ -0,0 +1,2 @@
+Fix IDLE hanging after Restart Shell while receiving a large output from the
+user process.

_______________________________________________
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