FazBrowse GitHub Viewer | Trending |
URL:
| Home
Tools: [Download Repo ZIP]   [Original HTTPS Page]

[3.15] gh-74112: Make Ctrl-C in the IDLE Shell interrupt blocking calls (GH-157662) by miss-islington · Pull Request #158917 · python/cpython · GitHub

Repository navigation

Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension .py  (4) .rst  (1) All 2 file types selected
Viewed files
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Unified
Split
Hide whitespace
Diff view
Unified
Split
Hide whitespace
14 changes: 14 additions & 0 deletions Lib/idlelib/idle_test/test_rpc.py
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode characters
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,9 @@
from idlelib import rpc
import socket
import struct
import threading
import unittest
from unittest import mock


class SocketIOTest(unittest.TestCase):
Expand All @@ -22,6 +24,18 @@ def test_reconnect_discards_partial_packet(self):
new_peer.sendall(struct.pack('<i', 3) + b'abc')
self.assertEqual(sockio.pollpacket(1), b'abc')

def test_getresponse_interrupted(self):
# gh-74112: an interrupted wait must release the lock and forget
# the sequence number, so that a late response is discarded.
sockio = rpc.SocketIO(mock.Mock(), debugging=False)
sockio.sockthread = None # Not the current thread.
cvar = sockio.cvars[7] = threading.Condition()
with mock.patch.object(cvar, 'wait', side_effect=KeyboardInterrupt):
with self.assertRaises(KeyboardInterrupt):
sockio._getresponse(7, 0.05)
self.assertNotIn(7, sockio.cvars)
self.assertTrue(cvar.acquire(blocking=False))
cvar.release()


class CodePicklerTest(unittest.TestCase):
Expand Down
34 changes: 34 additions & 0 deletions Lib/idlelib/idle_test/test_run.py
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode characters
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,11 @@

from idlelib import run
import io
import signal
import sys
import threading
import time
from test import support
from test.support import captured_output, captured_stderr
import unittest
from unittest import mock
Expand Down Expand Up @@ -522,5 +526,35 @@ def test_exceptions(self):
self.assertTrue(isinstance(e.__context__, ZeroDivisionError))


class InterruptTest(unittest.TestCase):

def setUp(self):
self.ex = run.Executive(mock.Mock(sendlock=threading.Lock()))
self.addCleanup(setattr, run, 'interruptible', run.interruptible)
run.interruptible = True

@unittest.skipIf(signal.getsignal(signal.SIGINT)
in (signal.SIG_DFL, signal.SIG_IGN, None),
'SIGINT is not handled by Python')
def test_interrupt_blocking_call(self):
# gh-74112: interrupt the main thread blocked in time.sleep().
timer = threading.Timer(0.1, self.ex.interrupt_the_server)
self.addCleanup(timer.join)
start = time.monotonic()
with self.assertRaises(KeyboardInterrupt):
# On a loaded machine the signal can arrive before the main
# thread reaches time.sleep(), so start the timer in the block.
timer.start()
time.sleep(support.SHORT_TIMEOUT)
self.assertLess(time.monotonic() - start, support.SHORT_TIMEOUT / 2)

def test_interrupt_ignored(self):
old_handler = signal.signal(signal.SIGINT, signal.SIG_IGN)
self.addCleanup(signal.signal, signal.SIGINT, old_handler)
with mock.patch.object(run.thread, 'interrupt_main') as interrupt_main:
self.ex.interrupt_the_server()
interrupt_main.assert_called_once_with()


if __name__ == '__main__':
unittest.main(verbosity=2)
39 changes: 23 additions & 16 deletions Lib/idlelib/rpc.py
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode characters
Original file line number Diff line number Diff line change
Expand Up @@ -139,6 +139,7 @@ def __init__(self, sock, objtable=None, debugging=None):
self.objtable = objtable
self.responses = {}
self.cvars = {}
self.sendlock = threading.Lock()
# Receive buffer state. A new connection must not inherit a
# partially received packet from the old one (gh-89544).
self.buff = b''
Expand Down Expand Up @@ -319,15 +320,20 @@ def _getresponse(self, myseq, wait):
else:
# wait for notification from socket handling thread
cvar = self.cvars[myseq]
cvar.acquire()
while myseq not in self.responses:
cvar.wait()
response = self.responses[myseq]
self.debug("_getresponse:%s: thread woke up: response: %s" %
(myseq, response))
del self.responses[myseq]
del self.cvars[myseq]
cvar.release()
with cvar:
try:
while myseq not in self.responses:
cvar.wait()
except BaseException:
# Interrupted; a late response will be discarded.
del self.cvars[myseq]
self.responses.pop(myseq, None)
raise
response = self.responses[myseq]
self.debug("_getresponse:%s: thread woke up: response: %s" %
(myseq, response))
del self.responses[myseq]
del self.cvars[myseq]
return response

def newseq(self):
Expand All @@ -342,13 +348,14 @@ def putmessage(self, message):
print("Cannot pickle:", repr(message), file=sys.__stderr__)
raise
s = struct.pack("<i", len(s)) + s
while len(s) > 0:
try:
r, w, x = select.select([], [self.sock], [])
n = self.sock.send(s[:BUFSIZE])
except (AttributeError, TypeError):
raise OSError("socket no longer exists")
s = s[n:]
with self.sendlock:
while len(s) > 0:
try:
r, w, x = select.select([], [self.sock], [])
n = self.sock.send(s[:BUFSIZE])
except (AttributeError, TypeError):
raise OSError("socket no longer exists")
s = s[n:]

def pollpacket(self, wait):
self._stage0()
Expand Down
15 changes: 14 additions & 1 deletion Lib/idlelib/run.py
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode characters
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
import io
import linecache
import queue
import signal
import sys
import textwrap
import time
Expand Down Expand Up @@ -670,7 +671,19 @@ def runcode(self, code):

def interrupt_the_server(self):
if interruptible:
thread.interrupt_main()
handler = signal.getsignal(signal.SIGINT)
if handler not in (signal.SIG_DFL, signal.SIG_IGN, None):
# A real signal interrupts blocking calls such as
# time.sleep() (gh-74112). The lock prevents interrupting
# the main thread in the middle of sending a message.
with self.rpchandler.sendlock:
if hasattr(signal, 'pthread_kill'):
signal.pthread_kill(threading.main_thread().ident,
signal.SIGINT)
else:
signal.raise_signal(signal.SIGINT)
else:
thread.interrupt_main()

def start_the_debugger(self, gui_adap_oid):
return debugger_r.start_debugger(self.rpchandler, gui_adap_oid)
Expand Down
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode characters
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
Ctrl-C in the IDLE Shell now interrupts blocking calls such as
:func:`time.sleep` and :meth:`socket.recv <socket.socket.recv>`.
Loading

Back | FazBrowse Home | New Git URL