How can I avoid losing data when accessing a Rust class via ffi? #2900
-
I'm working on implementing a python profiler in Rust, using #[cfg(not(PyPy))]
#[path = ""]
mod _kolo {
use pyo3::ffi;
use pyo3::prelude::*;
use serde_json::json;
extern "C" fn profile(
_obj: *mut ffi::PyObject,
_frame: *mut ffi::PyFrameObject,
what: i32,
_arg: *mut ffi::PyObject,
) -> i32 {
let event = {
if what == ffi::PyTrace_CALL {
"call"
} else if what == ffi::PyTrace_RETURN {
"return"
} else {
return 0;
}
};
let _frame = _frame as *mut ffi::PyObject;
Python::with_gil(|py| unsafe {
let obj = PyObject::from_borrowed_ptr_or_err(py, _obj);
let obj = match obj {
Ok(obj) => obj,
Err(err) => {
err.restore(py);
return -1;
}
};
let obj = obj.extract::<KoloProfiler>(py);
let mut obj = match obj {
Ok(obj) => obj,
Err(err) => {
err.restore(py);
return -1;
}
};
match obj.process_frame(event) {
Ok(_) => 0,
Err(err) => {
err.restore(py);
-1
}
}
})
}
#[pyclass(module = "kolo._kolo")]
#[derive(Clone)]
struct KoloProfiler {
frames: Vec<serde_json::Value>,
}
impl KoloProfiler {
fn process_frame(&mut self, event: &str) -> Result<(), PyErr> {
let frame_data = json!({
"event": event,
});
println!("{}", self.frames.len()); // Always 0
self.frames.push(frame_data);
println!("{}", self.frames.len()); // Always 1
Ok(())
}
}
#[pyfunction]
fn register_profiler() -> Result<(), PyErr> {
Python::with_gil(|py| {
let profiler = KoloProfiler { frames: Vec::new() };
unsafe {
ffi::PyEval_SetProfile(Some(profile), profiler.into_py(py).into_ptr());
}
Ok(())
})
}
#[pymodule]
fn _kolo(_py: Python, m: &PyModule) -> PyResult<()> {
m.add_function(wrap_pyfunction!(register_profiler, m)?)?;
Ok(())
}
}
#[cfg(not(PyPy))]
pub use _kolo::*; When I run this, the length of |
Beta Was this translation helpful? Give feedback.
Replies: 2 comments 2 replies
-
When you do What you probably want is to use (also, using |
Beta Was this translation helpful? Give feedback.
-
let obj = obj.extract::<PyRefMut<KoloProfiler>>(py); which will call |
Beta Was this translation helpful? Give feedback.
#[pyclass]
is currently always wrapped inside aPyCell
to support interior mutability in the face pervasive shared ownership in Python. The#[derive(Clone)]
should not be necessary at all AFAIU. I think you just need to extractPyRefMut<KoloProfiler>
instead of a plainKoloProfiler
which is a clone of the cell contents, e.g.which will call
PyCell::try_borrow_mut
behind the scenes.