import threading
import multiprocessing
import traceback
import logging


class FunctionNameAlreadyExists(Exception):
    def __init__(self, eventName):
        self.name = eventName
    def __str__(self):
        return self.name

class ThreadTemplate(threading.Thread):
    def __init__(self, name, function, pipe_Thread, pipe_Main, baseReference = None):
        threading.Thread.__init__(self)
        self.function = function
        self.name = name

        # MainPipe and ThreadPipe are the two ends of the same pipe.
        # Data sent on MainPipe can be received on ThreadPipe, and vice versa.
        # MainPipe is used by the bot framework. ThreadPipe is used by the thread.
        self.MainPipe = pipe_Main
        self.ThreadPipe = pipe_Thread

        self.running = False
        self.signal = False

        self.base = baseReference

    def run(self):
        self.running = True
        try:
            self.function(self, self.ThreadPipe)
        except Exception as error:
            self.ThreadPipe.send({
                                  "action" : "exceptionOccured", "exception" : error,
                                  "functionName" : self.name, "traceback" : str(traceback.format_exc())
                                  })

        self.running = False

class ThreadPool():
    def __init__(self):
        self.pool = {}
        self.__threadPool_log__ = logging.getLogger("ThreadPool")

    def addThread(self, name, function, baseReference = None):
        MainPipe, ThreadPipe = multiprocessing.Pipe(True)

        if name in self.pool:
            raise FunctionNameAlreadyExists("The name is already used by a different thread function!")

        thread = ThreadTemplate(name, function, ThreadPipe, MainPipe, baseReference)
        self.pool[name] = {"thread" : thread, "threadPipe" : ThreadPipe, "mainPipe" : MainPipe}
        self.pool[name]["thread"].start()
        self.__threadPool_log__.debug("New thread '%s' started", name)

    def sigquitThread(self, name):
        self.pool[name]["thread"].signal = True
        del self.pool[name]
        self.__threadPool_log__.debug("Sending SIGKILL to thread '%s'", name)

    def send(self, name, obj):
        self.pool[name]["mainPipe"].send(obj)

    def recv(self, name):
        return self.pool[name]["mainPipe"].recv()

    def poll(self, name, timeout=0.0):
        # if the timeout is 0.0 it returns immediately, according to the CPython source code
        return self.pool[name]["mainPipe"].poll(timeout)

    def checkStatus(self, name):
        if name not in self.pool:
            return False, None

        if name in self.pool:
            isRunning = self.pool[name]["thread"].running

            return True, isRunning

    def sigquitAll(self):
        self.__threadPool_log__.debug("Sending SIGKILL to all running threads")
        threads = [name for name in self.pool]

        for thread in threads:
            self.pool[thread]["thread"].signal = True
            del self.pool[thread]

        self.__threadPool_log__.debug("SIGKILL to all running threads sent")

@@ -66,16 +66,108 @@
 ogging%0A%0A
+from timeit import default_timer%0A%0A_threadlogger = logging.getLogger(%22ThreadPoolExceptions%22)%0A
 %0Aclass F
@@ -939,16 +939,115 @@
 erence%0A%0A
+        self._metadata = %7B%7D%0A        self._startTime = None%0A        self._lastTimeRunning = None%0A%0A%0A%0A
     def 
@@ -1182,16 +1182,64 @@
  error:%0A
+            exception = traceback.format_exc()%0A%0A
         
@@ -1431,82 +1431,591 @@
 %22 : 
-str(traceback.format_exc())%0A                                  %7D)%0A%0A
+exception%0A                                  %7D)%0A%0A            _threadlogger.warning(%22Thread '%25s' crashed! Exception follows.%22, self.name)%0A            _threadlogger.exception(%22Thread exception of '%25s':%22, self.name)%0A%0A        self.running = False%0A%0A    # Set the starting point for the timer.%0A    def setTimer(self):%0A        self._startTime = default_timer()%0A%0A    # Stop the timer and set the _lastTimeRunning variable%0A    def stopTimer(self):%0A        if self._startTime == None:%0A            raise RuntimeError(%22Can't stop the timer if it hasn't been started yet.%22)%0A        else:%0A 
         
 self
@@ -2010,36 +2010,506 @@
         
+   
 self.
-running = False
+_lastTimeRunning = default_timer() - self._startTime%0A            self._startTime = None%0A%0A    # Get the time difference between now and the starting point without stopping the timer.%0A    def getTimeDiff(self):%0A        if self._startTime == None:%0A            raise RuntimeError(%22Can't get time difference if timer hasn't been started yet.%22)%0A        else:%0A            return default_timer() - self._startTime%0A%0A    @property%0A    def timeDelta(self):%0A        return self._lastTimeRunning
 %0A%0Aclass 

Add thread stats, log thread exceptions