Python: How to prevent subprocesses from receiving CTRL-C / Control-C / SIGINT
Asked Answered
P

5

50

I am currently working on a wrapper for a dedicated server running in the shell. The wrapper spawns the server process via subprocess and observes and reacts to its output.

The dedicated server must be explicitly given a command to shut down gracefully. Thus, CTRL-C must not reach the server process.

If I capture the KeyboardInterrupt exception or overwrite the SIGINT-handler in python, the server process still receives the CTRL-C and stops immediately.

So my question is: How to prevent subprocesses from receiving CTRL-C / Control-C / SIGINT?

Postmeridian answered 18/2, 2011 at 19:39 Comment(4)
I'm interested in the workaround, would be nice if you posted it!Kreiner
What operating system are you using?Goggle
The dedicated server is running on a Linux system (Debian).Postmeridian
Does anybody know of a solution for this on Windows?Reese
P
44

Somebody in the #python IRC-Channel (Freenode) helped me by pointing out the preexec_fn parameter of subprocess.Popen(...):

If preexec_fn is set to a callable object, this object will be called in the child process just before the child is executed. (Unix only)

Thus, the following code solves the problem (UNIX only):

import subprocess
import signal

def preexec_function():
    # Ignore the SIGINT signal by setting the handler to the standard
    # signal handler SIG_IGN.
    signal.signal(signal.SIGINT, signal.SIG_IGN)

my_process = subprocess.Popen(
    ["my_executable"],
    preexec_fn = preexec_function
)

Note: The signal is actually not prevented from reaching the subprocess. Instead, the preexec_fn above overwrites the signal's default handler so that the signal is ignored. Thus, this solution may not work if the subprocess overwrites the SIGINT handler again.

Another note: This solution works for all sorts of subprocesses, i.e. it is not restricted to subprocesses written in Python, too. For example the dedicated server I am writing my wrapper for is in fact written in Java.

Postmeridian answered 19/2, 2011 at 11:27 Comment(1)
Popen(preexec_fn=...) will eventually cause a deadlock if used in a python application with multiple threads. I have hit this a few times, and it's documented here: docs.python.org/3/library/subprocess.html#subprocess.PopenVenue
L
27

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

On Python 3.11 and newer you can use the process_group argument for Popen:

from subprocess import Popen

Popen('do_not_want_signals', process_group=0)

On Python 3.10 and older you can use preexec_fn:

import os
from subprocess import Popen

def preexec(): # Don't forward signals.
    os.setpgrp()

Popen('do_not_want_signals', preexec_fn=preexec)
# The above can be shortened to:
Popen('do_not_want_signals', preexec_fn=os.setpgrp)
Leverage answered 27/3, 2011 at 3:16 Comment(3)
+1 You don't need the preexec function, Popen(args, preexec_nf=os.setpgrp) is cool too.Crocidolite
preexec_nf? Better try Popen(args, preexec_fn=os.setpgrp) ;-)Haemato
On Python 3.11+ get rid of preexec_fn=os.setpgrp in favor of process_group=0 as preexec_fn is incompatible with threads. On earlier Python versions you can set start_new_session=True and achieve a similar result rather than that preexec_fn=, though it's setsid() call also does more...Tiffanytiffi
A
10

you can do something like this to make it work in windows and unix:

import subprocess
import sys

def pre_exec():
    # To ignore CTRL+C signal in the new process
    signal.signal(signal.SIGINT, signal.SIG_IGN)

if sys.platform.startswith('win'):
    # https://msdn.microsoft.com/en-us/library/windows/desktop/ms684863(v=vs.85).aspx
    # If CREATE_NEW_PROCESS_GROUP flag is specified, CTRL+C signals will be disabled
    my_sub_process=subprocess.Popen(["executable"], creationflags=subprocess.CREATE_NEW_PROCESS_GROUP)
else:
    my_sub_process=subprocess.Popen(["executable"], preexec_fn=pre_exec)
Adamo answered 26/8, 2016 at 21:2 Comment(3)
When I use your creationflags, the main process is unkillable with Ctrl+C on windows. Ideas?Palatal
@Palatal i found a quick workaround for this by using win32api instead of signal: win32api.SetConsoleCtrlHandler(exit_handler, True). Worked like a charm.Pipkin
similar note as on the other answer, avoid preexec_fn= and use 3.11's process_group=0 or 3.2's start_new_session=True flags as preexec_fn= is not thread safe.Tiffanytiffi
T
5

After an hour of various attempts, this works for me:

process = subprocess.Popen(["someprocess"], creationflags=subprocess.DETACHED_PROCESS | subprocess.CREATE_NEW_PROCESS_GROUP)

It's solution for windows.

Tol answered 1/8, 2020 at 17:59 Comment(0)
C
1

Try setting SIGINT to be ignored before spawning the subprocess (reset it to default behavior afterward).

If that doesn't work, you'll need to read up on job control and learn how to put a process in its own background process group, so that ^C doesn't even cause the kernel to send the signal to it in the first place. (May not be possible in Python without writing C helpers.)

See also this older question.

Consult answered 18/2, 2011 at 20:6 Comment(1)
I tried this, but it did not work (ignoring SIGINT before spawning the subprocess, i.e. not the job control thing). I have a workaround for now, which I will present later today or tomorrow.Postmeridian

© 2022 - 2024 — McMap. All rights reserved.