signals: Add support for Windows

This commit is contained in:
Lorenz Leutgeb 2026-02-12 19:14:58 +01:00 committed by Fintan Halpenny
parent 865abd35e2
commit 03bbe52417
10 changed files with 83 additions and 16 deletions

View File

@ -27,6 +27,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
This is fixed by using `winsplit` on Windows instead.
- On Windows, zombie `git-upload-pack` processes are now prevented by using the
"Job" API of the operating system to group child processes and their children.
- On Windows, signal handling was not supported. The `radicle-node` executable
will now respect signal handling on Windows.
## Deprecations

1
Cargo.lock generated
View File

@ -3151,6 +3151,7 @@ dependencies = [
"crossbeam-channel",
"libc",
"signals_receipts",
"windows 0.62.2",
]
[[package]]

View File

@ -70,6 +70,7 @@ sqlite = "0.32.0"
tempfile = "3.3.0"
thiserror = { version = "2", default-features = false }
uds_windows = "1.1.0"
windows = "0.62"
winpipe = "0.1.1"
winsplit = "0.1.0"
zeroize = "1.5.7"

View File

@ -13,7 +13,6 @@ use radicle::profile;
use radicle_node::crypto::ssh::keystore::{Keystore, MemorySigner};
use radicle_node::fingerprint::{Fingerprint, FingerprintVerification};
use radicle_node::{Runtime, VERSION};
#[cfg(unix)]
use radicle_signals as signals;
const HELP_MSG: &str = r#"
@ -320,20 +319,12 @@ fn execute(options: Options) -> Result<(), ExecutionError> {
log::warn!(target: "node", "Unable to set process open file limit: {e}");
}
#[cfg(unix)]
let signals = {
let (notify, signals) = chan::bounded(1);
signals::install(notify)?;
signals
};
#[cfg(windows)]
let signals = {
let (_, signals) = chan::bounded(1);
log::info!(target: "node", "No signal handlers were installed, because this is not available on Windows.");
signals
};
if options.force {
log::debug!(target: "node", "Removing existing control socket..");
std::fs::remove_file(home.socket()).ok();

View File

@ -287,7 +287,6 @@ impl Runtime {
|| control::listen(listener, handle)
});
#[cfg(unix)]
let _signals = thread::spawn(&self.id, "signals", move || loop {
use radicle::node::Handle as _;

View File

@ -8,7 +8,16 @@ edition.workspace = true
version = "0.11.0"
rust-version.workspace = true
[target.'cfg(unix)'.dependencies]
[dependencies]
crossbeam-channel = { workspace = true }
[target.'cfg(unix)'.dependencies]
libc = { workspace = true }
signals_receipts = { version = "0.2.0", features = ["channel_notify_facility"] }
[target.'cfg(windows)'.dependencies.windows]
workspace = true
features = [
"Win32_Foundation",
"Win32_System_Console",
]

View File

@ -1,9 +1,17 @@
use std::io;
#[cfg(unix)]
mod unix;
#[cfg(unix)]
pub use unix::*;
#[cfg(windows)]
mod windows;
#[cfg(windows)]
pub use windows::*;
/// Operating system signal.
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
pub enum Signal {
@ -16,3 +24,12 @@ pub enum Signal {
/// `SIGWINCH`.
WindowChanged,
}
/// Return an error indicating that signal handling is already installed.
#[inline(always)]
fn already_installed() -> io::Error {
io::Error::new(
io::ErrorKind::AlreadyExists,
"signal handling is already installed",
)
}

View File

@ -57,10 +57,7 @@ pub fn install(notify: chan::Sender<Signal>) -> io::Result<()> {
}
SignalsChannel::install_with_outside_channel(ChanSender(notify)).map_err(|e| match e {
InstallError::AlreadyInstalled { unused_notify: _ } => io::Error::new(
io::ErrorKind::AlreadyExists,
"signal handling is already installed",
),
InstallError::AlreadyInstalled { unused_notify: _ } => already_installed(),
_ => io::Error::other(e), // The error type is non-exhaustive.
})
}

View File

@ -0,0 +1,50 @@
use std::io;
use std::sync::OnceLock;
use crossbeam_channel as chan;
use ::windows::core::BOOL;
use ::windows::Win32::System::Console::{
SetConsoleCtrlHandler, CTRL_BREAK_EVENT, CTRL_CLOSE_EVENT, CTRL_C_EVENT, CTRL_LOGOFF_EVENT,
CTRL_SHUTDOWN_EVENT,
};
use crate::{already_installed, Signal};
static NOTIFY: OnceLock<chan::Sender<Signal>> = OnceLock::new();
/// Callback function, called by the system when a control signal is to be received.
/// See <https://learn.microsoft.com/en-us/windows/console/handlerroutine>.
unsafe extern "system" fn handler(ctrltype: u32) -> BOOL {
match ctrltype {
CTRL_C_EVENT | CTRL_BREAK_EVENT | CTRL_CLOSE_EVENT | CTRL_SHUTDOWN_EVENT => {
if let Some(notify) = NOTIFY.get() {
if notify.send(Signal::Terminate).is_ok() {
return true.into();
}
} else {
// Do nothing, since we do not have a channel to send notifications to.
}
}
CTRL_LOGOFF_EVENT => {
// Do nothing, since we do not know which user is logging off.
}
_ => {
// Do nothing, since we received an unknown control signal.
}
}
false.into()
}
/// Install global signal handlers, with notifications sent to the given
/// `notify` channel.
pub fn install(notify: chan::Sender<Signal>) -> io::Result<()> {
if let Err(_) = NOTIFY.set(notify) {
return Err(already_installed());
}
// SAFETY: Our handler function is sane.
let result = unsafe { SetConsoleCtrlHandler(Some(handler), true) };
result.map_err(|_| io::Error::last_os_error())
}

View File

@ -12,7 +12,7 @@ rust-version.workspace = true
thiserror = { workspace = true }
[target.'cfg(windows)'.dependencies.windows]
version = "0.62"
workspace = true
features = [
"Win32_Foundation",
"Win32_Security",