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