Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

pyo3-error crate #4500

Open
juntyr opened this issue Aug 29, 2024 · 2 comments
Open

pyo3-error crate #4500

juntyr opened this issue Aug 29, 2024 · 2 comments

Comments

@juntyr
Copy link
Contributor

juntyr commented Aug 29, 2024

I've recently been down a rabbit hole to improve error reporting across Python and Rust with preserved causality chains and just published the first version of my new https://github.com/juntyr/pyo3-error crate.

Most importantly, the crate provides conversions from std::error::Error::source causal chains to pyo3::PyErr::cause chains and a wrapper for PyErr to interpret its pyo3::PyErr::cause chain as a std::error::Error::source chain.

I hope that this crate might be useful to the broader PyO3 community (and I am sure someone will have ideas for how to make its API a lot more convenient :) )

@bschoenmaeckers
Copy link
Contributor

What is the performance impact of converting all source errors into rust compared to a plain PyErr?

@juntyr
Copy link
Contributor Author

juntyr commented Aug 29, 2024

What is the performance impact of converting all source errors into rust compared to a plain PyErr?

At the moment, this conversion scales linearly with the length of the causal chain. std::error::Error::source returns a reference lifetime-bound to the original error, but PyErr::cause returns an unbound PyErr. Therefore, we need to materialize the full chain into a second chain on the Rust side (broadly speaking, struct PyErrChain { err: PyErr, source: Option<Box<PyErrChain>> }). Since PyErr can modify its cause through a clone, this materialization is also just a snapshot.

An alternative design I could think of is one that would provide a zero-cost wrapper around PyErr and push all cost to when source is called. Every error down the source chain would still point to the original top-level PyErr but use type-system-level-only layers of wrapping to denote the chain depth. However, this would mean that when you have a reference to the 3rd cause in the chain and call it's source method, it would have to traverse all the way from the top-level back down to the 4th cause layer. Furthermore, this approach might need to panic if the causal chain has been modified in Python since wrapping it in Python.

Since I personally prefer the snapshot approach, I went with that one for the initial implementation.

I'd be delighted to hear if you have some ideas for how the API and its cost could be improved :)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants