Alex Rivera | Logout

How to stop python from propagating signals to subprocesses?

Asked 2010-09-24T22:12:10.163
25

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(shlex.split(command), stdout=pipe, stderr=pipe)

My code handles different signal. Ctrl+C will stop the simulation, ask if I want to save, and exit gracefully. I have other signal handlers (to force data output for example).

What I want is to send a signal (SIGINT, Ctrl+C) to my python script which will ask the user which signal he wants to send to the program.

The only thing preventing the code to work is that it seems that whatever I do, Ctrl+C will be "forwarded" to the subprocess: the code will catch it to and exit:

try:
  <wait for available slots>
except KeyboardInterrupt:
  print "KeyboardInterrupt catched! All simulations are paused. Please choose the signal to send:"
  print "  0: SIGCONT (Continue simulation)"
  print "  1: SIGINT  (Exit and save)"
  [...]
  answer = raw_input()
  pid.send_signal(signal.SIGCONT)
  if   (answer == "0"):
    print "    --> Continuing simulation..."
  elif (answer == "1"):
    print "    --> Exit and save."
    pid.send_signal(signal.SIGINT)
    [...]

So whatever I do, the program is receiving the SIGINT that I only want my python script to see. How can I do that???

I also tried:

signal.signal(signal.SIGINT, signal.SIG_IGN)
pid = subprocess.Popen(shlex.split(command), stdout=pipe, stderr=pipe)
signal.signal(signal.SIGINT, signal.SIG_DFL)

to run the program but this gives the same result: the program catches the SIGINT.

Thanx!

Edit
Report

2 Answers

8

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 :)

answered 2010-09-25T03:01:02.777
4

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.

answered 2010-09-25T02:15:09.937

Your Answer