From 9b90641b0c981a047ab1bdf77e492a9f56cd728a Mon Sep 17 00:00:00 2001
From: Paul McCarthy <pauldmccarthy@gmail.com>
Date: Wed, 10 Feb 2021 12:19:26 +0000
Subject: [PATCH] ENH: TaskThread accepts an onError function which is called
 on idle loop if queued task raises an error.

---
 fsl/utils/idle.py | 25 +++++++++++++++++++------
 1 file changed, 19 insertions(+), 6 deletions(-)

diff --git a/fsl/utils/idle.py b/fsl/utils/idle.py
index bef726633..f8ff25349 100644
--- a/fsl/utils/idle.py
+++ b/fsl/utils/idle.py
@@ -759,10 +759,11 @@ class Task(object):
     """Container object which encapsulates a task that is run by a
     :class:`TaskThread`.
     """
-    def __init__(self, name, func, onFinish, args, kwargs):
+    def __init__(self, name, func, onFinish, onError, args, kwargs):
         self.name     = name
         self.func     = func
         self.onFinish = onFinish
+        self.onError  = onError
         self.args     = args
         self.kwargs   = kwargs
         self.enabled  = True
@@ -808,9 +809,16 @@ class TaskThread(threading.Thread):
 
         :arg onFinish: An optional function to be called (via :func:`idle`)
                        when the task funtion has finished. Must be provided as
-                       a keyword argument. If the ``func`` raises a
-                       :class`TaskThreadVeto` error, this function will not
-                       be called.
+                       a keyword argument, and must itself accept no arguments.
+                       If the ``func`` raises a :class`TaskThreadVeto` error,
+                       this function will not be called.
+
+        :arg onError:  An optional function to be called (via :func:`idle`)
+                       if the task funtion raises an ``Exception``. Must be
+                       provided as a keyword argument, and must itself accept
+                       the raised ``Exception`` object as a single argument.
+                       If the ``func`` raises a :class`TaskThreadVeto` error,
+                       this function will not be called.
 
         All other arguments are passed through to the task function when it is
         executed.
@@ -821,16 +829,18 @@ class TaskThread(threading.Thread):
                   results.
 
         .. warning:: Make sure that your task function is not expecting keyword
-                     arguments called ``taskName`` or ``onFinish``!
+                     arguments called ``taskName``, ``onFinish``, or
+                     ``onError``!
         """
 
         name     = kwargs.pop('taskName', None)
         onFinish = kwargs.pop('onFinish', None)
+        onError  = kwargs.pop('onError',  None)
 
         log.debug('Enqueueing task: {} [{}]'.format(
             name, getattr(func, '__name__', '<unknown>')))
 
-        t = Task(name, func, onFinish, args, kwargs)
+        t = Task(name, func, onFinish, onError, args, kwargs)
         self.__enqueued[name] = t
         self.__q.put(t)
 
@@ -951,6 +961,9 @@ class TaskThread(threading.Thread):
                     type(e).__name__,
                     str(e)),
                     exc_info=True)
+                if task.onError is not None:
+                    idle(task.onError, e)
+
             finally:
                 self.__q.task_done()
 
-- 
GitLab