1

When I exit my application (code below), the two ping processes I start using subprocess.Popen are not automatically killed, and still show up in the Windows 7 task list.

While the app is running, the ping processes show up under Python.exe as two threads. When the app exits, these two processes move to the system process tab and keep running there.

How can I fix this? I want the two ping processes to be killed when my application is closed.

# -*- coding: utf-8 -*- 

import sys 
import time 
import subprocess 
from threading import Thread 
import re 
from PyQt4.QtGui import QMainWindow, QApplication, QStandardItemModel, QStandardItem, QWidget, QVBoxLayout, QTableView 
from PyQt4.QtCore import pyqtSignature, Qt, QTimer, SIGNAL, QString, QMetaObject 
from Queue import Queue 

try: 
    _fromUtf8 = QString.fromUtf8 
except AttributeError: 
    _fromUtf8 = lambda s: s 

class Ui_MainWindow(object): 
    def setupUi(self, MainWindow): 
        MainWindow.setObjectName(_fromUtf8("MainWindow")) 
        MainWindow.resize(500, 435) 
        self.centralWidget = QWidget(MainWindow) 
        self.centralWidget.setObjectName(_fromUtf8("centralWidget")) 
        self.verticalLayout = QVBoxLayout(self.centralWidget) 
        self.verticalLayout.setObjectName(_fromUtf8("verticalLayout")) 
        self.tableView = QTableView(self.centralWidget) 
        self.tableView.setObjectName(_fromUtf8("tableView")) 
        self.verticalLayout.addWidget(self.tableView) 
        MainWindow.setCentralWidget(self.centralWidget) 

        self.retranslateUi(MainWindow) 
        QMetaObject.connectSlotsByName(MainWindow) 

    def retranslateUi(self, MainWindow): 
        MainWindow.setWindowTitle(QApplication.translate("MainWindow", "Ping Tester", None, QApplication.UnicodeUTF8)) 

if sys.platform.startswith('linux'): 
    getdata = re.compile(r"icmp_req=(\d+) ttl=(\d+) time=([\d\.]+)\sms") 
    pingstr = ["ping", "-n", "-i 0.2"] 
    filtered = "Packet filtered" 
    delaytime = 200 
else: 
    getdata = re.compile(r"=([\d\.]+)ms TTL=(\d+)") 
    pingstr = ["ping.exe", "-t"] 
    timeout = "Request timed out." 
    delaytime = 500 

try: 
    with open("ips.conf", "r") as f: 
        t_node = f.read().decode('utf-8') 
        if not t_node: 
            raise IOError 
except IOError: 
    with open("ips.conf", "w") as f: 
        t_node = u""" 
        8.8.8.8-Google 
        184.22.112.34-USAHE 
        """ 
        f.write(t_node.encode('utf-8')) 

node = [] 
for line in t_node.split('\n'): 
    try: 
        ip, desc = line.strip().split("-") 
        node.append((ip, desc)) 
    except ValueError: 
        pass 
nodecount = len(node) 

class MainWindow(QMainWindow, Ui_MainWindow): 
    """ 
    Class documentation goes here. 
    """ 
    def __init__(self, parent = None): 
        """ 
        Constructor 
        """ 
        QMainWindow.__init__(self, parent) 
        self.setupUi(self) 
        self.model = QStandardItemModel() 
        self.model.setColumnCount(6) 
        self.model.setRowCount(nodecount) 
        self.model.setHorizontalHeaderLabels(["IP", "Description", "Loss%", "CurPing", "AvgPing", "TTL"]) 
        for i, (ip, desc) in enumerate(node): 
            self.setitem(i, 0, ip) 
            self.setitem(i, 1, desc) 
            self.setitem(i, 2, "") 
            self.setitem(i, 3, "") 
            self.setitem(i, 4, "") 
            self.setitem(i, 5, "") 
        self.tableView.setModel(self.model) 
        for i in range(len(node)): 
            self.tableView.setRowHeight(i, 18) 
        self.resizetable() 
        self.timer = QTimer(self) 
        self.connect(self.timer, 
                     SIGNAL("timeout()"), 
                     self.checkitems) 
        self.timer.start(delaytime) 

    def checkitems(self): 
        while not q.empty(): 
            item = q.get() 
            self.chgtxt(*item) 
            q.task_done() 
        self.resizetable() 

    def resizetable(self): 
        self.tableView.resizeColumnsToContents() 

    def chgtxt(self, x, y, value): 
        self.model.item(x, y).setText(value) 

    def setitem(self, x, y, value): 
        self.model.setItem(x, y, QStandardItem(value)) 

app = QApplication(sys.argv) 
ui = MainWindow() 
ui.show() 
q = Queue() 

