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 threading import Thread, Event
from threading import Event
from exceptionThreading import ExceptionThread
class ConfigFile():
'''
@ -21,21 +26,7 @@ class ConfigFile():
with open(self._path, 'w') as f:
yaml.dump(self._dict, f, default_flow_style=False)
class async:
'''
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):
class CountdownTimer(ExceptionThread):
'''
Launches thread which self terminates after some time (given in seconds).
Termination triggers some action (a function). Optionally, a sound can be

View File

@ -1,11 +1,12 @@
import RPi.GPIO as GPIO
import time, logging
from threading import Thread, Event
from threading import Event
from exceptionThreading import ExceptionThread
from itertools import chain
logger = logging.getLogger(__name__)
class Blinkenlights(Thread):
class Blinkenlights(ExceptionThread):
def __init__(self, pin, cyclePeriod=2):
self._stopper = Event()
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
from auxilary import async, ConfigFile
from auxilary import ConfigFile
from exceptionThreading import async
from smtplib import SMTP
from datetime import datetime
from email.mime.multipart import MIMEMultipart

View File

@ -1,5 +1,5 @@
import logging, os, sys, stat
from threading import Thread
from exceptionThreading import ExceptionThread
from evdev import InputDevice, ecodes
from select import select
from auxilary import waitForPath
@ -7,7 +7,7 @@ import stateMachine
logger = logging.getLogger(__name__)
class KeypadListener(Thread):
class KeypadListener(ExceptionThread):
def __init__(self, stateMachine, callbackDisarm, callbackArm, soundLib, passwd):
ctrlKeys = { 69: 'NUML', 98: '/', 14: 'BS', 96: 'ENTER'}
@ -115,7 +115,7 @@ class KeypadListener(Thread):
# TODO: these are not threadsafe
# TODO: this code gets really confused if the pipe is deleted
class PipeListener(Thread):
class PipeListener(ExceptionThread):
def __init__(self, callback, 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 stateMachine import StateMachine
from exceptionThreading import excChildListener, excStopper
logger = logging.getLogger(__name__)
@ -23,6 +24,7 @@ def clean():
logger.critical(traceback.format_exc())
def sigtermHandler(signum, stackFrame):
excStopper.set()
logger.info('Caught SIGTERM')
raise SystemExit
@ -39,8 +41,7 @@ if __name__ == '__main__':
#~ signal.signal(signal.SIGSEGV, sig_handler)
signal.signal(signal.SIGTERM, sigtermHandler)
while 1:
time.sleep(31536000)
excChildListener()
except Exception:
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 subprocess import call
from collections import OrderedDict
from auxilary import async
from queue import Queue
logger = logging.getLogger(__name__)
@ -89,8 +89,8 @@ class SoundLib:
self.volume = 100
self._applyVolumesToSounds(self.volume)
self._ttsQueue = Queue()
self._stop = threading.Event()
self._ttsQueue = queue.Queue()
self._stop = Event()
self._startMonitor()
def changeVolume(self, volumeDelta):
@ -187,7 +187,7 @@ class SoundLib:
logger.debug('TTS engine received "%s"', text)
def _startMonitor(self):
self._thread = t = threading.Thread(target=self._ttsMonitor, daemon=True)
self._thread = t = ExceptionThread(target=self._ttsMonitor, daemon=True)
t.start()
logger.debug('Starting TTS Queue Monitor')

View File

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

View File

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