node: Handle `SIGINT` and `SIGTERM` properly
This ensures that the socket file is cleaned up on exit.
This commit is contained in:
parent
e7c929898a
commit
72c99b7939
|
|
@ -19,13 +19,13 @@ crossbeam-channel = { version = "0.5.6" }
|
||||||
cyphernet = { version = "0.1.0", features = ["tor", "dns", "ed25519", "p2p-ed25519"] }
|
cyphernet = { version = "0.1.0", features = ["tor", "dns", "ed25519", "p2p-ed25519"] }
|
||||||
fastrand = { version = "1.8.0" }
|
fastrand = { version = "1.8.0" }
|
||||||
git-ref-format = { version = "0.2", features = ["serde", "macro"] }
|
git-ref-format = { version = "0.2", features = ["serde", "macro"] }
|
||||||
|
io-reactor = { version = "0.1.0", features = ["popol"] }
|
||||||
lexopt = { version = "0.2.1" }
|
lexopt = { version = "0.2.1" }
|
||||||
libc = { version = "0.2.137" }
|
libc = { version = "0.2.137" }
|
||||||
log = { version = "0.4.17", features = ["std"] }
|
log = { version = "0.4.17", features = ["std"] }
|
||||||
localtime = { version = "1" }
|
localtime = { version = "1" }
|
||||||
netservices = { version = "0.1.0", features = ["io-reactor", "socket2"] }
|
netservices = { version = "0.1.0", features = ["io-reactor", "socket2"] }
|
||||||
nonempty = { version = "0.8.1", features = ["serialize"] }
|
nonempty = { version = "0.8.1", features = ["serialize"] }
|
||||||
io-reactor = { version = "0.1.0", features = ["popol"] }
|
|
||||||
qcheck = { version = "1", default-features = false, optional = true }
|
qcheck = { version = "1", default-features = false, optional = true }
|
||||||
sqlite = { version = "0.30.3" }
|
sqlite = { version = "0.30.3" }
|
||||||
sqlite3-src = { version = "0.4.0", features = ["bundled"] } # Ensures static linking
|
sqlite3-src = { version = "0.4.0", features = ["bundled"] } # Ensures static linking
|
||||||
|
|
|
||||||
|
|
@ -6,6 +6,7 @@ pub mod deserializer;
|
||||||
pub mod logger;
|
pub mod logger;
|
||||||
pub mod runtime;
|
pub mod runtime;
|
||||||
pub mod service;
|
pub mod service;
|
||||||
|
pub mod signals;
|
||||||
pub mod sql;
|
pub mod sql;
|
||||||
#[cfg(any(test, feature = "test"))]
|
#[cfg(any(test, feature = "test"))]
|
||||||
pub mod test;
|
pub mod test;
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,7 @@
|
||||||
use std::{env, net, process};
|
use std::{env, net, process};
|
||||||
|
|
||||||
use anyhow::Context as _;
|
use anyhow::Context as _;
|
||||||
|
use crossbeam_channel as chan;
|
||||||
use cyphernet::addr::PeerAddr;
|
use cyphernet::addr::PeerAddr;
|
||||||
use localtime::LocalDuration;
|
use localtime::LocalDuration;
|
||||||
|
|
||||||
|
|
@ -8,7 +9,7 @@ use radicle::profile;
|
||||||
use radicle_node::crypto::ssh::keystore::{Keystore, MemorySigner};
|
use radicle_node::crypto::ssh::keystore::{Keystore, MemorySigner};
|
||||||
use radicle_node::prelude::{Address, NodeId};
|
use radicle_node::prelude::{Address, NodeId};
|
||||||
use radicle_node::Runtime;
|
use radicle_node::Runtime;
|
||||||
use radicle_node::{logger, service};
|
use radicle_node::{logger, service, signals};
|
||||||
|
|
||||||
pub const HELP_MSG: &str = r#"
|
pub const HELP_MSG: &str = r#"
|
||||||
Usage
|
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(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -9,6 +9,7 @@ use crossbeam_channel as chan;
|
||||||
use cyphernet::{Cert, EcSign};
|
use cyphernet::{Cert, EcSign};
|
||||||
use netservices::resource::NetAccept;
|
use netservices::resource::NetAccept;
|
||||||
use radicle::git;
|
use radicle::git;
|
||||||
|
use radicle::node::Handle as _;
|
||||||
use radicle::profile::Home;
|
use radicle::profile::Home;
|
||||||
use radicle::Storage;
|
use radicle::Storage;
|
||||||
use reactor::poller::popol;
|
use reactor::poller::popol;
|
||||||
|
|
@ -77,6 +78,7 @@ pub struct Runtime<G: Signer + EcSign> {
|
||||||
pub daemon: net::SocketAddr,
|
pub daemon: net::SocketAddr,
|
||||||
pub pool: worker::Pool,
|
pub pool: worker::Pool,
|
||||||
pub local_addrs: Vec<net::SocketAddr>,
|
pub local_addrs: Vec<net::SocketAddr>,
|
||||||
|
pub signals: chan::Receiver<()>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<G: Signer + EcSign + 'static> Runtime<G> {
|
impl<G: Signer + EcSign + 'static> Runtime<G> {
|
||||||
|
|
@ -89,6 +91,7 @@ impl<G: Signer + EcSign + 'static> Runtime<G> {
|
||||||
listen: Vec<net::SocketAddr>,
|
listen: Vec<net::SocketAddr>,
|
||||||
proxy: net::SocketAddr,
|
proxy: net::SocketAddr,
|
||||||
daemon: net::SocketAddr,
|
daemon: net::SocketAddr,
|
||||||
|
signals: chan::Receiver<()>,
|
||||||
signer: G,
|
signer: G,
|
||||||
) -> Result<Runtime<G>, Error>
|
) -> Result<Runtime<G>, Error>
|
||||||
where
|
where
|
||||||
|
|
@ -176,6 +179,7 @@ impl<G: Signer + EcSign + 'static> Runtime<G> {
|
||||||
daemon,
|
daemon,
|
||||||
handle,
|
handle,
|
||||||
pool,
|
pool,
|
||||||
|
signals,
|
||||||
local_addrs,
|
local_addrs,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
@ -195,9 +199,18 @@ impl<G: Signer + EcSign + 'static> Runtime<G> {
|
||||||
return Err(err.into());
|
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())
|
.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());
|
log::info!(target: "node", "Spawning git daemon at {}..", self.storage.path().display());
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -6,6 +6,8 @@ use std::{
|
||||||
time::Duration,
|
time::Duration,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
use crossbeam_channel as chan;
|
||||||
|
|
||||||
use radicle::crypto::ssh::{keystore::MemorySigner, Keystore};
|
use radicle::crypto::ssh::{keystore::MemorySigner, Keystore};
|
||||||
use radicle::crypto::test::signer::MockSigner;
|
use radicle::crypto::test::signer::MockSigner;
|
||||||
use radicle::crypto::{KeyPair, Seed, Signature, Signer};
|
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 listen = vec![([0, 0, 0, 0], 0).into()];
|
||||||
let proxy = net::SocketAddr::new(net::Ipv4Addr::LOCALHOST.into(), 9050);
|
let proxy = net::SocketAddr::new(net::Ipv4Addr::LOCALHOST.into(), 9050);
|
||||||
let daemon = ([0, 0, 0, 0], fastrand::u16(1025..)).into();
|
let daemon = ([0, 0, 0, 0], fastrand::u16(1025..)).into();
|
||||||
|
let (_, signals) = chan::bounded(1);
|
||||||
let rt = Runtime::init(
|
let rt = Runtime::init(
|
||||||
self.home.clone(),
|
self.home.clone(),
|
||||||
config,
|
config,
|
||||||
listen,
|
listen,
|
||||||
proxy,
|
proxy,
|
||||||
daemon,
|
daemon,
|
||||||
|
signals,
|
||||||
self.signer.clone(),
|
self.signer.clone(),
|
||||||
)
|
)
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue