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