Mercurial > public > mercurial-scm > hg-stable
changeset 52797:5e3e8876fd9e
rust-pyo3-revlog: index and nodetree read accessor helpers
These are similar to what Python calls context managers: they
enclose neatly the intermediate `self_ref` and will allow writing
caller code that focuses on its own application logic.
author | Georges Racinet <georges.racinet@cloudcrane.io> |
---|---|
date | Wed, 25 Dec 2024 15:38:18 +0100 |
parents | 07740bd86fd9 |
children | 71ebe880f24a |
files | rust/hg-pyo3/src/exceptions.rs rust/hg-pyo3/src/revlog/mod.rs |
diffstat | 2 files changed, 62 insertions(+), 6 deletions(-) [+] |
line wrap: on
line diff
--- a/rust/hg-pyo3/src/exceptions.rs Sun Dec 22 20:26:57 2024 +0100 +++ b/rust/hg-pyo3/src/exceptions.rs Wed Dec 25 15:38:18 2024 +0100 @@ -42,10 +42,10 @@ PyRuntimeError::new_err(format!("In Rust PyO3 bindings: {e}")) } -/// Submodule to hold Mercurial errors defined on the Python side -/// -/// This is better for clarity, as many hg-core errors have the same names -/// as their Python world counterparts +pub fn map_try_lock_error<T>(e: std::sync::TryLockError<T>) -> PyErr { + PyRuntimeError::new_err(format!("In Rust PyO3 bindings: {e}")) +} + pub mod mercurial_py_errors { pyo3::import_exception!(mercurial.error, RevlogError); }
--- a/rust/hg-pyo3/src/revlog/mod.rs Sun Dec 22 20:26:57 2024 +0100 +++ b/rust/hg-pyo3/src/revlog/mod.rs Wed Dec 25 15:38:18 2024 +0100 @@ -11,7 +11,7 @@ use pyo3::types::{PyBytes, PyBytesMethods, PyList}; use pyo3_sharedref::PyShareable; -use std::sync::{atomic::AtomicUsize, RwLock}; +use std::sync::{atomic::AtomicUsize, RwLock, RwLockReadGuard}; use hg::{ revlog::{ @@ -25,7 +25,10 @@ }; use crate::{ - exceptions::{map_lock_error, nodemap_error, revlog_error_from_msg}, + exceptions::{ + map_lock_error, map_try_lock_error, nodemap_error, + revlog_error_from_msg, + }, store::PyFnCache, util::{new_submodule, take_buffer_with_slice}, }; @@ -129,6 +132,59 @@ } impl InnerRevlog { + /// Take the lock on `slf.irl` for reading and call a closure. + /// + /// This serves the purpose to keep the needed intermediate [`PyRef`] + /// that must be obtained to access the data from the [`Bound`] reference + /// and of which the locked [`CoreInnerRevlog`] depends. + /// This also provides releasing of the [`PyRef`] as soon as the closure + /// is done, which is crucial if the caller needs to obtain a [`PyRefMut`] + /// later on. + /// + /// In the closure, we hand back the intermediate [`PyRef`] that + /// has been generated so that the closure can access more attributes. + fn with_core_read<'py, T>( + slf: &Bound<'py, Self>, + f: impl FnOnce( + &PyRef<'py, Self>, + RwLockReadGuard<CoreInnerRevlog>, + ) -> PyResult<T>, + ) -> PyResult<T> { + let self_ref = slf.borrow(); + // Safety: the owner is the right one. We will anyway + // not actually `share` it. Perhaps pyo3-sharedref should provide + // something less scary for this kind of usage. + let shareable_ref = unsafe { self_ref.irl.borrow_with_owner(slf) }; + let guard = shareable_ref.try_read().map_err(map_try_lock_error)?; + f(&self_ref, guard) + } + + #[allow(dead_code)] + fn with_index_read<T>( + slf: &Bound<'_, Self>, + f: impl FnOnce(&Index) -> PyResult<T>, + ) -> PyResult<T> { + Self::with_core_read(slf, |_, guard| f(&guard.index)) + } + + /// Lock `slf` for reading and execute a closure on its [`Index`] and + /// [`NodeTree`] + /// + /// The [`NodeTree`] is initialized an filled before hand if needed. + #[allow(dead_code)] + fn with_index_nt_read<T>( + slf: &Bound<'_, Self>, + f: impl FnOnce(&Index, &CoreNodeTree) -> PyResult<T>, + ) -> PyResult<T> { + Self::with_core_read(slf, |self_ref, guard| { + let idx = &guard.index; + let nt = + self_ref.get_nodetree(idx)?.read().map_err(map_lock_error)?; + let nt = nt.as_ref().expect("nodetree should be set"); + f(idx, nt) + }) + } + /// Fill a [`CoreNodeTree`] by doing a full iteration on the given /// [`Index`] ///