Mercurial > public > mercurial-scm > hg
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) +}