This PR fixes error propagation through the call stack of durable coroutines.
I have added a dependency on the tblib package to deal with parsing stack traces and reconstructing traceback objects which is quite tricky to do since these values can only be constructed by the interpreter when raising exceptions (see).
Using this code example:
from fastapi import FastAPI
from dispatch.fastapi import Dispatch
app = FastAPI()
dispatch = Dispatch(app)
@dispatch.coroutine()
async def get(something):
raise ValueError("This is an error")
@dispatch.coroutine()
async def publish():
return await get.call("this")
@app.get("/")
def root():
publish.dispatch()
return "OK"
The execution produces these logs:
INFO: Started server process [22270]
INFO: Waiting for application startup.
INFO: Application startup complete.
INFO: Uvicorn running on http://127.0.0.1:8000 (Press CTRL+C to quit)
INFO: 127.0.0.1:52112 - "GET / HTTP/1.1" 200 OK
INFO: 127.0.0.1:52119 - "POST /dispatch.sdk.v1.FunctionService/Run HTTP/1.1" 200 OK
@dispatch.coroutine: 'get' raised an exception
Traceback (most recent call last):
File "/Users/achilleroussel/go/src/github.com/stealthrocket/dispatch-sdk-python/src/dispatch/scheduler.py", line 252, in _run
coroutine_yield = coroutine.run()
^^^^^^^^^^^^^^^
File "/Users/achilleroussel/go/src/github.com/stealthrocket/dispatch-sdk-python/src/dispatch/scheduler.py", line 115, 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 14, in get
raise ValueError("This is an error")
ValueError: This is an error
INFO: 127.0.0.1:52119 - "POST /dispatch.sdk.v1.FunctionService/Run HTTP/1.1" 200 OK
@dispatch.coroutine: 'publish' raised an exception
Traceback (most recent call last):
File "/Users/achilleroussel/go/src/github.com/stealthrocket/dispatch-sdk-python/src/dispatch/scheduler.py", line 252, in _run
coroutine_yield = coroutine.run()
^^^^^^^^^^^^^^^
File "/Users/achilleroussel/go/src/github.com/stealthrocket/dispatch-sdk-python/src/dispatch/scheduler.py", line 118, 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 19, in publish
return 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)
^^^^^^^^^^
File "/Users/achilleroussel/go/src/github.com/stealthrocket/dispatch-sdk-python/src/dispatch/scheduler.py", line 252, in _run
coroutine_yield = coroutine.run()
^^^^^^^^^^^^^^^^^
File "/Users/achilleroussel/go/src/github.com/stealthrocket/dispatch-sdk-python/src/dispatch/scheduler.py", line 115, 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 14, in get
raise ValueError("This is an error")
^^^^^^^^^^^^^^^^^
ValueError: This is an error
INFO: 127.0.0.1:52119 - "POST /dispatch.sdk.v1.FunctionService/Run HTTP/1.1" 200 OK
As we can see here, the original exception traceback is preserved and shown when re-raising the exception in the caller.
Please take a look and let me know if anything should be changed.
This PR fixes error propagation through the call stack of durable coroutines.
I have added a dependency on the tblib package to deal with parsing stack traces and reconstructing traceback objects which is quite tricky to do since these values can only be constructed by the interpreter when raising exceptions (see).
Using this code example:
The execution produces these logs:
As we can see here, the original exception traceback is preserved and shown when re-raising the exception in the caller.
Please take a look and let me know if anything should be changed.
Fixes #68