dispatchrun / dispatch-py

Python package to develop applications with Dispatch.
https://pypi.org/project/dispatch-py/
Apache License 2.0
56 stars 3 forks source link

Preserve traceback information when propagating errors through await points on durable coroutines #68

Closed achille-roussel closed 8 months ago

achille-roussel commented 9 months ago

Python uses stack traces to track down where exceptions originated from. When an exception bubbles up to the top of a durable coroutine call, and it is interpreted as a fatal error, the coroutine exits and passes the exception to the caller. In this process, we lose the traceback information about the location where the exception was raised, which makes it difficult to track down the origin of an error.

For example, this is the stack trace for a leaf function call:

Traceback (most recent call last):
  File "/Users/achilleroussel/go/src/github.com/stealthrocket/dispatch-sdk-python/src/dispatch/scheduler.py", line 162, in run
    return self._run(input)
           ^^^^^^^^^^^^^^^^
  File "/Users/achilleroussel/go/src/github.com/stealthrocket/dispatch-sdk-python/src/dispatch/scheduler.py", line 251, in _run
    coroutine_yield = coroutine.run()
                      ^^^^^^^^^^^^^^^
  File "/Users/achilleroussel/go/src/github.com/stealthrocket/dispatch-sdk-python/src/dispatch/scheduler.py", line 116, in run
    return self.coroutine.send(None)
           ^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/Users/achilleroussel/go/src/github.com/stealthrocket/dispatch-sdk-python/src/dispatch/experimental/durable/function.py", line 218, in send
    return self.coroutine.send(send)
           ^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/Users/achilleroussel/go/src/github.com/stealthrocket/dispatch-sdk-python/main.py", line 10, in get
    raise ValueError("This is an error")

And this is the stack trace for the awaiting caller:

Traceback (most recent call last):
  File "/Users/achilleroussel/go/src/github.com/stealthrocket/dispatch-sdk-python/src/dispatch/scheduler.py", line 162, in run
    return self._run(input)
           ^^^^^^^^^^^^^^^^
  File "/Users/achilleroussel/go/src/github.com/stealthrocket/dispatch-sdk-python/src/dispatch/scheduler.py", line 251, in _run
    coroutine_yield = coroutine.run()
                      ^^^^^^^^^^^^^^^
  File "/Users/achilleroussel/go/src/github.com/stealthrocket/dispatch-sdk-python/src/dispatch/scheduler.py", line 120, in run
    return self.coroutine.throw(error)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/Users/achilleroussel/go/src/github.com/stealthrocket/dispatch-sdk-python/src/dispatch/experimental/durable/function.py", line 221, in throw
    return self.coroutine.throw(typ, val, tb)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/Users/achilleroussel/go/src/github.com/stealthrocket/dispatch-sdk-python/main.py", line 15, in publish
    r1 = await get.call('this')
         ^^^^^^^^^^^^^^^^^^^^^^
  File "/Users/achilleroussel/go/src/github.com/stealthrocket/dispatch-sdk-python/src/dispatch/experimental/durable/function.py", line 285, in throw
    return self.generator.throw(typ, val, tb)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/Users/achilleroussel/go/src/github.com/stealthrocket/dispatch-sdk-python/src/dispatch/function.py", line 92, in _call_async
    return await dispatch.coroutine.call(
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/opt/homebrew/Cellar/python@3.11/3.11.7_1/Frameworks/Python.framework/Versions/3.11/lib/python3.11/types.py", line 220, in throw
    return self.__wrapped.throw(tp, *rest)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/Users/achilleroussel/go/src/github.com/stealthrocket/dispatch-sdk-python/src/dispatch/experimental/durable/function.py", line 285, in throw
    return self.generator.throw(typ, val, tb)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/Users/achilleroussel/go/src/github.com/stealthrocket/dispatch-sdk-python/src/dispatch/coroutine.py", line 14, in call
    return (yield call)

Bonus to go even further into helping users, we could try to figure out how to remove the stack frames within the SDK to keep the information focused on details relevant to the user.