Added support for PyErr_WriteUnraisable
This commit is contained in:
parent
d118ee3a73
commit
066880e7d5
|
@ -0,0 +1 @@
|
|||
Added `PyErr::write_unraisable()` to report an unraisable exception to Python.
|
|
@ -476,6 +476,40 @@ impl PyErr {
|
|||
unsafe { ffi::PyErr_Restore(ptype, pvalue, ptraceback) }
|
||||
}
|
||||
|
||||
/// Reports the error as unraisable.
|
||||
///
|
||||
/// This calls `sys.unraisablehook()` using the current exception and obj argument.
|
||||
///
|
||||
/// This method is useful to report errors in situations where there is no good mechanism
|
||||
/// to report back to the Python land. In Python this is used to indicate errors in
|
||||
/// background threads or destructors which are protected. In Rust code this is commonly
|
||||
/// useful when you are calling into a Python callback which might fail, but there is no
|
||||
/// obvious way to handle this error other than logging it.
|
||||
///
|
||||
/// Calling this method has the benefit that the error goes back into a standardized callback
|
||||
/// in Python which for instance allows unittests to ensure that no unraisable error
|
||||
/// actually happend by hooking `sys.unraisablehook`.
|
||||
///
|
||||
/// Example:
|
||||
/// ```rust
|
||||
/// # use pyo3::prelude::*;
|
||||
/// # use pyo3::exceptions::PyRuntimeError;
|
||||
/// # fn failing_function() -> PyResult<()> { Err(PyRuntimeError::new_err("foo")) }
|
||||
/// # fn main() -> PyResult<()> {
|
||||
/// Python::with_gil(|py| {
|
||||
/// match failing_function() {
|
||||
/// Err(pyerr) => pyerr.write_unraisable(py, None),
|
||||
/// Ok(..) => { /* do something here */ }
|
||||
/// }
|
||||
/// Ok(())
|
||||
/// })
|
||||
/// # }
|
||||
#[inline]
|
||||
pub fn write_unraisable(self, py: Python<'_>, obj: Option<&PyAny>) {
|
||||
self.restore(py);
|
||||
unsafe { ffi::PyErr_WriteUnraisable(obj.map_or(std::ptr::null_mut(), |x| x.as_ptr())) }
|
||||
}
|
||||
|
||||
/// Issues a warning message.
|
||||
///
|
||||
/// May return an `Err(PyErr)` if warnings-as-errors is enabled.
|
||||
|
|
|
@ -247,8 +247,7 @@ where
|
|||
if let Err(py_err) = panic::catch_unwind(move || body(py))
|
||||
.unwrap_or_else(|payload| Err(PanicException::from_panic_payload(payload)))
|
||||
{
|
||||
py_err.restore(py);
|
||||
ffi::PyErr_WriteUnraisable(ctx);
|
||||
py_err.write_unraisable(py, py.from_borrowed_ptr_or_opt(ctx));
|
||||
}
|
||||
trap.disarm();
|
||||
}
|
||||
|
|
|
@ -96,3 +96,50 @@ fn test_exception_nosegfault() {
|
|||
assert!(io_err().is_err());
|
||||
assert!(parse_int().is_err());
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[cfg(Py_3_8)]
|
||||
fn test_write_unraisable() {
|
||||
use pyo3::{exceptions::PyRuntimeError, ffi, AsPyPointer};
|
||||
|
||||
#[pyclass]
|
||||
struct UnraisableCapture {
|
||||
capture: Option<(PyErr, PyObject)>,
|
||||
}
|
||||
|
||||
#[pymethods]
|
||||
impl UnraisableCapture {
|
||||
fn hook(&mut self, unraisable: &PyAny) {
|
||||
let err = PyErr::from_value(unraisable.getattr("exc_value").unwrap());
|
||||
let instance = unraisable.getattr("object").unwrap();
|
||||
self.capture = Some((err, instance.into()));
|
||||
}
|
||||
}
|
||||
|
||||
Python::with_gil(|py| {
|
||||
let sys = py.import("sys").unwrap();
|
||||
let old_hook = sys.getattr("unraisablehook").unwrap();
|
||||
let capture = Py::new(py, UnraisableCapture { capture: None }).unwrap();
|
||||
|
||||
sys.setattr("unraisablehook", capture.getattr(py, "hook").unwrap())
|
||||
.unwrap();
|
||||
|
||||
assert!(capture.borrow(py).capture.is_none());
|
||||
|
||||
let err = PyRuntimeError::new_err("foo");
|
||||
err.write_unraisable(py, None);
|
||||
|
||||
let (err, object) = capture.borrow_mut(py).capture.take().unwrap();
|
||||
assert_eq!(err.to_string(), "RuntimeError: foo");
|
||||
assert!(object.is_none(py));
|
||||
|
||||
let err = PyRuntimeError::new_err("bar");
|
||||
err.write_unraisable(py, Some(py.NotImplemented().as_ref(py)));
|
||||
|
||||
let (err, object) = capture.borrow_mut(py).capture.take().unwrap();
|
||||
assert_eq!(err.to_string(), "RuntimeError: bar");
|
||||
assert!(object.as_ptr() == unsafe { ffi::Py_NotImplemented() });
|
||||
|
||||
sys.setattr("unraisablehook", old_hook).unwrap();
|
||||
});
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue