# HG changeset patch # User Rapha?l Gom?s # Date 1739875461 -3600 # Node ID 69d40a9778fe5ebd90e6cc9e65a72f3893d7f624 # Parent 0c7ac026ed633e2feae428280ef9b604046f5905 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. diff -r 0c7ac026ed63 -r 69d40a9778fe rust/hg-pyo3/src/utils.rs --- 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( + py: Python, + func: impl Fn() -> Result, +) -> PyResult> { + 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) +}