Windows Launcher mishandles CTRL-C
Nobody has claimed this yet.
- Dominant language
- Python
- Stars
- 2.9k
- Forks
- 1.4k
- Avg merge
- 1d 1h
- Merged PRs (30d)
- 1
Description
Originally reported by: acaron (Bitbucket: acaron, GitHub: acaron)
I've been experiencing this strange error case where CTRL-C is not properly forwarded, so I started looking into the launcher.c file to see if I can fix it. Turns out it can't be truly fixed (and I'll explain why shortly), but I noticed some other inconsitencies while I was looking at the code.
First, GenerateConsoleCtrlEvent() is called incorrectly. The first argument should be the event to generate, not the child PID (the 2nd argument designates the target process group). This value can only be CTRL_C_EVENT (0) or CTRL_BREAK_EVENT (1), so this call is no-op unless the child process PID is 0 or 1 (impossible on Windows). To confirm this, you can simply replace this line:
GenerateConsoleCtrlEvent(child_pid,0);
with this:
if (!GenerateConsoleCtrlEvent(child_pid,0)) {
fprintf(stderr, "failed to forward CTRL-C (error: %d).\n", GetLastError());
}
This will consistenly print "failed to forward CTRL-C (error: 87)." every time you press CTRL-C (87 is "invalid parameter").
Second, it should not be called at all. The CTRL-C event is automatically sent to all processes in the same process group, which means that the child process gets the CTRL-C even if you stop generating the event. In addition, if you fix the call to this:
GenerateConsoleCtrlEvent(control_type, 0);
and add a print statement in the console control handler, you will notice that both the launcher and the child process get the CTRL-C event more than once because the launcher is sending this signal to itself, which creates a quasi infinite feedback loop (the control handler is run in a special background thread so a race condition allows it to terminate after the launcher has been spamming itself for a while).
Third, the console control handler should return FALSE for control events it doesn't handle. The current implementation returns TRUE even if the event is not CTRL-C.
Last, if CreateProcessA() or GetExitCodeProcess() fail, the launcher returns 0 as the exit status which is misleading for the calling program.
Now, to get back to my original problem: if you use the launcher to start a Python script which stops when there is no more input, like this:
try:
line = sys.stdin.readline().strip()
while line:
# ...
line = sys.stdin.readline().strip()
except KeyboardInterrupt:
pass
finally:
print 'Cleaning up.'
and you terminate this script using CTRL-C, sometimes the KeyboardInterrupt exception is raised in the finally handler. AFAICT, there is a race condition that's caused by Windows: pressing CTRL-C shuts down the standard input, causing it to return an empty line before the CTRL-C event is propagated to all child processes in the process group. To confirm this, you can add a simple sleep after exhausting the standard input, which lets the time for the system to propagate the CTRL-C event and ensures Python's KeyboardInterrupt exception is raised before the program's shutdown sequence starts.
try:
line = sys.stdin.readline().strip()
while line:
# ...
line = sys.stdin.readline().strip()
# Ensure we get the CTRL-C events on Windows when launched
# through a distribute/setuptools wrapper executable.
if os.name == 'nt':
time.sleep(25)
except KeyboardInterrupt:
pass
finally:
print 'Cleaning up.'
AFAICT, there is no known way to fix this race condition in the setuptools launcher. However, it would be nice if this quirk was documented as a known problem.
I wrote a pair of C programs to investigate this issue. I'm attaching them in case someone wants to experiment with the errors I'm reporting.
Cheers,
André
Contributor guide
No contributing guide indexed for this repository
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
Research direction
Start in launcher.c by tracing the console control handler, GenerateConsoleCtrlEvent call, and CreateProcessA/GetExitCodeProcess error paths. Reproduce the Windows CTRL-C behavior with the reported launcher and attached C experiments, then verify that handled and unhandled events, exit statuses, and the documented stdin race behave as described.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- c, python
- Domain
- build-system, operating-systems, tooling
- Issue type
- Bug
- Difficulty
- 4/5
- Estimated time
- 3-5 days
- Activity status
- Stale
- Clarity
- Mostly clear
- Newbie friendliness
- 35/100