node: Handle `SIGINT` and `SIGTERM` properly

This ensures that the socket file is cleaned up on exit.
This commit is contained in:
Alexis Sellier 2023-02-13 17:25:18 +01:00
parent e7c929898a
commit 72c99b7939
No known key found for this signature in database
6 changed files with 82 additions and 5 deletions

View File

@ -19,13 +19,13 @@ crossbeam-channel = { version = "0.5.6" }
cyphernet = { version = "0.1.0", features = ["tor", "dns", "ed25519", "p2p-ed25519"] }
fastrand = { version = "1.8.0" }
git-ref-format = { version = "0.2", features = ["serde", "macro"] }
io-reactor = { version = "0.1.0", features = ["popol"] }
lexopt = { version = "0.2.1" }
libc = { version = "0.2.137" }
log = { version = "0.4.17", features = ["std"] }
localtime = { version = "1" }
netservices = { version = "0.1.0", features = ["io-reactor", "socket2"] }
nonempty = { version = "0.8.1", features = ["serialize"] }
io-reactor = { version = "0.1.0", features = ["popol"] }
qcheck = { version = "1", default-features = false, optional = true }
sqlite = { version = "0.30.3" }
sqlite3-src = { version = "0.4.0", features = ["bundled"] } # Ensures static linking

View File

@ -6,6 +6,7 @@ pub mod deserializer;
pub mod logger;
pub mod runtime;
pub mod service;
pub mod signals;
pub mod sql;
#[cfg(any(test, feature = "test"))]
pub mod test;

View File

@ -1,6 +1,7 @@
use std::{env, net, process};
use anyhow::Context as _;
use crossbeam_channel as chan;
use cyphernet::addr::PeerAddr;
use localtime::LocalDuration;
@ -8,7 +9,7 @@ use radicle::profile;
use radicle_node::crypto::ssh::keystore::{Keystore, MemorySigner};
use radicle_node::prelude::{Address, NodeId};
use radicle_node::Runtime;
use radicle_node::{logger, service};
use radicle_node::{logger, service, signals};
pub const HELP_MSG: &str = r#"
Usage
@ -117,7 +118,10 @@ fn execute() -> anyhow::Result<()> {
)
});
Runtime::init(home, config, options.listen, proxy, daemon, signer)?.run()?;
let (notify, signals) = chan::bounded(1);
signals::install(notify)?;
Runtime::init(home, config, options.listen, proxy, daemon, signals, signer)?.run()?;
Ok(())
}

View File

@ -9,6 +9,7 @@ use crossbeam_channel as chan;
use cyphernet::{Cert, EcSign};
use netservices::resource::NetAccept;
use radicle::git;
use radicle::node::Handle as _;
use radicle::profile::Home;
use radicle::Storage;
use reactor::poller::popol;
@ -77,6 +78,7 @@ pub struct Runtime<G: Signer + EcSign> {
pub daemon: net::SocketAddr,
pub pool: worker::Pool,
pub local_addrs: Vec<net::SocketAddr>,
pub signals: chan::Receiver<()>,
}
impl<G: Signer + EcSign + 'static> Runtime<G> {
@ -89,6 +91,7 @@ impl<G: Signer + EcSign + 'static> Runtime<G> {
listen: Vec<net::SocketAddr>,
proxy: net::SocketAddr,
daemon: net::SocketAddr,
signals: chan::Receiver<()>,
signer: G,
) -> Result<Runtime<G>, Error>
where
@ -176,6 +179,7 @@ impl<G: Signer + EcSign + 'static> Runtime<G> {
daemon,
handle,
pool,
signals,
local_addrs,
})
}
@ -195,9 +199,18 @@ impl<G: Signer + EcSign + 'static> Runtime<G> {
return Err(err.into());
}
};
let control = thread::Builder::new()
let control = thread::Builder::new().name(self.id.to_human()).spawn({
let handle = self.handle.clone();
move || control::listen(listener, handle)
})?;
let _signals = thread::Builder::new()
.name(self.id.to_human())
.spawn(move || control::listen(listener, self.handle))?;
.spawn(move || {
if let Ok(()) = self.signals.recv() {
log::info!(target: "node", "Termination signal received; shutting down..");
self.handle.shutdown().ok();
}
})?;
log::info!(target: "node", "Spawning git daemon at {}..", self.storage.path().display());

View File

@ -0,0 +1,55 @@
use std::io;
use std::sync::Mutex;
use crossbeam_channel as chan;
/// Signal notifications are sent via this channel.
static NOTIFY: Mutex<Option<chan::Sender<()>>> = Mutex::new(None);
/// Install global signal handlers for `SIGTERM` and `SIGINT`.
pub fn install(notify: chan::Sender<()>) -> io::Result<()> {
if let Ok(mut channel) = NOTIFY.try_lock() {
if channel.is_some() {
return Err(io::Error::new(
io::ErrorKind::AlreadyExists,
"signal handler is already installed",
));
}
*channel = Some(notify);
unsafe { _install() }?;
} else {
return Err(io::Error::new(
io::ErrorKind::WouldBlock,
"unable to install signal handler",
));
}
Ok(())
}
/// Install global signal handlers for `SIGTERM` and `SIGINT`.
///
/// # Safety
///
/// Calls `libc` functions safely.
unsafe fn _install() -> io::Result<()> {
if libc::signal(libc::SIGTERM, handler as libc::sighandler_t) == libc::SIG_ERR {
return Err(io::Error::last_os_error());
}
if libc::signal(libc::SIGINT, handler as libc::sighandler_t) == libc::SIG_ERR {
return Err(io::Error::last_os_error());
}
Ok(())
}
/// Called by `libc` when a signal is received.
extern "C" fn handler(sig: libc::c_int, _info: *mut libc::siginfo_t, _data: *mut libc::c_void) {
if sig != libc::SIGTERM && sig != libc::SIGINT {
return;
}
if let Ok(guard) = NOTIFY.try_lock() {
if let Some(c) = &*guard {
c.try_send(()).ok();
}
}
}

View File

@ -6,6 +6,8 @@ use std::{
time::Duration,
};
use crossbeam_channel as chan;
use radicle::crypto::ssh::{keystore::MemorySigner, Keystore};
use radicle::crypto::test::signer::MockSigner;
use radicle::crypto::{KeyPair, Seed, Signature, Signer};
@ -249,12 +251,14 @@ impl<G: cyphernet::EcSign<Pk = NodeId, Sig = Signature> + Signer + Clone> Node<G
let listen = vec![([0, 0, 0, 0], 0).into()];
let proxy = net::SocketAddr::new(net::Ipv4Addr::LOCALHOST.into(), 9050);
let daemon = ([0, 0, 0, 0], fastrand::u16(1025..)).into();
let (_, signals) = chan::bounded(1);
let rt = Runtime::init(
self.home.clone(),
config,
listen,
proxy,
daemon,
signals,
self.signer.clone(),
)
.unwrap();