Right, this is extremely obscure...
So on Windows, when you hit control-C to interrupt a console program, this sends a CTRL_C_EVENT
to the process. You can also do this manually via GenerateConsoleCtrlEvent
. In Python, os.kill
acts as a wrapper around the C-level GenerateConsoleCtrlEvent
, and allows us to send a CTRL_C_EVENT
to the current process by doing:
os.kill(os.getpid(), signal.CTRL_C_EVENT)
However, this doesn't just go to the current process – it actually goes to the whole "process group" that this process is a part of.
I have a test suite which calls os.kill
like you see above, as part of some tests to make sure my program's control-C handling works correctly. When running this test suite on appveyor, though, this causes a problem, because apparently some of appveyor's infrastructure is in the same "progress group" and gets broken.
The solution is that we need to spawn the test suite with the CREATE_NEW_PROCESS_GROUP
flag set, so that its CTRL_C_EVENT
s don't "leak" to the parent. That's easily done. BUT...
If I use CREATE_NEW_PROCESS_GROUP
and run the child script using python whatever.py
, then it works as expected: the CTRL_C_EVENT
is confined to the child.
If I use CREATE_NEW_PROCESS_GROUP
and run the child script using py whatever.py
(i.e., using the python launcher, which is supposed to be equivalent to running python
directly), then the CREATE_NEW_PROCESS_GROUP
seems not to have any effect: the CTRL_C_EVENT
affects the parent as well!
Here's a minimal sample program that just uses os.kill
on itself and then checks that it worked (minor wrinkle: CREATE_NEW_PROCESS_GROUP
sets CTRL_C_EVENT
to be ignored in child processes, so there's a bit of fluff here using SetConsoleCtrlHandler
to un-ignore it):
https://github.com/njsmith/appveyor-ctrl-c-test/blob/master/a.py
Here's the wrapper script I use to run the above program: https://github.com/njsmith/appveyor-ctrl-c-test/blob/master/run-a.py
If the wrapper script runs python a.py
, then everything works. If the wrapper script runs py a.py
, then the wrapper script receives a KeyboardInterrupt
.
So my question is: what the heck is going on here? What is the py
launcher doing differently from python
that causes the CTRL_C_EVENT
to "leak" into the parent process, even though it's in a different process group? And how is that even possible?
(I originally discovered this because running pytest a.py
acts like py a.py
, i.e. is broken, but python -m pytest a.py
works, presumably because the pytest
entry point uses the py
launcher.)