abstract shutdown/reload functionality.

Signed-off-by: Jason Volk <jason@zemos.net>
This commit is contained in:
Jason Volk 2024-06-16 01:39:14 +00:00
parent 30e7298dd7
commit 08f2b8579c
6 changed files with 49 additions and 26 deletions

View file

@ -5,7 +5,7 @@ use std::{
use tokio::{runtime, sync::broadcast}; use tokio::{runtime, sync::broadcast};
use crate::{config::Config, log}; use crate::{config::Config, log, Error, Result};
/// Server runtime state; public portion /// Server runtime state; public portion
pub struct Server { pub struct Server {
@ -59,6 +59,38 @@ impl Server {
} }
} }
pub fn reload(&self) -> Result<()> {
if cfg!(not(conduit_mods)) {
return Err(Error::Err("Reloading not enabled".into()));
}
if self.reloading.swap(true, Ordering::AcqRel) {
return Err(Error::Err("Reloading already in progress".into()));
}
if self.stopping.swap(true, Ordering::AcqRel) {
return Err(Error::Err("Shutdown already in progress".into()));
}
self.signal("SIGINT")
}
pub fn shutdown(&self) -> Result<()> {
if self.stopping.swap(true, Ordering::AcqRel) {
return Err(Error::Err("Shutdown already in progress".into()));
}
self.signal("SIGTERM")
}
pub fn signal(&self, sig: &'static str) -> Result<()> {
if let Err(e) = self.signal.send(sig) {
return Err(Error::Err(format!("Failed to send signal: {e}")));
}
Ok(())
}
#[inline] #[inline]
pub fn runtime(&self) -> &runtime::Handle { pub fn runtime(&self) -> &runtime::Handle {
self.runtime self.runtime

View file

@ -4,11 +4,7 @@ mod server;
extern crate conduit_core as conduit; extern crate conduit_core as conduit;
use std::{ use std::{cmp, sync::Arc, time::Duration};
cmp,
sync::{atomic::Ordering, Arc},
time::Duration,
};
use conduit::{debug_error, debug_info, error, trace, utils::available_parallelism, warn, Error, Result}; use conduit::{debug_error, debug_info, error, trace, utils::available_parallelism, warn, Error, Result};
use server::Server; use server::Server;
@ -107,7 +103,7 @@ async fn signal(server: Arc<Server>) {
use unix::SignalKind; use unix::SignalKind;
const CONSOLE: bool = cfg!(feature = "console"); const CONSOLE: bool = cfg!(feature = "console");
const RELOADING: bool = cfg!(all(unix, debug_assertions, not(CONSOLE))); const RELOADING: bool = cfg!(all(conduit_mods, not(CONSOLE)));
let mut quit = unix::signal(SignalKind::quit()).expect("SIGQUIT handler"); let mut quit = unix::signal(SignalKind::quit()).expect("SIGQUIT handler");
let mut term = unix::signal(SignalKind::terminate()).expect("SIGTERM handler"); let mut term = unix::signal(SignalKind::terminate()).expect("SIGTERM handler");
@ -120,19 +116,17 @@ async fn signal(server: Arc<Server>) {
_ = term.recv() => { sig = "SIGTERM"; }, _ = term.recv() => { sig = "SIGTERM"; },
} }
// Indicate the SIGINT is requesting a hot-reload.
if RELOADING && sig == "SIGINT" {
server.server.reloading.store(true, Ordering::Release);
}
// Indicate the signal is requesting a shutdown
if matches!(sig, "SIGQUIT" | "SIGTERM") || (!CONSOLE && sig == "SIGINT") {
server.server.stopping.store(true, Ordering::Release);
}
warn!("Received {sig}"); warn!("Received {sig}");
if let Err(e) = server.server.signal.send(sig) { let result = if RELOADING && sig == "SIGINT" {
debug_error!("signal channel: {e}"); server.server.reload()
} else if matches!(sig, "SIGQUIT" | "SIGTERM") || (!CONSOLE && sig == "SIGINT") {
server.server.shutdown()
} else {
server.server.signal(sig)
};
if let Err(e) = result {
debug_error!(?sig, "signal: {e}");
} }
} }
} }

