changeset 52983:69d40a9778fe

pyo3: add a util to handle SIGINT from a long-running Rust function This is going to be useful for the upcoming `update` module, and is the transliteration of the util of the same name in `hg-cpython`. Explanations are inline.
author Rapha?l Gom?s <rgomes@octobus.net>
date Tue, 18 Feb 2025 11:44:21 +0100
parents 0c7ac026ed63
children a39680ec3e76
files rust/hg-pyo3/src/utils.rs
diffstat 1 files changed, 30 insertions(+), 0 deletions(-) [+]
line wrap: on
line diff
--- a/rust/hg-pyo3/src/utils.rs	Tue Feb 18 11:39:17 2025 +0100
+++ b/rust/hg-pyo3/src/utils.rs	Tue Feb 18 11:44:21 2025 +0100
@@ -343,3 +343,33 @@
     }
 }
 
+/// Wrap a call to `func` so that Python's `SIGINT` handler is first stored,
+/// then restored after the call to `func` and finally raised if
+/// `func` returns a [`HgError::InterruptReceived`].
+///
+/// We cannot use [`Python::check_signals`] because it only works from the main
+/// thread of the main interpreter. To that end, long-running Rust functions
+/// need to cooperate by listening to their own `SIGINT` signal and return
+/// the appropriate error on catching that signal: this is especially helpful
+/// in multithreaded operations.
+pub fn with_sigint_wrapper<R>(
+    py: Python,
+    func: impl Fn() -> Result<R, HgError>,
+) -> PyResult<Result<R, HgError>> {
+    let signal_py_mod = py.import(intern!(py, "signal"))?;
+    let sigint_py_const = signal_py_mod.getattr(intern!(py, "SIGINT"))?;
+    let old_handler = signal_py_mod
+        .call_method1(intern!(py, "getsignal"), (sigint_py_const.clone(),))?;
+    let res = func();
+    // Reset the old signal handler in Python because we may have changed it
+    signal_py_mod.call_method1(
+        intern!(py, "signal"),
+        (sigint_py_const.clone(), old_handler),
+    )?;
+    if let Err(HgError::InterruptReceived) = res {
+        // Trigger the signal in Python
+        signal_py_mod
+            .call_method1(intern!(py, "raise_signal"), (sigint_py_const,))?;
+    }
+    Ok(res)
+}