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`
// debug.rs
//
// Copyright 2024 Mercurial developers
//
// This software may be used and distributed according to the terms of the
// GNU General Public License version 2 or any later version.
//! Module for updating a repository.
use cpython::{PyDict, PyModule, PyObject, PyResult, Python};
use hg::{
progress::{HgProgressBar, Progress},
update::update_from_null,
BaseRevision,
};
use crate::{
exceptions::FallbackError,
utils::{hgerror_to_pyerr, repo_from_path, with_sigint_wrapper},
};
pub fn update_from_null_fast_path(
py: Python,
repo_path: PyObject,
to: BaseRevision,
num_cpus: Option<usize>,
) -> PyResult<usize> {
log::trace!("Using update from null fastpath");
let repo = repo_from_path(py, repo_path)?;
let progress: &dyn Progress = &HgProgressBar::new("updating");
let res = with_sigint_wrapper(py, || {
update_from_null(&repo, to.into(), progress, num_cpus)
})?;
hgerror_to_pyerr(py, res)
}
pub fn init_module(py: Python, package: &str) -> PyResult<PyModule> {
let dotted_name = &format!("{}.update", package);
let m = PyModule::new(py, dotted_name)?;
m.add(py, "__package__", package)?;
m.add(py, "__doc__", "Rust module for updating a repository")?;
m.add(py, "FallbackError", py.get_type::<FallbackError>())?;
m.add(
py,
"update_from_null",
py_fn!(
py,
update_from_null_fast_path(
repo_path: PyObject,
to: BaseRevision,
num_cpus: Option<usize>
)
),
)?;
let sys = PyModule::import(py, "sys")?;
let sys_modules: PyDict = sys.get(py, "modules")?.extract(py)?;
sys_modules.set_item(py, dotted_name, &m)?;
Ok(m)
}