diff --git a/ipykernel/kernelbase.py b/ipykernel/kernelbase.py index a2991aea1..8c808566c 100644 --- a/ipykernel/kernelbase.py +++ b/ipykernel/kernelbase.py @@ -867,6 +867,7 @@ def _input_request(self, prompt, ident, parent, password=False): # Flush output before making the request. sys.stderr.flush() sys.stdout.flush() + # flush the stdin socket, to purge stale replies while True: try: @@ -885,14 +886,25 @@ def _input_request(self, prompt, ident, parent, password=False): # Await a response. while True: try: - ident, reply = self.session.recv(self.stdin_socket, 0) - except Exception: - self.log.warning("Invalid Message:", exc_info=True) + # Use polling with select() so KeyboardInterrupts can get + # through; doing a blocking recv() means stdin reads are + # uninterruptible on Windows. We need a timeout because + # zmq.select() is also uninterruptible, but at least this + # way reads get noticed immediately and KeyboardInterrupts + # get noticed fairly quickly by human response time standards. + rlist, _, xlist = zmq.select( + [self.stdin_socket], [], [self.stdin_socket], 0.01 + ) + if rlist or xlist: + ident, reply = self.session.recv(self.stdin_socket) + if (ident, reply) != (None, None): + break except KeyboardInterrupt: # re-raise KeyboardInterrupt, to truncate traceback raise KeyboardInterrupt("Interrupted by user") from None - else: - break + except Exception as e: + self.log.warning("Invalid Message:", exc_info=True) + try: value = py3compat.unicode_to_str(reply['content']['value']) except: diff --git a/ipykernel/tests/test_kernel.py b/ipykernel/tests/test_kernel.py index 219ded2d3..239f957f3 100644 --- a/ipykernel/tests/test_kernel.py +++ b/ipykernel/tests/test_kernel.py @@ -330,3 +330,21 @@ def test_shutdown(): else: break assert not km.is_alive() + + +def test_interrupt_during_input(): + """ + The kernel exits after being interrupted while waiting in input(). + + input() appears to have issues other functions don't, and it needs to be + interruptible in order for pdb to be interruptible. + """ + with new_kernel() as kc: + km = kc.parent + msg_id = kc.execute("input()") + time.sleep(1) # Make sure it's actually waiting for input. + km.interrupt_kernel() + # If we failed to interrupt interrupt, this will timeout: + reply = kc.get_shell_msg(timeout=TIMEOUT) + from .test_message_spec import validate_message + validate_message(reply, 'execute_reply', msg_id)