add support for exception-aware threading

This commit is contained in:
petrucci4prez 2017-06-02 02:17:32 -04:00
parent a4a1671650
commit db3fde9be3
9 changed files with 90 additions and 34 deletions

View File

@ -1,6 +1,11 @@
import time, psutil, yaml, os '''
Various helper functions and classes
'''
import time, yaml, os
from subprocess import check_output, DEVNULL, CalledProcessError from subprocess import check_output, DEVNULL, CalledProcessError
from threading import Thread, Event from threading import Event
from exceptionThreading import ExceptionThread
class ConfigFile(): class ConfigFile():
''' '''
@ -21,21 +26,7 @@ class ConfigFile():
with open(self._path, 'w') as f: with open(self._path, 'w') as f:
yaml.dump(self._dict, f, default_flow_style=False) yaml.dump(self._dict, f, default_flow_style=False)
class async: class CountdownTimer(ExceptionThread):
'''
Wraps any function in a thread and starts the thread. Intended to be used as
a decorator
'''
def __init__(self, daemon=False):
self._daemon = daemon
def __call__(self, f):
def wrapper(*args, **kwargs):
t = Thread(target=f, daemon=self._daemon, args=args, kwargs=kwargs)
t.start()
return wrapper
class CountdownTimer(Thread):
''' '''
Launches thread which self terminates after some time (given in seconds). Launches thread which self terminates after some time (given in seconds).
Termination triggers some action (a function). Optionally, a sound can be Termination triggers some action (a function). Optionally, a sound can be

View File

@ -1,11 +1,12 @@
import RPi.GPIO as GPIO import RPi.GPIO as GPIO
import time, logging import time, logging
from threading import Thread, Event from threading import Event
from exceptionThreading import ExceptionThread
from itertools import chain from itertools import chain
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
class Blinkenlights(Thread): class Blinkenlights(ExceptionThread):
def __init__(self, pin, cyclePeriod=2): def __init__(self, pin, cyclePeriod=2):
self._stopper = Event() self._stopper = Event()
self._pin = pin self._pin = pin

61
exceptionThreading.py Normal file
View File

@ -0,0 +1,61 @@
'''
Implementation of exception-aware threads, including a listener to be put in
the top-level thread
'''
import queue
from threading import Thread, Event
# Killswitch for exception queue listener. Pass this to top-level thread so it
# can be called as part of clean up code
excStopper = Event()
# queue to shuttle exceptions from children to top-level thread
_excQueue = queue.Queue()
def excChildListener():
'''
Queue listener to intercept exceptions thrown in child threads that are
exception-aware
'''
while not excStopper.isSet():
try:
raise _excQueue.get(True)
_excQueue.task_done()
except queue.Empty:
pass
while 1:
try:
raise _excQueue.get(False)
_excQueue.task_done()
except queue.Empty:
break
class ExceptionThread(Thread):
'''
Thread that passes exceptions to a queue, which is handled by
excChildListener in top-level thread
'''
def __init__(self, group=None, target=None, name=None, args=(), kwargs=None, *, daemon=None):
super().__init__(group, target, name, args, kwargs, daemon=daemon)
self._queue = _excQueue
def run(self):
try:
Thread.run(self)
except BaseException as e:
self._queue.put(e)
class async:
'''
Wraps any function in an exception-aware thread and starts the thread.
Intended to be used as a decorator
'''
def __init__(self, daemon=False):
self._daemon = daemon
def __call__(self, f):
def wrapper(*args, **kwargs):
t = ExceptionThread(target=f, daemon=self._daemon, args=args, kwargs=kwargs)
t.start()
return wrapper

View File

@ -1,5 +1,6 @@
import logging, time import logging, time
from auxilary import async, ConfigFile from auxilary import ConfigFile
from exceptionThreading import async
from smtplib import SMTP from smtplib import SMTP
from datetime import datetime from datetime import datetime
from email.mime.multipart import MIMEMultipart from email.mime.multipart import MIMEMultipart

View File

