Reputation: 694
I'm not going to do into details of why i need to do this, but my problem is as follows: I have a string containing some shell commands I need to run, which might have some output redirections in them. Consider the simple example:
cmd = "yes > output.txt"
I want to be able to run this command through python with a timeout so the command doesn't run forever if there's an infinite loop. I'm using the subprocess module. Here is my code:
import subprocess
subprocess.call("yes > output.txt", timeout=1, shell=True)
When I run this, I get a subprocess.TimeoutExpired
exception as expected after 1 second, but the process is not killed. If I look at htop
I still see the yes
process running.
Curiously, if I run this through the interpreter, and I press Ctrl+C
at the prompt after the exception was thrown, that kills the process. Am I just doing something dumb here?
I get this behaviour both on macOS Catalina with Python 3.7.7 as well as on Ubuntu 18.04 with Python 3.6.9
EDIT:
I'm unsure why there's an inconsistency if my command redirects output to a file. For example:
subprocess.call("sleep 100", timeout=1, shell=True)
does kill the process when it times out. However, the following:
subprocess.call("sleep 100 > f", timeout=1, shell=True)
does not kill the process. I'm aware that in this case nothing is actually redirected to the file.
More importantly, my actual question is, how would i go about killing the child process in all cases after timeout?
Upvotes: 1
Views: 2498
Reputation: 148890
The timeout
parameter is not the time allowed to the child process, but just the time allowed to the parent to wait for the end of its child. If the child does not end during the allowed time, nothing happens to the child and the parent receives a TimeoutExpired
exception. So what you observe is by design.
When you type Ctrl C in a interactive terminal session, the SIGINT interruption is sent to all the processes in the process group of the foreground process. As you have not explicitely started a new process group, childs of your Python process will share the same process group and will receive the SIGINT. Here again, what you observe is by design.
The sleep
program is special. It internally depends on the SIGALARM interruption, as does subprocess.call
with the timeout parameter. Here again, the signal is sent to the whole process group, hence the child terminates. But I have no explainations on why redirecting output to a file prevents the child to be killed.
If you want the child to be forcibly terminated, you should not use call
but create and manage explicitely a Process
object:
p = subprocess.Process("yes > output.txt", shell=True)
try:
p.wait(timeout=1)
except subprocess.TimeoutExpired:
p.terminate()
Beware untested code, so typos could be present...
Upvotes: 2