1use std::{path::PathBuf, process::exit};
2
3use anyhow::Result;
4use async_trait::async_trait;
5use clap::{Arg, Command};
6use tokio::runtime;
7use tracing::instrument;
8
9use crate::logging;
10
11const VERSION: &str = env!("CARGO_PKG_VERSION");
12const MINIMUM_WORKER_THREAD_COUNT: usize = 6;
13
14#[async_trait]
15pub trait Daemon {
16 type Config;
17
18 fn load_config(&self, path_maybe: &Option<PathBuf>) -> Result<Self::Config>;
19
20 async fn start(&mut self, cfg: Self::Config) -> Result<()>;
21 async fn stop(&mut self) -> Result<()>;
22}
23
24fn worker_thread_count() -> usize {
25 let core_count = num_cpus::get();
26 core_count.max(MINIMUM_WORKER_THREAD_COUNT)
27}
28
29#[instrument(level = "trace")]
30fn init_runtime() -> Result<runtime::Runtime> {
31 let threads = worker_thread_count();
32 let rt = runtime::Builder::new_multi_thread()
33 .enable_io()
34 .enable_time()
35 .worker_threads(threads)
36 .build()?;
37 tracing::trace!(threads = threads, "initialized tokio runtime");
38 Ok(rt)
39}
40
41fn main_loop<D: Daemon>(
42 cfg_path: Option<PathBuf>,
43 log_config: Option<PathBuf>,
44 mut daemon: D,
45) -> Result<()> {
46 logging::init_logger(&log_config)?;
47 let cfg = daemon.load_config(&cfg_path)?;
48
49 let rt = init_runtime()?;
50 rt.block_on(async move {
51 daemon.start(cfg).await?;
52 tokio::signal::ctrl_c().await?;
53 daemon.stop().await?;
54
55 Ok(())
56 })
57}
58
59pub struct DaemonProcess {}
60
61impl DaemonProcess {
62 pub fn start<D: Daemon>(name: &str, about: &str, daemon: D) {
63 let matches = Command::new(name)
64 .version(VERSION)
65 .about(about)
66 .arg(
67 Arg::new("config")
68 .short('c')
69 .long("cfg")
70 .value_name("FILE")
71 .help("Sets a config file")
72 .takes_value(true),
73 )
74 .arg(
75 Arg::new("log_config")
76 .short('l')
77 .long("log")
78 .help("Sets the log config file")
79 .takes_value(true),
80 )
81 .get_matches();
82
83 let cfg: Option<PathBuf> = matches.value_of_t("config").ok();
84
85 let log_config: Option<PathBuf> = matches.value_of_t("log_config").ok();
86
87 if let Err(e) = main_loop(cfg, log_config, daemon) {
88 tracing::error!("fatal: {}", e);
89 exit(1);
90 }
91 }
92}