comparison rust/hg-cpython/src/utils.rs @ 52183: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
comparison
equal deleted inserted replaced
52182:fa58f4f97337 52183:96b113d22b34
1 use cpython::exc::ValueError; 1 use cpython::exc::{KeyboardInterrupt, ValueError};
2 use cpython::{ 2 use cpython::{
3 ObjectProtocol, PyBytes, PyDict, PyErr, PyObject, PyResult, PyTuple, 3 ObjectProtocol, PyBytes, PyClone, PyDict, PyErr, PyObject, PyResult,
4 Python, ToPyObject, 4 PyTuple, Python, ToPyObject,
5 }; 5 };
6 use hg::config::Config; 6 use hg::config::Config;
7 use hg::errors::HgError; 7 use hg::errors::HgError;
8 use hg::repo::{Repo, RepoError}; 8 use hg::repo::{Repo, RepoError};
9 use hg::revlog::Node; 9 use hg::revlog::Node;
47 .unwrap(); 47 .unwrap();
48 PyErr::from_instance( 48 PyErr::from_instance(
49 py, 49 py,
50 cls.call(py, (msg,), None).ok().into_py_object(py), 50 cls.call(py, (msg,), None).ok().into_py_object(py),
51 ) 51 )
52 }
53 HgError::InterruptReceived => {
54 PyErr::new::<KeyboardInterrupt, _>(py, "")
52 } 55 }
53 e => PyErr::new::<cpython::exc::RuntimeError, _>(py, e.to_string()), 56 e => PyErr::new::<cpython::exc::RuntimeError, _>(py, e.to_string()),
54 }) 57 })
55 } 58 }
56 59
102 format!("{}-byte hash required", NODE_BYTES_LENGTH), 105 format!("{}-byte hash required", NODE_BYTES_LENGTH),
103 ) 106 )
104 }) 107 })
105 .map(Into::into) 108 .map(Into::into)
106 } 109 }
110
111 /// Wrap a call to `func` so that Python's `SIGINT` handler is first stored,
112 /// then restored after the call to `func` and finally raised if
113 /// `func` returns a [`HgError::InterruptReceived`]
114 pub fn with_sigint_wrapper<R>(
115 py: Python,
116 func: impl Fn() -> Result<R, HgError>,
117 ) -> PyResult<Result<R, HgError>> {
118 let signal_py_mod = py.import("signal")?;
119 let sigint_py_const = signal_py_mod.get(py, "SIGINT")?;
120 let old_handler = signal_py_mod.call(
121 py,
122 "getsignal",
123 PyTuple::new(py, &[sigint_py_const.clone_ref(py)]),
124 None,
125 )?;
126 let res = func();
127 // Reset the old signal handler in Python because we've may have changed it
128 signal_py_mod.call(
129 py,
130 "signal",
131 PyTuple::new(py, &[sigint_py_const.clone_ref(py), old_handler]),
132 None,
133 )?;
134 if let Err(HgError::InterruptReceived) = res {
135 // Trigger the signal in Python
136 signal_py_mod.call(
137 py,
138 "raise_signal",
139 PyTuple::new(py, &[sigint_py_const]),
140 None,
141 )?;
142 }
143 Ok(res)
144 }