use std::{future::Future, time::Duration};
#[cfg(all(target_os = "linux", feature = "systemd"))]
use libsystemd::daemon::{self, NotifyState};
use tokio::time::MissedTickBehavior;
#[cfg(all(target_os = "linux", feature = "systemd"))]
use tracing::{error, info, trace, warn};
use tracing::{trace_span, Instrument};
pub struct ServiceNotifier {
has_systemd: bool,
}
impl Default for ServiceNotifier {
fn default() -> Self {
Self::new()
}
}
impl ServiceNotifier {
#[must_use]
pub fn new() -> Self {
Self {
#[cfg(all(target_os = "linux", feature = "systemd"))]
has_systemd: daemon::booted(),
#[cfg(not(all(target_os = "linux", feature = "systemd")))]
has_systemd: false,
}
}
#[must_use]
fn watchdog_interval(&self) -> Option<Duration> {
if !self.has_systemd {
return None;
}
#[cfg(all(target_os = "linux", feature = "systemd"))]
return daemon::watchdog_enabled(true).map(|dur| dur / 2);
#[cfg(not(all(target_os = "linux", feature = "systemd")))]
return None;
}
pub fn on_ready(&self) {
#[allow(clippy::needless_return)]
if !self.has_systemd {
return;
}
#[cfg(all(target_os = "linux", feature = "systemd"))]
match daemon::notify(false, &[NotifyState::Ready]) {
Ok(true) => info!("supervisor notified: service ready"),
Ok(false) => warn!("supervisor unavailable: service ready"),
Err(err) => error!(%err, "supervisor notification error"),
}
}
pub fn on_reload(&self) {
#[allow(clippy::needless_return)]
if !self.has_systemd {
return;
}
#[cfg(all(target_os = "linux", feature = "systemd"))]
match daemon::notify(false, &[NotifyState::Reloading]) {
Ok(true) => info!("supervisor notified: service reloading"),
Ok(false) => warn!("supervisor unavailable: service reloading"),
Err(err) => error!(%err, "supervisor notification error"),
}
}
pub fn on_shutdown(&self) {
#[allow(clippy::needless_return)]
if !self.has_systemd {
return;
}
#[cfg(all(target_os = "linux", feature = "systemd"))]
match daemon::notify(false, &[NotifyState::Stopping]) {
Ok(true) => info!("supervisor notified: service stopping"),
Ok(false) => warn!("supervisor unavailable: service stopping"),
Err(err) => error!(%err, "supervisor notification error"),
}
}
pub fn watchdog_task(&self) -> impl Future<Output = ()> {
let span = trace_span!("systemd_watchdog");
let interval_time = self.watchdog_interval();
async move {
match interval_time {
None => futures::pending!(),
Some(int) => {
let mut timer = tokio::time::interval(int);
timer.set_missed_tick_behavior(MissedTickBehavior::Delay);
loop {
tokio::select! {
_ = timer.tick() => {
#[cfg(all(target_os = "linux", feature = "systemd"))]
match daemon::notify(false, &[NotifyState::Watchdog]) {
Ok(true) => trace!("supervisor notified: watchdog tick"),
Ok(false) => warn!("supervisor unavailable: watchdog tick"),
Err(err) => error!(%err, "watchdog notification error"),
}
}
}
}
}
}
}
.instrument(span)
}
}