0

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

Debianeese
  • 154
  • 1
  • 2
  • 9
  • See http://stackoverflow.com/questions/644073/signal-alarm-replacement-in-windows-python – John Zwinck Jan 23 '15 at 07:00
  • @JohnZwinck: solution doesnt work here for me. – Debianeese Jan 23 '15 at 07:05
  • "It doesn't work" is not usually a helpful response. – John Zwinck Jan 23 '15 at 07:21
  • I have used subprocess to create the process and wanted to kill it after it runs for 15 seconds. The solution given in ur referenced question is to handle it with subprocess. Another relevant question I found is http://stackoverflow.com/questions/4084322/killing-a-process-created-with-pythons-subprocess-popen – Debianeese Jan 23 '15 at 07:30
  • related: [Stop reading process output in Python without hang?](http://stackoverflow.com/a/4418891/4279) – jfs Jan 23 '15 at 14:52

2 Answers2

4

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
jfs
  • 399,953
  • 195
  • 994
  • 1,670
1

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()
Meridius
  • 360
  • 1
  • 7
  • Thanks for the response. The focus returns to the main window after the time_limit but I see the process is still running backround and its output are still printed in stdout. The process never killed unless it completes or I send CTL+C – Debianeese Jan 23 '15 at 07:37
  • 1
    You might try running "os.kill(self.proc.pid, signal.SIGTERM)" instead of "self.proc.kill()" then – Meridius Jan 23 '15 at 07:39
  • unfortunately still facing the same effect. The subprocess isnt killed . PS: I am Using windows 64 bit – Debianeese Jan 23 '15 at 07:58
  • According to this post http://stackoverflow.com/questions/696345/python-2-6-on-windows-how-to-terminate-subprocess-popen-with-shell-true-argum it looks like you may want to run the subprocess without shell=True, which has a slightly different syntax. use shlex.split() to save some figuring: https://docs.python.org/2/library/subprocess.html#popen-constructor – Meridius Jan 23 '15 at 09:23
  • you don't need a custom Thread subclass, [there is threading.Timer already](http://stackoverflow.com/a/28112284/4279) – jfs Jan 23 '15 at 14:53