@ -1,5 +1,5 @@
import logging, os, sys, stat import logging, os, sys, stat
from threading import Thread from exceptionThreading import ExceptionThread
from evdev import InputDevice, ecodes from evdev import InputDevice, ecodes
from select import select from select import select
from auxilary import waitForPath from auxilary import waitForPath
@ -7,7 +7,7 @@ import stateMachine
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
class KeypadListener(Thread): class KeypadListener(ExceptionThread):
def __init__(self, stateMachine, callbackDisarm, callbackArm, soundLib, passwd): def __init__(self, stateMachine, callbackDisarm, callbackArm, soundLib, passwd):
ctrlKeys = { 69: 'NUML', 98: '/', 14: 'BS', 96: 'ENTER'} ctrlKeys = { 69: 'NUML', 98: '/', 14: 'BS', 96: 'ENTER'}
@ -115,7 +115,7 @@ class KeypadListener(Thread):
# TODO: these are not threadsafe # TODO: these are not threadsafe
# TODO: this code gets really confused if the pipe is deleted # TODO: this code gets really confused if the pipe is deleted
class PipeListener(Thread): class PipeListener(ExceptionThread):
def __init__(self, callback, path): def __init__(self, callback, path):
self._path = path self._path = path

View File

@ -5,6 +5,7 @@ import RPi.GPIO as GPIO
from sharedLogging import unmountGluster # this should be first program module from sharedLogging import unmountGluster # this should be first program module
from stateMachine import StateMachine from stateMachine import StateMachine
from exceptionThreading import excChildListener, excStopper
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
@ -23,6 +24,7 @@ def clean():
logger.critical(traceback.format_exc()) logger.critical(traceback.format_exc())
def sigtermHandler(signum, stackFrame): def sigtermHandler(signum, stackFrame):
excStopper.set()
logger.info('Caught SIGTERM') logger.info('Caught SIGTERM')
raise SystemExit raise SystemExit
@ -32,15 +34,14 @@ if __name__ == '__main__':
GPIO.setwarnings(False) GPIO.setwarnings(False)
GPIO.setmode(GPIO.BCM) GPIO.setmode(GPIO.BCM)
stateMachine = StateMachine() stateMachine = StateMachine()
# TODO: segfaults are annoying :( # TODO: segfaults are annoying :(
#~ signal.signal(signal.SIGSEGV, sig_handler) #~ signal.signal(signal.SIGSEGV, sig_handler)
signal.signal(signal.SIGTERM, sigtermHandler) signal.signal(signal.SIGTERM, sigtermHandler)
while 1: excChildListener()
time.sleep(31536000)
except Exception: except Exception:
logger.critical(traceback.format_exc()) logger.critical(traceback.format_exc())

View File

@ -1,9 +1,9 @@
import logging, os, hashlib, queue, threading, time, psutil import logging, os, hashlib, queue, time, psutil
from threading import Event
from exceptionThreading import ExceptionThread, async
from pygame import mixer from pygame import mixer
from subprocess import call from subprocess import call
from collections import OrderedDict from collections import OrderedDict
from auxilary import async
from queue import Queue
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
@ -89,8 +89,8 @@ class SoundLib:
self.volume = 100 self.volume = 100
self._applyVolumesToSounds(self.volume) self._applyVolumesToSounds(self.volume)
self._ttsQueue = Queue() self._ttsQueue = queue.Queue()
self._stop = threading.Event() self._stop = Event()
self._startMonitor() self._startMonitor()
def changeVolume(self, volumeDelta): def changeVolume(self, volumeDelta):
@ -187,7 +187,7 @@ class SoundLib:
logger.debug('TTS engine received "%s"', text) logger.debug('TTS engine received "%s"', text)
def _startMonitor(self): def _startMonitor(self):
self._thread = t = threading.Thread(target=self._ttsMonitor, daemon=True) self._thread = t = ExceptionThread(target=self._ttsMonitor, daemon=True)
t.start() t.start()
logger.debug('Starting TTS Queue Monitor') logger.debug('Starting TTS Queue Monitor')

View File

@ -16,7 +16,8 @@ import gi, time, os, logging
from datetime import datetime from datetime import datetime
from threading import Thread, Lock from threading import Thread, Lock
from auxilary import async, waitForPath, mkdirSafe from auxilary import waitForPath, mkdirSafe
from exceptionThreading import async
from sharedLogging import gluster from sharedLogging import gluster
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)

View File

@ -5,7 +5,7 @@ from flask_wtf import FlaskForm
from wtforms.fields import StringField, SubmitField from wtforms.fields import StringField, SubmitField
from wtforms.validators import InputRequired from wtforms.validators import InputRequired
from auxilary import async from exceptionThreading import async
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)