Debianeese
Debianeese

Reputation: 164

cannot kill a Sub process created by Popen when printing process.stdout

I have created a script which should run a command and kill it after 15 seconds

import logging

import subprocess
import time
import os
import sys
import signal

#cmd = "ping 192.168.1.1 -t"
cmd = "C:\\MyAPP\MyExe.exe -t 80 -I C:\MyApp\Temp -M Documents"

proc=subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.STDOUT,shell=True)

**for line in proc.stdout:
    print (line.decode("utf-8"), end='')**

time.sleep(15)
os.kill(proc.pid, signal.SIGTERM)
#proc.kill() #Tried this too but no luck 

This doesnot terminate my subprocess. however if I comment out the logging to stdout part, ie

for line in proc.stdout:
    print (line.decode("utf-8"), end='')

the subprocess has been killed.

I have tried proc.kill() and CTRL_C_EVENT too but no luck. Any help would be highly appreciated. Please see me as novice to python

Upvotes: 0

Views: 5091

Answers (2)

jfs
jfs

Reputation: 414179

To terminate subprocess in 15 seconds while printing its output line-by-line:

#!/usr/bin/env python
from __future__ import print_function
from threading import Timer
from subprocess import Popen, PIPE, STDOUT

# start process
cmd = r"C:\MyAPP\MyExe.exe -t 80 -I C:\MyApp\Temp -M Documents"
process = Popen(cmd, stdout=PIPE, stderr=STDOUT,
                bufsize=1, universal_newlines=True)

# terminate process in 15 seconds
timer = Timer(15, terminate, args=[process])
timer.start()

# print output
for line in iter(process.stdout.readline, ''):
    print(line, end='')
process.stdout.close()
process.wait() # wait for the child process to finish
timer.cancel()

Notice, you don't need shell=True here. You could define terminate() as:

def terminate(process):
    if process.poll() is None:
        try:
            process.terminate()
        except EnvironmentError:
            pass # ignore 

If you want to kill the whole process tree then define terminate() as:

from subprocess import call

def terminate(process):
    if process.poll() is None:
        call('taskkill /F /T /PID ' + str(process.pid))
  1. Use raw-string literals for Windows paths: r"" otherwise you should escape all backslashes in the string literal
  2. Drop shell=True. It creates an additional process for no reason here
  3. universal_newlines=True enables text mode (bytes are decode into Unicode text using the locale preferred encoding automatically on Python 3)
  4. iter(process.stdout.readline, '') is necessary for compatibility with Python 2 (otherwise the data may be printed with a delay due to the read-ahead buffer bug)
  5. Use process.terminate() instead of process.send_signal(signal.SIGTERM) or os.kill(proc.pid, signal.SIGTERM)
  6. taskkill allows to kill a process tree on Windows

Upvotes: 4

Meridius
Meridius

Reputation: 360

The problem is reading from stdout is blocking. You need to either read the subprocess's output or run the timer on a separate thread.

from subprocess import Popen, PIPE
from threading import Thread
from time import sleep

class ProcKiller(Thread):
    def __init__(self, proc, time_limit):
        super(ProcKiller, self).__init__()
        self.proc = proc
        self.time_limit = time_limit

    def run(self):
        sleep(self.time_limit)
        self.proc.kill()


p = Popen('while true; do echo hi; sleep 1; done', shell=True)
t = ProcKiller(p, 5)
t.start()
p.communicate()

EDITED to reflect suggested changes in comment

from subprocess import Popen, PIPE
from threading import Thread
from time import sleep
from signal import SIGTERM
import os

class ProcKiller(Thread):
    def __init__(self, proc, time_limit):
        super(ProcKiller, self).__init__()
        self.proc = proc
        self.time_limit = time_limit

    def run(self):
        sleep(self.time_limit)
        os.kill(self.proc.pid, SIGTERM)

p = Popen('while true; do echo hi; sleep 1; done', shell=True)
t = ProcKiller(p, 5)
t.start()
p.communicate()

Upvotes: 1

Related Questions