4

Actually, there are two questions. However, I think they are closely related, so I ask them together. I use Python 2.7.10 32bit under Windows.

The first one is about this program:

import sys

sys.stdin.readline()
a = 1
b = 2
print 'hello'

When the program asks for input, if I press Ctrl-C, the program will be stopped by KeyboardInterrupt. However, this exception will happen in print 'hello', not in sys.stdin.readline().

The second one is about this program:

import sys

while True:
    sys.stdin.readline()

When I want to stop this program, I have to first press Ctrl-C, then press Enter or press Ctrl-C again.

Neither of the problems happens under Linux, and they are annoying when debugging. I will be grateful if someone can give an detailed explanation.

delphifirst
  • 1,781
  • 1
  • 14
  • 23
  • You might want to check [this question](http://stackoverflow.com/questions/1112343/how-do-i-capture-sigint-in-python) about SIGINT (`ctrl + c`) – MaTh Dec 10 '15 at 11:10
  • In Win 7+, a Ctrl+C event starts in conhost.exe and bounces to csrss.exe, which creates a remote thread in your process, starting at `CtrlRoutine` in kernel32.dll. This calls the registered control event handlers. Each C runtime in the process has a control handler that calls its registered `SIGINT` function, which will be set to Python's handler in the CRT used by Python. This call happens on a new thread, so Python's native handler just sets a flag for the main thread, which some time later will call the registered Python handler, such as the function that raises `KeyboardInterrupt`. – Eryk Sun Dec 12 '15 at 12:48
  • Linux implements signals natively in the kernel. So the underlying `read()` system call gets interrupted, and Python's native signal handler is executed on the main thread. Then when the `readline` implementation resumes, `getc` returns `EOF` and sets the `stdin` stream error state and `errno` is set to `EINTR`. Given this Python knows to check its signal flags and will call the registered `SIGINT` handler, such as the default handler that raises `KeyboardInterrupt`. It all works smoothly because POSIX systems are designed for signals, whereas they're non-native on Windows. – Eryk Sun Dec 12 '15 at 13:01
  • @eryksun Thanks very much. Can I understand it like this? When I press Ctrl+C in Windows, sys.stdin.readline() will return since the system call for getting console input is interrupted by Ctrl+C. At the same time, Windows creates another thread to inform python, and python set a flag. However, it may take some time to set this flag, and the main thread will continue to run during this time period. – delphifirst Dec 13 '15 at 08:58
  • 2
    Note that Windows console I/O is the rare case of a synchronized system call that can get interrupted, so the C runtime isn't designed to set `errno` to `EINTR` in its low-level `read` and `write` functions. It could in this case by checking for `ERROR_OPERATION_ABORTED`. Python's I/O implementation doesn't check for this either, except in the code used for reading input in the REPL and built-in `raw_input` and `input` (i.e. `PyOS_StdioReadline`). Python 3 uses a Windows event object to help with synchronization in this race condition, but it still has room for improvement. – Eryk Sun Dec 13 '15 at 13:29
  • The system call used for console I/O on Windows (i.e. IPC to the attached conhost process) is `NtRequestWaitReplyPort` (i.e. LPC port w/ shared memory) prior to Windows 8. Later versions use a kernel driver, condrv.sys, so it's no longer special cased but uses the normal I/O calls `NtReadFile`/ `NtWriteFile` (for `ReadFile` / `WriteFile`), and `NtDeviceIoControlFile` (for `ReadConsole` / `WriteConsole`). – Eryk Sun Dec 13 '15 at 13:40
  • 1
    I mention the above change in Windows 8+ because it introduced a bug in `ReadFile` and `WriteFile`. They no longer set `ERROR_OPERATION_ABORTED` when the operation is interrupted. Now Python's `PyOS_StdioReadline` can't distinguish end of file (`EOF`) from an interrupted read in Windows 8+. So Ctrl+C makes the REPL quit as if you entered Ctrl+Z. And `raw_input` and `input` set `EOFError`, which races with checking the `SIGINT` flag that was set by the Ctrl+C thread. The workaround would involve a complete rewrite to use the `ReadConsole` API instead. – Eryk Sun Dec 13 '15 at 13:51

0 Answers0