From de6f6c18e7a56a05d036979b5650ca0afc9b6f66 Mon Sep 17 00:00:00 2001 From: sisungo Date: Sun, 10 Dec 2023 19:35:59 +0800 Subject: [PATCH] [Internals] More improvements to `reboot`-series milestones --- airup-sdk/src/system.rs | 16 +++++++++++ airup/src/edit.rs | 1 - airupd/src/app.rs | 6 +++- airupd/src/ipc/api/system.rs | 9 ++++++ airupd/src/milestones/mod.rs | 24 +++++++++++----- airupd/src/milestones/reboot.rs | 49 +++++++++++++++++++++++++++++++++ 6 files changed, 96 insertions(+), 9 deletions(-) diff --git a/airup-sdk/src/system.rs b/airup-sdk/src/system.rs index 48a8418..43e2bcc 100644 --- a/airup-sdk/src/system.rs +++ b/airup-sdk/src/system.rs @@ -49,6 +49,7 @@ pub enum Status { Stopped, } +/// Item of an log record #[derive(Debug, Clone, Serialize, Deserialize)] pub struct LogRecord { pub timestamp: i64, @@ -56,6 +57,14 @@ pub struct LogRecord { pub message: String, } +/// Information of an entered milestone. +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct EnteredMilestone { + pub name: String, + pub begin_timestamp: i64, + pub finish_timestamp: i64, +} + pub trait ConnectionExt { /// Sideloads a service. fn sideload_service( @@ -132,6 +141,9 @@ pub trait ConnectionExt { subject: &str, n: usize, ) -> impl Future, Error>>>; + + /// Enters the specific milestone. + fn enter_milestone(&mut self, name: &str) -> impl Future>>; } impl ConnectionExt for super::Connection { async fn sideload_service( @@ -206,4 +218,8 @@ impl ConnectionExt for super::Connection { ) -> anyhow::Result, Error>> { self.invoke("system.tail_logs", (subject, n)).await } + + async fn enter_milestone(&mut self, name: &str) -> anyhow::Result> { + self.invoke("system.enter_milestone", name).await + } } diff --git a/airup/src/edit.rs b/airup/src/edit.rs index 4db0f0c..d142e91 100644 --- a/airup/src/edit.rs +++ b/airup/src/edit.rs @@ -20,7 +20,6 @@ pub async fn main(cmdline: Cmdline) -> anyhow::Result<()> { } else { Err(anyhow!("file suffix must be specified to edit")) } - } } diff --git a/airupd/src/app.rs b/airupd/src/app.rs index 0825a10..b75371a 100644 --- a/airupd/src/app.rs +++ b/airupd/src/app.rs @@ -51,10 +51,14 @@ impl Airupd { /// Queries information about the whole system. pub async fn query_system(&self) -> QuerySystem { + let booted_since = self + .query_milestone_stack() + .last() + .map(|x| x.finish_timestamp); QuerySystem { status: Status::Active, boot_timestamp: self.boot_timestamp, - booted_since: self.booted_since(), + booted_since, is_booting: self.is_booting(), hostname: airupfx::env::host_name(), services: self.supervisors.list().await, diff --git a/airupd/src/ipc/api/system.rs b/airupd/src/ipc/api/system.rs index bd88159..0014737 100644 --- a/airupd/src/ipc/api/system.rs +++ b/airupd/src/ipc/api/system.rs @@ -24,6 +24,7 @@ pub fn init(methods: &mut HashMap<&'static str, Method, H>) { methods.insert("system.list_services", list_services); methods.insert("system.use_logger", use_logger); methods.insert("system.tail_logs", tail_logs); + methods.insert("system.enter_milestone", enter_milestone); methods.insert("system.poweroff", poweroff); methods.insert("system.reboot", reboot); methods.insert("system.halt", halt); @@ -153,6 +154,14 @@ fn tail_logs(_: Arc, req: Request) -> MethodFuture { }) } +fn enter_milestone(_: Arc, req: Request) -> MethodFuture { + Box::pin(async move { + let name: String = req.extract_params()?; + airupd().enter_milestone(name).await?; + ok_null() + }) +} + fn poweroff(_: Arc, _: Request) -> MethodFuture { Box::pin(async move { airupd().lifetime.poweroff(); diff --git a/airupd/src/milestones/mod.rs b/airupd/src/milestones/mod.rs index c90a99d..9e49aed 100644 --- a/airupd/src/milestones/mod.rs +++ b/airupd/src/milestones/mod.rs @@ -3,13 +3,14 @@ pub mod early_boot; mod reboot; -use crate::app; +use crate::app::{self, airupd}; use ahash::AHashSet; use airup_sdk::{ files::{ milestone::{Item, Kind}, Milestone, }, + system::EnteredMilestone, Error, }; use airupfx::prelude::*; @@ -22,7 +23,7 @@ use std::sync::{ #[derive(Debug, Default)] pub struct Manager { is_booting: AtomicBool, - booted_since: RwLock>, + stack: RwLock>, } impl Manager { /// Creates a new [`Manager`] instance. @@ -51,7 +52,6 @@ impl crate::app::Airupd { self.enter_milestone(name).await.ok(); - *self.milestones.booted_since.write().unwrap() = Some(airupfx::time::timestamp_ms()); self.milestones .is_booting .store(false, atomic::Ordering::Relaxed); @@ -63,9 +63,9 @@ impl crate::app::Airupd { self.milestones.is_booting.load(atomic::Ordering::Relaxed) } - /// Returns a timestamp of boot completion. - pub fn booted_since(&self) -> Option { - *self.milestones.booted_since.read().unwrap() + /// Queries the milestone stack. + pub fn query_milestone_stack(&self) -> Vec { + self.milestones.stack.read().unwrap().clone() } } @@ -91,6 +91,7 @@ fn enter_milestone(name: String, hist: &mut AHashSet) -> BoxFuture<'_, R } tracing::info!(target: "console", "Entering milestone {}", def.display_name()); + let begin_timestamp = airupfx::time::timestamp_ms(); // Enters dependency milestones for dep in def.manifest.milestone.dependencies.iter() { @@ -104,6 +105,15 @@ fn enter_milestone(name: String, hist: &mut AHashSet) -> BoxFuture<'_, R // Starts services exec_milestone(&def).await; + // Record the milestone as entered + let finish_timestamp = airupfx::time::timestamp_ms(); + let record = EnteredMilestone { + name: name.clone(), + begin_timestamp, + finish_timestamp, + }; + airupd().milestones.stack.write().unwrap().push(record); + Ok(()) }) } @@ -222,7 +232,7 @@ async fn display_name(name: &str) -> String { .query_service(name) .await .map(|x| x.definition.display_name().into()) - .unwrap_or_else(|_| name.into()) + .unwrap_or_else(|_| format!("`{name}`")) } pub async fn run_wait(ace: &Ace, cmd: &str) -> anyhow::Result<()> { diff --git a/airupd/src/milestones/reboot.rs b/airupd/src/milestones/reboot.rs index 548fbd4..af77c15 100644 --- a/airupd/src/milestones/reboot.rs +++ b/airupd/src/milestones/reboot.rs @@ -1,7 +1,10 @@ //! The `reboot` milestone preset series. +use crate::app::airupd; use ahash::AHashSet; use airup_sdk::Error; +use tokio::task::JoinHandle; +use std::time::Duration; pub const PRESETS: &[&str] = &["reboot", "poweroff", "halt", "ctrlaltdel"]; @@ -24,6 +27,9 @@ async fn enter_reboot() -> Result<(), Error> { super::enter_milestone("reboot".into(), &mut AHashSet::with_capacity(8)) .await .ok(); + + stop_all_services(Duration::from_millis(5000)).await; + Ok(()) } @@ -32,6 +38,9 @@ async fn enter_poweroff() -> Result<(), Error> { super::enter_milestone("poweroff".into(), &mut AHashSet::with_capacity(8)) .await .ok(); + + stop_all_services(Duration::from_millis(5000)).await; + Ok(()) } @@ -40,6 +49,9 @@ async fn enter_halt() -> Result<(), Error> { super::enter_milestone("halt".into(), &mut AHashSet::with_capacity(8)) .await .ok(); + + stop_all_services(Duration::from_millis(5000)).await; + Ok(()) } @@ -55,3 +67,40 @@ async fn enter_ctrlaltdel() -> Result<(), Error> { Err(_) => enter_reboot().await, } } + +/// Stops all running services. +async fn stop_all_services(timeout: Duration) { + tokio::time::timeout(timeout, async { + let services = airupd().supervisors.list().await; + let mut join_handles = Vec::with_capacity(services.len()); + for service in services { + join_handles.push(stop_service_task(service)); + } + for join_handle in join_handles { + join_handle.await.ok(); + } + }) + .await + .ok(); +} + +/// Spawns a task to interactively stop a service. +fn stop_service_task(service: String) -> JoinHandle<()> { + tokio::spawn(async move { + match airupd().stop_service(&service).await { + Ok(_) => { + tracing::info!("Stopping {}", super::display_name(&service).await); + } + Err(err) => { + if matches!(err, Error::UnitNotFound | Error::UnitNotStarted) { + return; + } + tracing::error!( + "Failed to stop {}: {}", + super::display_name(&service).await, + err + ); + } + }; + }) +} \ No newline at end of file