Error handling and debuging#

During the design of a transformation pipeline, mistakes and programming errors are relatively frequent. SeqTools tries to recover from them and report useful informations as much as possible. This tutorial reviews some details about the internal error management and should facilitate your debugging sessions.

Tracing mapping errors#

Due to on-demand execution, an error generated by mapping a function to an item won’t raise when the mapping is created but rather when the problematic element is read.

[2]:
import math
import seqtools


def f1(x):
    return math.sqrt(x)  # this will fail for negative values


data = [0, 4, 6, 7, 2, 4, 4, -1]  # sqrt(-1) raises ValueError
out = seqtools.smap(f1, data)

Due to on-demand execution, no error is raised yet for the last item.

As soon as it is evaluated, SeqTools raises an EvaluationError and sets the original exception as its cause.

[3]:
list(out)
---------------------------------------------------------------------------
ValueError                                Traceback (most recent call last)
File ~/checkouts/readthedocs.org/user_builds/seqtools-doc/envs/stable/lib/python3.12/site-packages/seqtools/mapping.py:23, in Mapping.__iter__(self)
     22 for args in zip(*self.sequences):
---> 23     yield self.f(*args)
     24     i += 1

Cell In[2], line 6, in f1(x)
      5 def f1(x):
----> 6     return math.sqrt(x)

ValueError: math domain error

The above exception was the direct cause of the following exception:

EvaluationError                           Traceback (most recent call last)
Cell In[3], line 1
----> 1 list(out)

File ~/checkouts/readthedocs.org/user_builds/seqtools-doc/envs/stable/lib/python3.12/site-packages/seqtools/mapping.py:33, in Mapping.__iter__(self)
     29 else:
     30     msg = "Failed to evaluate item {} in {} created at:\n{}".format(
     31         i, self.__class__.__name__, self.stack
     32     )
---> 33     raise EvaluationError(msg) from error

EvaluationError: Failed to evaluate item 7 in Mapping created at:
  File "/tmp/ipykernel_660/2020489251.py", line 10, in <module>
    out = seqtools.smap(f1, data)

The ValueError that caused the failure is detailed first.

The EvaluationError message provides additional clarification: it tells which item caused the error and where the mapping was defined, a crucial debugging information when the mapping function is used at multiple locations in the code.

If you prefer working with the original exception directly and skip the EvaluationError wrapper, you can enable the ‘passthrough’ error mode which does just that:

[4]:
seqtools.seterr(evaluation="passthrough")

list(out)
---------------------------------------------------------------------------
ValueError                                Traceback (most recent call last)
Cell In[4], line 3
      1 seqtools.seterr(evaluation="passthrough")
----> 3 list(out)

File ~/checkouts/readthedocs.org/user_builds/seqtools-doc/envs/stable/lib/python3.12/site-packages/seqtools/mapping.py:23, in Mapping.__iter__(self)
     21 try:
     22     for args in zip(*self.sequences):
---> 23         yield self.f(*args)
     24         i += 1
     26 except Exception as error:

Cell In[2], line 6, in f1(x)
      5 def f1(x):
----> 6     return math.sqrt(x)

ValueError: math domain error
[5]:
seqtools.seterr(evaluation="wrap")  # revert to normal behaviour
[5]:
'wrap'

Errors inside worker#

Background workers used by prefetch do not share the execution space of the main program and exceptions raised while evaluating elements will happen asynchronously.

To facilitate troubleshooting, SeqTools silently stores exception data, sends it back to the main process to be re-raised when failed items are read. In practice it looks like exceptions happen when the items are read.

[6]:
out = seqtools.smap(f1, data)
out = seqtools.prefetch(out, max_buffered=10)

# evaluate all elements but the last
for i in range(len(out) - 1):
    out[i]

# evaluate the final one
out[-1]
---------------------------------------------------------------------------
ValueError                                Traceback (most recent call last)
File ~/checkouts/readthedocs.org/user_builds/seqtools-doc/envs/stable/lib/python3.12/site-packages/seqtools/mapping.py:38, in Mapping.__getitem__(self, item)
     37 try:
---> 38     return self.f(*(l[item] for l in self.sequences))
     40 except Exception as cause:

Cell In[2], line 6, in f1(x)
      5 def f1(x):
----> 6     return math.sqrt(x)

ValueError: math domain error

The above exception was the direct cause of the following exception:

EvaluationError                           Traceback (most recent call last)
Cell In[6], line 9
      6     out[i]
      8 # evaluate the final one
----> 9 out[-1]

File ~/checkouts/readthedocs.org/user_builds/seqtools-doc/envs/stable/lib/python3.12/site-packages/seqtools/utils.py:55, in basic_getitem.<locals>.getitem(self, key)
     52         if key >= size:
     53             raise IndexError(self.__class__.__name__ + " index out of range")
---> 55     return func(self, key)
     57 else:
     58     raise TypeError(
     59         self.__class__.__name__ + " indices must be integers or "
     60         "slices, not " + key.__class__.__name__
     61     )

File ~/checkouts/readthedocs.org/user_builds/seqtools-doc/envs/stable/lib/python3.12/site-packages/seqtools/mapping.py:47, in Mapping.__getitem__(self, item)
     43 else:
     44     msg = "Failed to evaluate item {} in {} created at:\n{}".format(
     45         item, self.__class__.__name__, self.stack
     46     )
---> 47     raise EvaluationError(msg) from cause

EvaluationError: Failed to evaluate item 7 in Mapping created at:
  File "/tmp/ipykernel_660/3884726540.py", line 1, in <module>
    out = seqtools.smap(f1, data)

Note that the workers will continue processing other items just fine after an error.

Transfering exceptions back to the parent process has some notable limitations:

  • Process-based workers cannot save errors that cannot be pickled, in particular exception types defined inside a function. These will be replaced by an text message.

  • Error tracebacks cannot be completely serialized so debuggers won’t be able to explore the whole error context.