diff rust/hg-cpython/src/utils.rs @ 52213:96b113d22b34 stable

rust-update: handle SIGINT from long-running update threads The current code does not respond to ^C until after the Rust bit is finished doing its work. This is expected, since Rust holds the GIL for the duration of the call and does not call `PyErr_CheckSignals`. Freeing the GIL to do our work does not really improve anything since the Rust threads are still going, and the only way of cancelling a thread is by making it cooperate. So we do the following: - remember the SIGINT handler in hg-cpython and reset it after the call into core (see inline comment in `update.rs` about this) - make all update threads watch for a global `AtomicBool` being `true`, and if so stop their work - reset the global bool and exit early (i.e. before writing the dirstate) - raise SIGINT from `hg-cpython` if update returns `InterruptReceived`
author Rapha?l Gom?s <rgomes@octobus.net>
date Tue, 12 Nov 2024 12:52:13 +0100
parents e698e3e75420
children
line wrap: on
line diff
--- a/rust/hg-cpython/src/utils.rs	Fri Nov 08 17:08:11 2024 +0100
+++ b/rust/hg-cpython/src/utils.rs	Tue Nov 12 12:52:13 2024 +0100
@@ -1,7 +1,7 @@
-use cpython::exc::ValueError;
+use cpython::exc::{KeyboardInterrupt, ValueError};
 use cpython::{
-    ObjectProtocol, PyBytes, PyDict, PyErr, PyObject, PyResult, PyTuple,
-    Python, ToPyObject,
+    ObjectProtocol, PyBytes, PyClone, PyDict, PyErr, PyObject, PyResult,
+    PyTuple, Python, ToPyObject,
 };
 use hg::config::Config;
 use hg::errors::HgError;
@@ -50,6 +50,9 @@
                 cls.call(py, (msg,), None).ok().into_py_object(py),
             )
         }
+        HgError::InterruptReceived => {
+            PyErr::new::<KeyboardInterrupt, _>(py, "")
+        }
         e => PyErr::new::<cpython::exc::RuntimeError, _>(py, e.to_string()),
     })
 }
@@ -104,3 +107,38 @@
         })
         .map(Into::into)
 }
+
+/// 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`]
+pub fn with_sigint_wrapper<R>(
+    py: Python,
+    func: impl Fn() -> Result<R, HgError>,
+) -> PyResult<Result<R, HgError>> {
+    let signal_py_mod = py.import("signal")?;
+    let sigint_py_const = signal_py_mod.get(py, "SIGINT")?;
+    let old_handler = signal_py_mod.call(
+        py,
+        "getsignal",
+        PyTuple::new(py, &[sigint_py_const.clone_ref(py)]),
+        None,
+    )?;
+    let res = func();
+    // Reset the old signal handler in Python because we've may have changed it
+    signal_py_mod.call(
+        py,
+        "signal",
+        PyTuple::new(py, &[sigint_py_const.clone_ref(py), old_handler]),
+        None,
+    )?;
+    if let Err(HgError::InterruptReceived) = res {
+        // Trigger the signal in Python
+        signal_py_mod.call(
+            py,
+            "raise_signal",
+            PyTuple::new(py, &[sigint_py_const]),
+            None,
+        )?;
+    }
+    Ok(res)
+}