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 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
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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
|
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
|
||||||
|
|
|
@ -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
|
||||||
|
|
||||||
|
|
5
main.py
5
main.py
|
@ -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
|
||||||
|
|
||||||
|
@ -39,8 +41,7 @@ if __name__ == '__main__':
|
||||||
#~ 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())
|
||||||
|
|
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 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')
|
||||||
|
|
||||||
|
|
|
@ -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__)
|
||||||
|
|
|
@ -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__)
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue