Python - Windows - Exiting Child Process when “unr

2019-07-27 04:51发布

问题:

After some research there was no definitive answer as to how to have a child process understand that the parent process has died/crashed/exited under Windows which can make the child process run unattended. There are some proposals as in:

  • Jobs: Python: how to kill child process(es) when parent dies?

  • Psutil: subprocess: deleting child processes in Windows

  • Multiprocessing: Kill Child Process if Parent is killed in Python

Always involving a known parent which has started a child. But there are cases in which the child doesn't know it is a child, because it is not conceived as a child and the parent makes no effort to kill the children.

Furthermore, there is no control of the parent. Practical case:

  • Cygwin running under Windows
  • Windows Python 1st in the path
  • Python executable installed via setuptools entry_points facility.

As mentioned above the Python to be executed is the Windows one. The setuptools-generated executable will find it and execute it as subprocess with the asocciated script.

Because one is running under Cygwin the following may fail:

  • Pressing Ctrl-c will kill the parent (the stub setuptools executable)
  • But will leave the child running (to be found in the process list as python.exe)

In this case and as mentioned above, it isn't possible to control the parent and the child doesn't know it's a child (because it may also be directly executed as a Python script)

回答1:

The solution is to do as follows

import sys

def win_wait_for_parent(raise_exceptions=False):
    if not sys.platform == 'win32':
        return True

    # When started under cygwin, the parent process will die leaving a child
    # hanging around. The process has to be waited upon
    import ctypes
    from ctypes.wintypes import DWORD, BOOL, HANDLE
    import os
    import threading

    INFINITE = -1
    SYNCHRONIZE = 0x00100000

    kernel32 = ctypes.WinDLL('kernel32', use_last_error=True)

    kernel32.OpenProcess.argtypes = (DWORD, BOOL, DWORD)
    kernel32.OpenProcess.restype = HANDLE

    kernel32.WaitForSingleObject.argtypes = (HANDLE, DWORD)
    kernel32.WaitForSingleObject.restype = DWORD

    phandle = kernel32.OpenProcess(SYNCHRONIZE, 0, os.getppid())

    def check_parent():
        # Get a token with right access to parent and wait for it to be
        # signaled (die). Exit ourselves then
            kernel32.WaitForSingleObject(phandle, INFINITE)
            os._exit(0)

    if not phandle:
        if raise_exceptions:
            raise ctypes.WinError(ctypes.get_last_error())

        return False

    threading.Thread(target=check_parent).start()
    return True

which runs in a separate thread if the PID of the process is not the same as the PID of the parent waiting for the parent to be signaled (death). This works under Python 3.3, where os.getppid() does actually returnt the PID of the parent under window

It requires no modification of the parent and the child doesn't need to be coded in advance as a child, because a check is done as to where the thread has to run or no.

-- refactored as a function and added improvements from comments