View file

@ -39,6 +39,7 @@ pub(crate) async fn run(server: &Arc<Server>, starts: bool) -> Result<(bool, boo
return Err(error); return Err(error);
} }
} }
server.server.stopping.store(false, Ordering::Release);
let run = main_mod.get::<RunFuncProto>("run")?; let run = main_mod.get::<RunFuncProto>("run")?;
if let Err(error) = run(&server.server).await { if let Err(error) = run(&server.server).await {
error!("Running server: {error}"); error!("Running server: {error}");

View file

@ -13,7 +13,7 @@ use tracing::{debug, error, trace};
pub(crate) async fn spawn( pub(crate) async fn spawn(
State(server): State<Arc<Server>>, req: http::Request<axum::body::Body>, next: axum::middleware::Next, State(server): State<Arc<Server>>, req: http::Request<axum::body::Body>, next: axum::middleware::Next,
) -> Result<axum::response::Response, StatusCode> { ) -> Result<axum::response::Response, StatusCode> {
if server.stopping.load(Ordering::Relaxed) { if !server.running() {
debug_warn!("unavailable pending shutdown"); debug_warn!("unavailable pending shutdown");
return Err(StatusCode::SERVICE_UNAVAILABLE); return Err(StatusCode::SERVICE_UNAVAILABLE);
} }
@ -35,7 +35,7 @@ pub(crate) async fn spawn(
pub(crate) async fn handle( pub(crate) async fn handle(
State(server): State<Arc<Server>>, req: http::Request<axum::body::Body>, next: axum::middleware::Next, State(server): State<Arc<Server>>, req: http::Request<axum::body::Body>, next: axum::middleware::Next,
) -> Result<axum::response::Response, StatusCode> { ) -> Result<axum::response::Response, StatusCode> {
if server.stopping.load(Ordering::Relaxed) { if !server.running() {
debug_warn!( debug_warn!(
method = %req.method(), method = %req.method(),
uri = %req.uri(), uri = %req.uri(),

View file

@ -25,7 +25,6 @@ pub(crate) async fn run(server: Arc<Server>) -> Result<(), Error> {
_ = services().admin.handle.lock().await.insert(admin::handle); _ = services().admin.handle.lock().await.insert(admin::handle);
// Setup shutdown/signal handling // Setup shutdown/signal handling
server.stopping.store(false, Ordering::Release);
let handle = ServerHandle::new(); let handle = ServerHandle::new();
let (tx, _) = broadcast::channel::<()>(1); let (tx, _) = broadcast::channel::<()>(1);
let sigs = server let sigs = server

View file

@ -1,6 +1,6 @@
use std::{ use std::{
collections::{BTreeMap, HashMap}, collections::{BTreeMap, HashMap},
sync::{atomic, Arc, Mutex as StdMutex}, sync::{Arc, Mutex as StdMutex},
}; };
use conduit::{debug_info, Result, Server}; use conduit::{debug_info, Result, Server};
@ -301,8 +301,6 @@ bad_signature_ratelimiter: {bad_signature_ratelimiter}
pub async fn interrupt(&self) { pub async fn interrupt(&self) {
trace!("Interrupting services..."); trace!("Interrupting services...");
self.server.stopping.store(true, atomic::Ordering::Release);
self.sending.interrupt(); self.sending.interrupt();
self.presence.interrupt(); self.presence.interrupt();
self.admin.interrupt(); self.admin.interrupt();
@ -310,7 +308,6 @@ bad_signature_ratelimiter: {bad_signature_ratelimiter}
trace!("Services interrupt complete."); trace!("Services interrupt complete.");
} }
#[tracing::instrument(skip_all)]
pub async fn stop(&self) { pub async fn stop(&self) {
info!("Shutting down services"); info!("Shutting down services");
self.interrupt().await; self.interrupt().await;