2

I am running some shell scripts with the subprocess module in python. If the shell scripts is running to long, I like to kill the subprocess. I thought it will be enough if I am passing the timeout=30 to my run(..) statement.

Here is the code:

try:
    result=run(['utilities/shell_scripts/{0} {1} {2}'.format(
                        self.language_conf[key][1], self.proc_dir, config.main_file)],
                shell=True,
                check=True,
                stdout=PIPE,
                stderr=PIPE, 
                universal_newlines=True, 
                timeout=30,
                bufsize=100)
except TimeoutExpired as timeout:

I have tested this call with some shell scripts that runs 120s. I expected the subprocess to be killed after 30s, but in fact the process is finishing the 120s script and than raises the Timeout Exception. Now the Question how can I kill the subprocess by timeout?

12
  • have you tried legacy methods with Popen ? Commented Feb 13, 2018 at 9:37
  • 1
    What do you have in your except block? From the doc: "The child process is not killed if the timeout expires, so in order to cleanup properly a well-behaved application should kill the child process and finish communication" Commented Feb 13, 2018 at 9:38
  • I have read the official document, it sends SIGKILL to kill the subprocess. Maybe your script cannot be killed by SIGKILL? Try it in raw terminal. Commented Feb 13, 2018 at 9:38
  • @cdarke That is the behavior of Popen but not run. run will kill the child process. Commented Feb 13, 2018 at 9:39
  • @Sraw: sorry, you are right. I would still like to know what is in the except block though. Commented Feb 13, 2018 at 9:40

1 Answer 1

9

The documentation explicitly states that the process should be killed:

from the docs for subprocess.run:

"The timeout argument is passed to Popen.communicate(). If the timeout expires, the child process will be killed and waited for. The TimeoutExpired exception will be re-raised after the child process has terminated."

But in your case you're using shell=True, and I've seen issues like that before, because the blocking process is a child of the shell process.

I don't think you need shell=True if you decompose your arguments properly and your scripts have the proper shebang. You could try this:

result=run(
  [os.path.join('utilities/shell_scripts',self.language_conf[key][1]), self.proc_dir, config.main_file],  # don't compose argument line yourself
            shell=False,  # no shell wrapper
            check=True,
            stdout=PIPE,
            stderr=PIPE, 
            universal_newlines=True, 
            timeout=30,
            bufsize=100)

note that I can reproduce this issue very easily on Windows (using Popen, but it's the same thing):

import subprocess,time

p=subprocess.Popen("notepad",shell=True)
time.sleep(1)
p.kill()

=> notepad stays open, probably because it manages to detach from the parent shell process.

import subprocess,time

p=subprocess.Popen("notepad",shell=False)
time.sleep(1)
p.kill()

=> notepad closes after 1 second

Funnily enough, if you remove time.sleep(), kill() works even with shell=True probably because it successfully kills the shell which is launching notepad.

I'm not saying you have exactly the same issue, I'm just demonstrating that shell=True is evil for many reasons, and not being able to kill/timeout the process is one more reason.

However, if you need shell=True for a reason, you can use psutil to kill all the children in the end. In that case, it's better to use Popen so you get the process id directly:

import subprocess,time,psutil

parent=subprocess.Popen("notepad",shell=True)
for _ in range(30): # 30 seconds
    if parent.poll() is not None:  # process just ended
      break
    time.sleep(1)
else:
   # the for loop ended without break: timeout
   parent = psutil.Process(parent.pid)
   for child in parent.children(recursive=True):  # or parent.children() for recursive=False
       child.kill()
   parent.kill()

(source: how to kill process and child processes from python?)

that example kills the notepad instance as well.

Sign up to request clarification or add additional context in comments.

11 Comments

In run's source code, it also use kill() to terminate child process. So in this case, I don't think this will work. But who knows...
@Sraw: I think it's the bloody shell=True which causes the issue.
I'm... still not sure about it as I have never seen this behavior. Since you said you have seen it, could you do us a favor by providing an example which will cause this situation?
@Sraw actually I can. I'm using windows, but I'm sure it can be reproduced on Linux as well.
Because of some reasons, I need to execute the commands in the shell, so shell=true is necessary. When I am not using the statement I get an FileNotFoundError. Or is this error caused by an other false configuration?
|

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.