diff --git a/taskflow/exceptions.py b/taskflow/exceptions.py index 7f572ff3..39819a22 100644 --- a/taskflow/exceptions.py +++ b/taskflow/exceptions.py @@ -16,6 +16,10 @@ # License for the specific language governing permissions and limitations # under the License. +import StringIO + +import traceback + class TaskFlowException(Exception): """Base class for exceptions emitted from this library.""" @@ -27,6 +31,33 @@ class Duplicate(TaskFlowException): pass +class LinkedException(TaskFlowException): + """A linked chain of many exceptions.""" + def __init__(self, message, cause, tb): + super(LinkedException, self).__init__(message) + self.cause = cause + self.tb = tb + self.next = None + + @classmethod + def link(cls, exc_infos): + first = None + previous = None + for exc_info in exc_infos: + if not all(exc_info) or not len(exc_infos) == 3: + raise ValueError("Invalid exc_info") + buf = StringIO.StringIO() + traceback.print_exception(exc_info[0], exc_info[1], exc_info[2], + file=buf) + exc = cls(str(exc_info[1]), exc_info[1], buf.getvalue()) + if previous is not None: + previous.next = exc + else: + first = exc + previous = exc + return first + + class StorageError(TaskFlowException): """Raised when logbook can not be read/saved/deleted.""" diff --git a/taskflow/patterns/threaded_flow.py b/taskflow/patterns/threaded_flow.py index 6869b63f..e9e2380b 100644 --- a/taskflow/patterns/threaded_flow.py +++ b/taskflow/patterns/threaded_flow.py @@ -349,11 +349,12 @@ class Flow(flow.Flow): except exc.InvalidStateException: pass finally: - # TODO(harlowja): re-raise a combined exception when - # there are more than one failures?? - for f in failures: - if all(f.exc_info): - raise f.exc_info[0], f.exc_info[1], f.exc_info[2] + if len(failures) > 1: + exc_infos = [f.exc_info for f in failures] + raise exc.LinkedException.link(exc_infos) + else: + f = failures[0] + raise f.exc_info[0], f.exc_info[1], f.exc_info[2] def handle_results(): # Isolate each runner state into groups so that we can easily tell