def pinger(i, ip, desc): 
    s = "" 
    avgping = 0 
    count = 0 
    timeoutcount = 0 
    ret = subprocess.Popen(pingstr + [ip], 
                            stdout=subprocess.PIPE) 
    while True: 
        try: 
            s += ret.stdout.read(1) 

            tryfind = getdata.findall(s) 
            if sys.platform.startswith('linux'): 
                if len(tryfind) > 0: 
                    req, ttl, crtping = tryfind[-1] 
                    avgping += float(crtping) 
                    count += 1 
                    q.put((i, 3, crtping + "ms")) 
                    q.put((i, 4, "%.2f" % (avgping * 1.0 / count) + "ms")) 
                    q.put((i, 5, ttl)) 
                    q.put((i, 2, "%.2f" % ((int(req) - count) * 100.0 / int(req)))) 
                    s = "" 
                elif filtered in s: 
                    q.put((i, 2, "Failed")) 
                    q.put((i, 3, "Failed")) 
                    q.put((i, 4, "Failed")) 
                    q.put((i, 5, "Failed")) 
                    ret.kill() 
                    s = "" 
            else: 
                if len(tryfind) > 0: 
                    crtping, ttl = tryfind[-1] 
                    avgping += float(crtping) 
                    count += 1 
                    q.put((i, 3, crtping + "ms")) 
                    q.put((i, 4, "%.2f" % (avgping * 1.0 / count) + "ms")) 
                    q.put((i, 5, ttl)) 
                    q.put((i, 2, "%.2f" % (timeoutcount * 100.0 / (count + timeoutcount)))) 
                elif timeout in s: 
                    timeoutcount += 1 
                    q.put((i, 2, "-")) 
                    q.put((i, 3, "-")) 
                    if count: 
                        q.put((i, 5, "%.2f" % (timeoutcount * 100.0 / (count + timeoutcount)))) 
                    else: 
                        q.put((i, 5, "-")) 
                    s = "" 
        except IOError: 
            print s 
            break 

def startworkers(): 
    for i, (ip, desc) in enumerate(node): 
        worker = Thread(target=pinger, args=(i, ip, desc)) 
        worker.setDaemon(True) 
        worker.start() 
        time.sleep(delaytime / 10000.0) 

startthread = Thread(target=startworkers) 
startthread.setDaemon(True) 
startthread.start() 

sys.exit(app.exec_())  
dano
  • 91,354
  • 19
  • 222
  • 219
soneedu
  • 73
  • 1
  • 3
  • 11
  • 2
    could you maybe reduce your code to the minimal problem. The idea here is to keep questions as general as possible, so that they can help as many people as possible. – cchristelis Jun 25 '14 at 13:54
  • possible duplicate of [Ensuring subprocesses are dead on exiting Python program](http://stackoverflow.com/questions/320232/ensuring-subprocesses-are-dead-on-exiting-python-program) – dano Jun 25 '14 at 14:23
  • @dano thanks to ur suggestion. problem is i am newbie on Python, after i read the post u provied, i do not how to use in my code. in my code, main thread -> thread -> subprocess. i am stucked here. – soneedu Jun 25 '14 at 14:53

2 Answers2

1

Here's a way you can do it, using atexit:

import subprocess
from threading import Thread
import sys 
import atexit

from PyQt4.QtGui import QMainWindow, QApplication

class Ui_MainWindow(object): 
    def setupUi(self, MainWindow): 
        MainWindow.setObjectName(("MainWindow")) 
        MainWindow.resize(500, 435) 

def runproc():
    p = subprocess.Popen(["sleep", "500"])
    atexit.register(kill_proc, p)
    p.communicate()

def kill_proc(proc):
    try:
        proc.terminate()
    except Exception:
        pass

class MainWindow(QMainWindow):
    def __init__(self):
        QMainWindow.__init__(self)
        self.resize(300, 300)

if __name__ == "__main__":
    app = QApplication(sys.argv)
    ui = MainWindow()
    ui.show()
    for i in range(0, 3): 
        t = Thread(target=runproc)
        t.start()
    sys.exit(app.exec_())

Each thread registers an atexit callback, which passes the Popen object it created. When the process exits via normal means, the atexit handlers get called, and in each one we call terminate on the Popen object, which kills the process. Note that this does not handle someone sending a signal like SIGKILL to your process; it only handles it being exited by closing the QMainWindow, or something doing something like Ctrl+C if you're running via the CLI.

Edit:

To handle the exception you get on shutdown, you have to change how your code handles the data it reads from stdout of the subprocesses. When you kill the subprocesses at shutdown, they sends None to their stdout, and your thread tries to process that None as if it was actual data. You just need to handle that case gracefully:

out = ret.stdout.read(1)
if not out:
    break
s += out 
print s
tryfind = getdata.findall(s)
dano
  • 91,354
  • 19
  • 222
  • 219
  • ,use ur way, can kill subprocess. just some exception during kill, not get the stdout, here is code modified [modified code](http://codepad.org/pXslkhc6) – soneedu Jun 25 '14 at 16:30
  • @soneedu I edited my answer to show how you can avoid the exceptions you're seeing on shutdown. – dano Jun 25 '14 at 16:36
  • ,thanks a lot to u. i have thougnt check the out False or True. but forget break. u are professional and patient。 – soneedu Jun 26 '14 at 01:53
0

You have a general design problem :

  • you start you subprocesses in daemon threads to not have to stop them explicitely when program ends
  • but you want the processes to stop

IMHO the simplest (and cleanest) solution in your case is to explicitely ask your threads to stop and have them kill the subprocess the started :

  • set a global variable global stopping = False
  • use non daemon threads
  • replace your sys.exit(app.exec_()) at the end with

    cr = app_exec_()
    stopping = true
    sys.exit(cr)
    
  • in pinger, replace while True: with

    global stopping
    while True:
        if stopping:
            ret.kill()
            break
    

This should be enough to correctly kill your subprocesses.

Serge Ballesta
  • 143,923
  • 11
  • 122
  • 252
  • i try this,but failed to kill subprocess. thanks to u all the same – soneedu Jun 25 '14 at 16:25
  • This doesn't work because the pinger thread blocks on `s+= ret.stdout.read(1)`, so it never loops around to see that `stopping` was set. – dano Jun 25 '14 at 16:38
  • @dano : As there's a ping at the other end of the pipe, I hope that next character will bring me to the test. – Serge Ballesta Jun 26 '14 at 07:41