diff --git a/Mailnag/common/subproc.py b/Mailnag/common/subproc.py new file mode 100644 index 0000000..3b472c8 --- /dev/null +++ b/Mailnag/common/subproc.py @@ -0,0 +1,108 @@ +#!/usr/bin/env python2 +# -*- coding: utf-8 -*- +# +# subproc.py +# +# Copyright 2013 Patrick Ulbrich +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, +# MA 02110-1301, USA. +# + +import subprocess +import threading +import logging +import time + +_plock = threading.Lock() +_tlock = threading.Lock() +_procs = [] +_threads = [] + +def start_subprocess(args, shell = False, callback = None): + def thread(): + p = subprocess.Popen(args, shell = shell) + with _plock: _procs.append(p) + retcode = p.wait() + with _plock: _procs.remove(p) + if callback != None: + callback(retcode) + + t = threading.Thread(target = thread) + with _tlock: + _threads.append(t) + t.start() + + +def terminate_subprocesses(): + with _tlock: + if len(_threads) == 0: + return + + with _plock: + for p in _procs: + # Ask all runnig processes to terminate. + # This will also terminate threads waiting for p.wait(). + # Note : terminate() does not block. + try: p.terminate() + except: logging.debug('p.terminate() failed') + + # Start a watchdaog thread that will kill + # all processes that didn't terminate within 3 seconds. + wd = _Watchdog(3.0) + wd.start() + + with _tlock: + # Wait for all threads to terminate + for t in _threads: + t.join() + del _threads[0 : len(_threads)] + + wd.stop() + + if (not wd.triggered): + logging.info('All subprocesses exited normally.') + + +class _Watchdog(threading.Thread): + def __init__(self, timeout): + threading.Thread.__init__(self) + self.triggered = False + self._timeout = timeout + self._event = threading.Event() + + + def run(self): + # Note : keep in mind that cleanup() in mailnag.py + # has a timeout as well, so don't wait too long. + self._event.wait(self._timeout) + if not self._event.is_set(): + logging.warning('Process termination took too long - watchdog starts killing...') + self.triggered = True + with _plock: + for p in _procs: + try: + # Kill process p and quit the thread + # waiting for p to terminate (p.wait()). + p.kill() + logging.info('Watchdog killed process %s' % p.pid) + except: logging.debug('p.kill() failed') + + + def stop(self): + # Abort watchdog thread (may have been triggered already) + self._event.set() + # Wait for watchdog thread (may be inactive already) + self.join() diff --git a/Mailnag/common/utils.py b/Mailnag/common/utils.py index 76a5f55..fde3e86 100644 --- a/Mailnag/common/utils.py +++ b/Mailnag/common/utils.py @@ -28,8 +28,6 @@ import sys import time import dbus import urllib2 -import subprocess -import threading from common.dist_cfg import PACKAGE_NAME, DBUS_BUS_NAME, DBUS_OBJ_PATH @@ -86,14 +84,3 @@ def shutdown_existing_instance(): print 'OK' except: print 'FAILED' - - -def start_subprocess(args, shell = False, callback = None): - def thread(): - p = subprocess.Popen(args, shell = shell) - retcode = p.wait() - if callback != None: - callback(retcode) - - t = threading.Thread(target = thread) - t.start() diff --git a/Mailnag/mailnag.py b/Mailnag/mailnag.py index 5b962c0..6f78987 100644 --- a/Mailnag/mailnag.py +++ b/Mailnag/mailnag.py @@ -35,6 +35,7 @@ from common.config import read_cfg, cfg_exists, cfg_folder from common.utils import set_procname, is_online, shutdown_existing_instance from common.accounts import AccountList from common.plugins import Plugin, HookRegistry, MailnagController +from common.subproc import terminate_subprocesses from daemon.mailchecker import MailChecker from daemon.idlers import IdlerRunner @@ -112,6 +113,7 @@ def cleanup(): mailchecker = None unload_plugins() + terminate_subprocesses() event.set() threading.Thread(target = thread).start() diff --git a/Mailnag/plugins/libnotifyplugin.py b/Mailnag/plugins/libnotifyplugin.py index 28c9ac3..6a4ef59 100644 --- a/Mailnag/plugins/libnotifyplugin.py +++ b/Mailnag/plugins/libnotifyplugin.py @@ -26,7 +26,7 @@ import threading from gi.repository import Notify, Gio, Gtk from common.plugins import Plugin, HookTypes from common.i18n import _ -from common.utils import start_subprocess +from common.subproc import start_subprocess from daemon.mails import sort_mails NOTIFICATION_MODE_COUNT = '0' diff --git a/Mailnag/plugins/userscriptplugin.py b/Mailnag/plugins/userscriptplugin.py index 6d77d10..b1243b2 100644 --- a/Mailnag/plugins/userscriptplugin.py +++ b/Mailnag/plugins/userscriptplugin.py @@ -25,7 +25,7 @@ import os from gi.repository import Gtk from common.plugins import Plugin, HookTypes from common.i18n import _ -from common.utils import start_subprocess +from common.subproc import start_subprocess plugin_defaults = { 'script_file' : '' }