Eryk Sun <eryk...@gmail.com> added the comment:

> test_call_timeout() or test_timeout() in test_subprocess.py.

These tests don't override the standard files, and they only spawn a single 
child with no descendants. I don't see why this would hang. It shouldn't be a 
problem with leaked pipe handles (see bpo-43346). It probably will need to be 
diagnosed by attaching a debugger, or offline with a dump file.

> process trees whereas terminating a parent automatically kills the children

One can use a job object to manage a child process and all of its descendants, 
including resource usage and termination. A process can belong to multiple job 
objects in Windows 8+, which is required by Python 3.9+.

For reliability, the child has to be created in a suspended state via 
CREATE_SUSPENDED. It can be resumed with ResumeThread() after adding it to the 
job with AssignProcessToJobObject().

You can try to terminate a job cleanly, which is similar in effect to sending 
SIGTERM to a process group in POSIX. In Windows, this has to be approached 
differently for console vs graphical processes.

To handle console apps, assuming the child inherits the current console, spawn 
it as a new process group via 
creationflags=subprocess.CREATE_NEW_PROCESS_GROUP. You can request an exit by 
sending a Ctrl+Break event to the group via os.kill(p.pid, 
signal.CTRL_BREAK_EVENT) [1]. The request might be ignored, but typically the 
default handler is called, which calls ExitProcess().

To handle GUI apps, assuming the child inherits the current desktop (usually 
"WinSta0\Default"), first enumerate the top-level and message-only windows on 
the current desktop via EnumWindows() and FindWindowExW(). Use 
GetWindowThreadProcessId() to filter the list to include only windows that 
belong to the job. Post WM_CLOSE to each window in the job. A process might 
ignore a request to close. It could keep the window open or continue running in 
the background.

After an internal timeout, you can call TerminateJobObject() to kill any 
process in the job that remains alive. This is a forced and abrupt termination, 
which is similar to sending SIGKILL to a process group in POSIX.

---

[1] This usage of os.kill() is what we're stuck with. Rightfully, we should be 
using os.killpg(p.pid, signal.SIGBREAK) or os.kill(-p.pid, signal.SIGBREAK) 
(note the negative pid value).

----------

_______________________________________
Python tracker <rep...@bugs.python.org>
<https://bugs.python.org/issue46716>
_______________________________________
_______________________________________________
Python-bugs-list mailing list
Unsubscribe: 
https://mail.python.org/mailman/options/python-bugs-list/archive%40mail-archive.com

Reply via email to