import threading
import time
import inspect
import ctypes
from queue import Queue
class ThreadTTLMonitor(threading.Thread):
def __init__(self, threads, ttl=600, delay=0.1, **kwargs):
threading.Thread.__init__(self, **kwargs)
self.setDaemon(True)
self._threads = threads
self.delay = delay
self.name = 'ThreadMonitor'
#
self._thread_timer_queue = Queue()
for thread in threads:
self._thread_timer_queue.put({'thread': thread, 'ttl': ttl})
#
self._dismissed = threading.Event()
self.start()
def create_monitor(self, thread, ttl=600):
self._thread_timer_queue.put({'thread': thread, 'ttl': ttl})
@staticmethod
def _async_raise(thread_id, exc_type):
"""raises the exception, performs cleanup if needed"""
thread_id = ctypes.c_long(thread_id)
if not inspect.isclass(exc_type):
exc_type = type(exc_type)
res = ctypes.pythonapi.PyThreadState_SetAsyncExc(thread_id, ctypes.py_object(exc_type))
if res == 0:
raise Exception("invalid thread id")
elif res != 1:
ctypes.pythonapi.PyThreadState_SetAsyncExc(thread_id, None)
raise Exception("PyThreadState_SetAsyncExc failed")
@classmethod
def stop_thread(cls, thread):
if not thread.is_alive():
return
cls._async_raise(thread.ident, Exception)
def dismiss(self):
"""Sets a flag to tell the thread to exit when done with current job.
"""
self._dismissed.set()
def run(self):
while True:
try:
if self._dismissed.isSet():
break
item = self._thread_timer_queue.get()
if not item:
time.sleep(self.delay)
continue
thread = item.get('thread')
ttl = item.get('ttl')
ttl -= self.delay
if ttl <= 0:
self.stop_thread(thread)
continue
item.update(ttl=ttl)
self._thread_timer_queue.put(item)
except Exception as e:
raise e
time.sleep(self.delay)