add support for exception-aware threading
This commit is contained in:
parent
a4a1671650
commit
db3fde9be3
25
auxilary.py
25
auxilary.py
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
3
gmail.py
3
gmail.py
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
7
main.py
7
main.py
|
@ -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
|
||||
|
||||
|
@ -32,15 +34,14 @@ if __name__ == '__main__':
|
|||
|
||||
GPIO.setwarnings(False)
|
||||
GPIO.setmode(GPIO.BCM)
|
||||
|
||||
|
||||
stateMachine = StateMachine()
|
||||
|
||||
# TODO: segfaults are annoying :(
|
||||
#~ signal.signal(signal.SIGSEGV, sig_handler)
|
||||
signal.signal(signal.SIGTERM, sigtermHandler)
|
||||
|
||||
while 1:
|
||||
time.sleep(31536000)
|
||||
excChildListener()
|
||||
|
||||
except Exception:
|
||||
logger.critical(traceback.format_exc())
|
||||
|
|
12
soundLib.py
12
soundLib.py
|
@ -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')
|
||||
|
||||
|
|
|
@ -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__)
|
||||
|
|
|
@ -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__)
|
||||
|
||||
|
|
Loading…
Reference in New Issue