Radish alpha
h
Radicle Heartwood Protocol & Stack
Radicle
Git (anonymous pull)
Log in to clone via SSH
node: Handle `SIGINT` and `SIGTERM` properly
Alexis Sellier committed 3 years ago
commit 72c99b79394656d788ad10030c49b2ce41d30ade
parent e7c929898a462aba736864532faef8810dfee466
6 files changed +82 -5
modified radicle-node/Cargo.toml
@@ -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
modified radicle-node/src/lib.rs
@@ -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;
modified radicle-node/src/main.rs
@@ -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(())
}
modified radicle-node/src/runtime.rs
@@ -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());

added radicle-node/src/signals.rs
@@ -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();
+
        }
+
    }
+
}
modified radicle-node/src/test/environment.rs
@@ -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();