How would I handle terminating a process on exit

Because this isn’t working. What I’m trying to do is catch the exit and terminate a multiprocessing.Process and then exiting the script. I’m doing this 'cause process_wrapper is stopping the script from closing. It hangs and I have to click the close button a second time.

I’m thinking that both of these are being ran in their own threads.

@atexit.register
def exit_handler():
    if process_wrapper is not None:
        process_wrapper .terminate()
    Keyboard.stop()


def kill_handler(*args):  # noqa
    if process_wrapper is not None:
        process_wrapper .terminate()
    Keyboard.stop()
    sys.exit(0)


signal(SIGTERM, kill_handler)
signal(SIGINT, kill_handler)

try to call kill() instead of terminate. Btw, what does that process do? I mean, does it execute some external program, or is it just something you have made? If it’s external program, it probably has a handle for SIGTERM and that’s what you are seeing

It is a wrapper for TightVNC to check if I’m connected to my notebook. When I disconnect it shuts off the display. The script has a system try icon so if for any reason I get on the computer and can double click the icon in the system try and shut off my display.

The multiprocessing.Process runs a while loop.

It is using psutil.Process to watch TightVNC.

I forgot to mention that process_wrapper is returning None in both of those functions even though it is still running.

how do you initialize it/create the process and where?

This isn’t all of the code.

Process_Wrapper: Optional[processing.Process] = None


def start():
    global Process_Wrapper
    
    if get_object(object_name = 'tvnserver.exe', parent_name = 'services.exe', object_type = psutil.Process) is None:
        print('This is a shell program for TightVNC Server.\nTightVNC Server must be running to use this script.')
        exit()

    Reciver, Sender = Pipe(duplex = True)
    Process_Wrapper = processing.Process(
        name = 'shell',
        args = (Reciver, ),
        target = wrapper_shell
    )
    Process_Wrapper.start()

    ICON = Icon(
        name = 'TightVNC Service',
        title = 'TightVNC Service',
        icon = Image.open(r'TightVNC Service.ico'),
        menu = Menu(
            MenuItem(
                text = 'Power Off Displays',
                action = lambda: Sender.send(True),
                visible = False,
                default = True
            ),
            MenuItem(
                text = 'Exit',
                action = lambda: close()
            )
        )
    )
    ICON.run()


if __name__ == '__main__':
    start()

I have a problem with both SendMessage, and PostMessage hanging in Windows 11 so I created another process that I could run long enough to shut off the display and then terminate it.

def wrapper_shell(reciver: Optional[PipeConnection] = None):
    RESET: bool = False  # noqa
    TightVNC: Optional[psutil.Process] = get_object(object_name = 'tvnserver.exe', parent_name = 'services.exe', object_type = psutil.Process)
    if TightVNC is None:
        return

    while True:
        RESET = False
        if not TightVNC.is_running():
            break

        if not len(TightVNC.children()) and not len(active_children()):
            processing.Process(target = power_saving).start()
            sleep(1)
            active_children()[0].terminate()

        while not len(TightVNC.children()) and not RESET:
            if reciver is not None and reciver.poll(timeout = 0):
                RESET = reciver.recv()
            sleep(1)
        sleep(1)


def power_saving():
    SendMessage(HWND_BROADCAST, WM_SYSCOMMAND, SC_MONITORPOWER, 2)  # off

I tried to use multiprocessing.active_children to close any child processes but it didn’t work so I gave the process a name so I cansee if it was returning None in those two functions.

The process_wrapper in your signal handler and Process_Wrapper in your main are spelled differently. Is this just how you pasted the code here or is it like that in your code? Different case means it’s a different variable. Since you are not getting an exception, I am assuming the spelling is correct.

Are signal handlers located in a different file/module by any chance? If they are indeed getting None, it seems they are reading the initial value of the Process_Wrapper, not the one after process spawning. And it’s better to check using Process_Wrapper.is_alive() if the process is still running.

I figured out what is going on and why everything is coming back None. It finally clicked to try multiprocessing.current_process().name. Somehow both of the functions are inside the Process and when I stop the script both the functions are being ran inside the Process.

@register
def exit_handler():
    print(f'current_process().name == {processing.current_process().name}')
    global ICON, KEYBOARD, RECIEVER, SENDER

    print(f'ICON is None == {ICON is None}')
    print(f'SENDER is None == {SENDER is None}')
    print(f'RECIEVER is None == {ICON is None}')
    print(f'KEYBOARD is None == {KEYBOARD is None}')
    for process in processing.active_children():
        process.terminate()
    KEYBOARD.stop()
    print()


def kill_handler(*args):  # noqa
    print(f'current_process().name == {processing.current_process().name}')
    global ICON, KEYBOARD, RECIEVER, SENDER

    print(f'ICON is None == {ICON is None}')
    print(f'SENDER is None == {SENDER is None}')
    print(f'RECIEVER is None == {ICON is None}')
    print(f'KEYBOARD is None == {KEYBOARD is None}')
    for process in processing.active_children():
        process.terminate()
    KEYBOARD.stop()
    print()
    sys.exit(0)


signal(SIGTERM, kill_handler)
signal(SIGINT, kill_handler)

>>> current_process().name == wrapper_shell
>>> ICON is None == True
>>> SENDER is None == True
>>> RECIEVER is None == True
>>> KEYBOARD is None == False
>>> 
>>> current_process().name == wrapper_shell
>>> ICON is None == True
>>> SENDER is None == True
>>> RECIEVER is None == True
>>> KEYBOARD is None == False

Anyway, It is the child process that is closing and the MainProcess that isn’t closing.

I finally got it figured out. My problem was this …

def shell_process(reciver: Optional[PipeConnection] = None):
    """
    I was already using Pipe to break out of a while loop
    to power off my monitor
    """
    pass


def kill_handler(sugnum, frame):
    """
    This function was being looped through several
    times because I was using sys.exit(). sys.exit()
    was generating along with the code below, all
    kinds of exceptions.
    """
    if mp.current_process().name == 'ShellProcess':
        for process in mp.active_children():
            process.terminate()  # was returning AtrributeError

    if mp.current_process().name == 'MainProcess':
        for process in mp.active_children():
            process.terminate()  # was returning AtrributeError

    """I was also getting NoneType, and KeyboardInterupt errors"""
    pass


signal(SIGINT, kill_handler)
signal(SIGTERM, kill_handler)


def main():
    """
    I was also having an issue with the main function as well.
    Stupid as that is.
    """
    RECIEVER, SENDER = mp_con.Pipe(duplex = True)
    mp.Process(
        name = 'ShellProcess',
        args = (RECIEVER, ),
        target = shell_process
    ).start()

    """code for Icon configuration"""
    ICON.run()


if __name__ == '__main__':
    main()

But I got it figured out … finally …

TERMINATE = False


def shell_process(reciver: Optional[PipeConnection] = None):
    global RECIEVER
    RECIEVER = reciver


def kill_handler(sugnum, frame):  # noqa
    if mp.current_process().name == 'ShellProcess':
        RECIEVER.send(True)
    if mp.current_process().name == 'MainProcess':
        global TERMINATE
        TERMINATE = True


signal(SIGINT, kill_handler)
signal(SIGTERM, kill_handler)


if __name__ == '__main__':
    RECIEVER, SENDER = Pipe(duplex = True)
    SHELL = mp.Process(
        name = 'ShellProcess',
        args = (RECIEVER, ),
        target = shell_process
    )
    SHELL.daemon = True
    SHELL.start()

    """code for Icon configuration"""
    ICON.run_detached()

    while not TERMINATE:
        if SENDER.poll():
            TERMINATE = SENDER.recv()

    close()

So you know, this is only a small part of the code. There is allot more to it including some of my own modules.