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`]
     ///