mirror of
https://gitlab.steamos.cloud/holo/steamos-manager.git
synced 2025-07-12 01:12:30 -04:00
daemon: Allow context-specific commands on the message channel
This commit is contained in:
parent
b582d51c90
commit
1a69cce50b
3 changed files with 50 additions and 13 deletions
|
@ -8,7 +8,6 @@
|
||||||
use anyhow::{anyhow, ensure, Result};
|
use anyhow::{anyhow, ensure, Result};
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use std::fmt::Debug;
|
use std::fmt::Debug;
|
||||||
use std::marker::PhantomData;
|
|
||||||
use std::path::PathBuf;
|
use std::path::PathBuf;
|
||||||
use tokio::signal::unix::{signal, SignalKind};
|
use tokio::signal::unix::{signal, SignalKind};
|
||||||
use tokio::sync::mpsc::{self, Receiver, Sender};
|
use tokio::sync::mpsc::{self, Receiver, Sender};
|
||||||
|
@ -24,8 +23,8 @@ use crate::sls::{LogLayer, LogReceiver};
|
||||||
use crate::Service;
|
use crate::Service;
|
||||||
|
|
||||||
mod config;
|
mod config;
|
||||||
mod root;
|
pub(crate) mod root;
|
||||||
mod user;
|
pub(crate) mod user;
|
||||||
|
|
||||||
pub use root::daemon as root;
|
pub use root::daemon as root;
|
||||||
pub use user::daemon as user;
|
pub use user::daemon as user;
|
||||||
|
@ -33,6 +32,7 @@ pub use user::daemon as user;
|
||||||
pub(crate) trait DaemonContext: Sized {
|
pub(crate) trait DaemonContext: Sized {
|
||||||
type State: for<'a> Deserialize<'a> + Serialize + Default + Debug;
|
type State: for<'a> Deserialize<'a> + Serialize + Default + Debug;
|
||||||
type Config: for<'a> Deserialize<'a> + Default + Debug;
|
type Config: for<'a> Deserialize<'a> + Default + Debug;
|
||||||
|
type Command: Send;
|
||||||
|
|
||||||
fn state_path(&self) -> Result<PathBuf> {
|
fn state_path(&self) -> Result<PathBuf> {
|
||||||
let config_path = self.user_config_path()?;
|
let config_path = self.user_config_path()?;
|
||||||
|
@ -51,17 +51,20 @@ pub(crate) trait DaemonContext: Sized {
|
||||||
) -> Result<()>;
|
) -> Result<()>;
|
||||||
|
|
||||||
async fn reload(&mut self, config: Self::Config, daemon: &mut Daemon<Self>) -> Result<()>;
|
async fn reload(&mut self, config: Self::Config, daemon: &mut Daemon<Self>) -> Result<()>;
|
||||||
|
|
||||||
|
async fn handle_command(&mut self, cmd: Self::Command, daemon: &mut Daemon<Self>)
|
||||||
|
-> Result<()>;
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) struct Daemon<C: DaemonContext> {
|
pub(crate) struct Daemon<C: DaemonContext> {
|
||||||
services: JoinSet<Result<()>>,
|
services: JoinSet<Result<()>>,
|
||||||
token: CancellationToken,
|
token: CancellationToken,
|
||||||
channel: Receiver<DaemonCommand>,
|
channel: Receiver<DaemonCommand<C::Command>>,
|
||||||
_context: PhantomData<C>,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub(crate) enum DaemonCommand {
|
pub(crate) enum DaemonCommand<T> {
|
||||||
|
ContextCommand(T),
|
||||||
ReadConfig,
|
ReadConfig,
|
||||||
WriteState,
|
WriteState,
|
||||||
}
|
}
|
||||||
|
@ -70,7 +73,7 @@ impl<C: DaemonContext> Daemon<C> {
|
||||||
pub(crate) async fn new<S: SubscriberExt + Send + Sync + for<'a> LookupSpan<'a>>(
|
pub(crate) async fn new<S: SubscriberExt + Send + Sync + for<'a> LookupSpan<'a>>(
|
||||||
subscriber: S,
|
subscriber: S,
|
||||||
connection: Connection,
|
connection: Connection,
|
||||||
channel: Receiver<DaemonCommand>,
|
channel: Receiver<DaemonCommand<C::Command>>,
|
||||||
) -> Result<Daemon<C>> {
|
) -> Result<Daemon<C>> {
|
||||||
let services = JoinSet::new();
|
let services = JoinSet::new();
|
||||||
let token = CancellationToken::new();
|
let token = CancellationToken::new();
|
||||||
|
@ -84,7 +87,6 @@ impl<C: DaemonContext> Daemon<C> {
|
||||||
services,
|
services,
|
||||||
token,
|
token,
|
||||||
channel,
|
channel,
|
||||||
_context: PhantomData::default(),
|
|
||||||
};
|
};
|
||||||
daemon.add_service(log_receiver);
|
daemon.add_service(log_receiver);
|
||||||
|
|
||||||
|
@ -139,7 +141,9 @@ impl<C: DaemonContext> Daemon<C> {
|
||||||
None => Err(anyhow!("SIGHUP machine broke")),
|
None => Err(anyhow!("SIGHUP machine broke")),
|
||||||
},
|
},
|
||||||
msg = self.channel.recv() => match msg {
|
msg = self.channel.recv() => match msg {
|
||||||
Some(msg) => self.handle_message(&mut context, msg).await,
|
Some(msg) => {
|
||||||
|
self.handle_message(msg, &mut context).await
|
||||||
|
}
|
||||||
None => Err(anyhow!("All senders have been closed")),
|
None => Err(anyhow!("All senders have been closed")),
|
||||||
},
|
},
|
||||||
_ = sigquit.recv() => Err(anyhow!("Got SIGQUIT")),
|
_ = sigquit.recv() => Err(anyhow!("Got SIGQUIT")),
|
||||||
|
@ -165,8 +169,13 @@ impl<C: DaemonContext> Daemon<C> {
|
||||||
res.inspect_err(|e| error!("Encountered error: {e}"))
|
res.inspect_err(|e| error!("Encountered error: {e}"))
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn handle_message(&mut self, context: &mut C, cmd: DaemonCommand) -> Result<()> {
|
async fn handle_message(
|
||||||
|
&mut self,
|
||||||
|
cmd: DaemonCommand<C::Command>,
|
||||||
|
context: &mut C,
|
||||||
|
) -> Result<()> {
|
||||||
match cmd {
|
match cmd {
|
||||||
|
DaemonCommand::ContextCommand(cmd) => context.handle_command(cmd, self).await,
|
||||||
DaemonCommand::ReadConfig => match read_config(context).await {
|
DaemonCommand::ReadConfig => match read_config(context).await {
|
||||||
Ok(config) => context.reload(config, self).await,
|
Ok(config) => context.reload(config, self).await,
|
||||||
Err(error) => {
|
Err(error) => {
|
||||||
|
@ -179,6 +188,12 @@ impl<C: DaemonContext> Daemon<C> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn channel() -> (Sender<DaemonCommand>, Receiver<DaemonCommand>) {
|
// Rust doesn't support a good way to simplify this type yet
|
||||||
|
// See <https://github.com/rust-lang/rust/issues/8995>
|
||||||
|
#[allow(clippy::type_complexity)]
|
||||||
|
pub(crate) fn channel<C: DaemonContext>() -> (
|
||||||
|
Sender<DaemonCommand<C::Command>>,
|
||||||
|
Receiver<DaemonCommand<C::Command>>,
|
||||||
|
) {
|
||||||
mpsc::channel(10)
|
mpsc::channel(10)
|
||||||
}
|
}
|
||||||
|
|
|
@ -36,11 +36,15 @@ pub(crate) struct RootState {
|
||||||
#[derive(Copy, Clone, Default, Deserialize, Serialize, Debug)]
|
#[derive(Copy, Clone, Default, Deserialize, Serialize, Debug)]
|
||||||
pub(crate) struct RootServicesState {}
|
pub(crate) struct RootServicesState {}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub(crate) enum RootCommand {}
|
||||||
|
|
||||||
struct RootContext {}
|
struct RootContext {}
|
||||||
|
|
||||||
impl DaemonContext for RootContext {
|
impl DaemonContext for RootContext {
|
||||||
type State = RootState;
|
type State = RootState;
|
||||||
type Config = RootConfig;
|
type Config = RootConfig;
|
||||||
|
type Command = RootCommand;
|
||||||
|
|
||||||
fn user_config_path(&self) -> Result<PathBuf> {
|
fn user_config_path(&self) -> Result<PathBuf> {
|
||||||
Ok(path("/etc/steamos-manager"))
|
Ok(path("/etc/steamos-manager"))
|
||||||
|
@ -72,6 +76,14 @@ impl DaemonContext for RootContext {
|
||||||
// Nothing to do yet
|
// Nothing to do yet
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async fn handle_command(
|
||||||
|
&mut self,
|
||||||
|
_cmd: RootCommand,
|
||||||
|
_daemon: &mut Daemon<RootContext>,
|
||||||
|
) -> Result<()> {
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn create_connection() -> Result<Connection> {
|
async fn create_connection() -> Result<Connection> {
|
||||||
|
@ -93,7 +105,7 @@ pub async fn daemon() -> Result<()> {
|
||||||
|
|
||||||
let stdout_log = fmt::layer();
|
let stdout_log = fmt::layer();
|
||||||
let subscriber = Registry::default().with(stdout_log);
|
let subscriber = Registry::default().with(stdout_log);
|
||||||
let (_tx, rx) = channel();
|
let (_tx, rx) = channel::<RootContext>();
|
||||||
|
|
||||||
let connection = match create_connection().await {
|
let connection = match create_connection().await {
|
||||||
Ok(c) => c,
|
Ok(c) => c,
|
||||||
|
|
|
@ -41,6 +41,7 @@ struct UserContext {}
|
||||||
impl DaemonContext for UserContext {
|
impl DaemonContext for UserContext {
|
||||||
type State = UserState;
|
type State = UserState;
|
||||||
type Config = UserConfig;
|
type Config = UserConfig;
|
||||||
|
type Command = ();
|
||||||
|
|
||||||
#[cfg(not(test))]
|
#[cfg(not(test))]
|
||||||
fn user_config_path(&self) -> Result<PathBuf> {
|
fn user_config_path(&self) -> Result<PathBuf> {
|
||||||
|
@ -79,6 +80,15 @@ impl DaemonContext for UserContext {
|
||||||
// Nothing to do yet
|
// Nothing to do yet
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async fn handle_command(
|
||||||
|
&mut self,
|
||||||
|
_cmd: Self::Command,
|
||||||
|
_daemon: &mut Daemon<UserContext>,
|
||||||
|
) -> Result<()> {
|
||||||
|
// Nothing to do yet
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn create_connections() -> Result<(Connection, Connection)> {
|
async fn create_connections() -> Result<(Connection, Connection)> {
|
||||||
|
@ -103,7 +113,7 @@ pub async fn daemon() -> Result<()> {
|
||||||
|
|
||||||
let stdout_log = fmt::layer();
|
let stdout_log = fmt::layer();
|
||||||
let subscriber = Registry::default().with(stdout_log);
|
let subscriber = Registry::default().with(stdout_log);
|
||||||
let (_tx, rx) = channel();
|
let (_tx, rx) = channel::<UserContext>();
|
||||||
|
|
||||||
let (_session, system) = match create_connections().await {
|
let (_session, system) = match create_connections().await {
|
||||||
Ok(c) => c,
|
Ok(c) => c,
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue