How to stop python from propagating signals to subprocesses?

前端 未结 5 1618
情歌与酒
情歌与酒 2020-12-17 08:51

I\'m using python to manage some simulations. I build the parameters and run the program using:

pipe = open(\'/dev/null\', \'w\')
pid = subprocess.Popen(shle         


        
相关标签:
5条回答
  • 2020-12-17 09:25

    The function:

    os.setpgrp()
    

    Works well only if Popen is being called right afterwards. If you are trying to prevent signals from being propagated to the subprocesses of an arbitrary package, then the package may override this before creating subprocesses causing signals to be propagated anyways. This is the case when, for example, trying to prevent signal propagation into web browser processes spawned from the package Selenium.

    This function also removes the ability to easily communicate between the separated processes without something like sockets.

    For my purposes, this seemed like overkill. Instead of worrying about signals propagating, I used the custom signal SIGUSR1. Many Python packages ignore SIGUSR1, so even if it is sent to all subprocesses, it will usually be ignored

    It can be sent to a process in bash on Ubuntu using

    kill -10 <pid>
    

    It can be recognized in your code via

    signal.signal(signal.SIGUSR1, callback_function)
    

    The available signal numbers on Ubuntu can be found at /usr/include/asm/signal.h.

    0 讨论(0)
  • 2020-12-17 09:34

    This can indeed be done using ctypes. I wouldn't really recommend this solution, but I was interested enough to cook something up, so I thought I would share it.

    parent.py

    #!/usr/bin/python
    
    from ctypes import *
    import signal
    import subprocess
    import sys
    import time
    
    # Get the size of the array used to
    # represent the signal mask
    SIGSET_NWORDS = 1024 / (8 * sizeof(c_ulong))
    
    # Define the sigset_t structure
    class SIGSET(Structure):
        _fields_ = [
            ('val', c_ulong * SIGSET_NWORDS)
        ]
    
    # Create a new sigset_t to mask out SIGINT
    sigs = (c_ulong * SIGSET_NWORDS)()
    sigs[0] = 2 ** (signal.SIGINT - 1)
    mask = SIGSET(sigs)
    
    libc = CDLL('libc.so.6')
    
    def handle(sig, _):
        if sig == signal.SIGINT:
            print("SIGINT from parent!")
    
    def disable_sig():
        '''Mask the SIGINT in the child process'''
        SIG_BLOCK = 0
        libc.sigprocmask(SIG_BLOCK, pointer(mask), 0)
    
    # Set up the parent's signal handler
    signal.signal(signal.SIGINT, handle)
    
    # Call the child process
    pid = subprocess.Popen("./child.py", stdout=sys.stdout, stderr=sys.stdin, preexec_fn=disable_sig)
    
    while (1):
        time.sleep(1)
    

    child.py

    #!/usr/bin/python
    import time
    import signal
    
    def handle(sig, _):
        if sig == signal.SIGINT:
            print("SIGINT from child!")
    
    signal.signal(signal.SIGINT, handle)
    while (1):
        time.sleep(1)
    

    Note that this makes a bunch of assumptions about various libc structures and as such, is probably quite fragile. When running, you won't see the message "SIGINT from child!" printed. However, if you comment out the call to sigprocmask, then you will. Seems to do the job :)

    0 讨论(0)
  • 2020-12-17 09:34

    POSIX says that a program run with execvp (which is what subprocess.Popen uses) should inherit the signal mask of the calling process.

    I could be wrong, but I don't think calling signal modifies the mask. You want sigprocmask, which python does not directly expose.

    It would be a hack, but you could try setting it via a direct call to libc via ctypes. I'd definitely be interested in seeing a better answer on this strategy.

    The alternative strategy would be to poll stdin for user input as part of your main loop. ("Press Q to quit/pause" -- something like that.) This sidesteps the issue of handling signals.

    0 讨论(0)
  • 2020-12-17 09:40

    Combining some of other answers that will do the trick - no signal sent to main app will be forwarded to the subprocess.

    import os
    from subprocess import Popen
    
    def preexec(): # Don't forward signals.
        os.setpgrp()
    
    Popen('whatever', preexec_fn = preexec)
    
    0 讨论(0)
  • 2020-12-17 09:50

    I resolved this problem by creating a helper app that I call instead of creating the child directly. This helper changes its parent group and then spawn the real child process.

    import os
    import sys
    
    from time import sleep
    from subprocess import Popen
    
    POLL_INTERVAL=2
    
    # dettach from parent group (no more inherited signals!)
    os.setpgrp()
    
    app = Popen(sys.argv[1:])
    while app.poll() is None:
        sleep(POLL_INTERVAL)
    
    exit(app.returncode)
    

    I call this helper in the parent, passing the real child and its parameters as arguments:

    Popen(["helper", "child", "arg1", ...])
    

    I have to do this because my child app is not under my control, if it were I could have added the setpgrp there and bypassed the helper altogether.

    0 讨论(0)
提交回复
热议问题