lib.rs (4571B)
1 /* 2 This file is part of TALER 3 Copyright (C) 2024, 2025, 2026 Taler Systems SA 4 5 TALER is free software; you can redistribute it and/or modify it under the 6 terms of the GNU Affero General Public License as published by the Free Software 7 Foundation; either version 3, or (at your option) any later version. 8 9 TALER is distributed in the hope that it will be useful, but WITHOUT ANY 10 WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR 11 A PARTICULAR PURPOSE. See the GNU Affero General Public License for more details. 12 13 You should have received a copy of the GNU Affero General Public License along with 14 TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/> 15 */ 16 17 use std::{path::PathBuf, time::Duration}; 18 19 use config::{Config, parser::ConfigSource}; 20 use mimalloc::MiMalloc; 21 use tracing::error; 22 use tracing_subscriber::util::SubscriberInitExt; 23 24 use crate::log::taler_logger; 25 26 pub mod api; 27 pub mod bench; 28 pub mod cli; 29 pub mod config; 30 pub mod db; 31 pub mod encoding; 32 pub mod error; 33 pub mod error_code; 34 pub mod json_file; 35 pub mod log; 36 pub mod signature; 37 pub mod types; 38 39 #[global_allocator] 40 static GLOBAL: MiMalloc = MiMalloc; 41 42 #[derive(clap::Parser, Debug, Clone)] 43 pub struct CommonArgs { 44 /// Specifies the configuration file 45 #[arg(short, long, global = true)] 46 config: Option<PathBuf>, 47 48 /// Configure logging to use LOGLEVEL 49 #[arg(short('L'), long, global = true)] 50 log: Option<tracing::Level>, 51 52 /// Show logs from all sources 53 #[arg(short, long, global = true, hide = true)] 54 verbose: bool, 55 } 56 57 pub fn taler_main( 58 src: ConfigSource, 59 args: CommonArgs, 60 app: impl AsyncFnOnce(&Config) -> Result<(), anyhow::Error>, 61 ) { 62 taler_logger(args.log, args.verbose).init(); 63 let cfg = match Config::load(src, args.config) { 64 Ok(cfg) => cfg, 65 Err(err) => { 66 error!(target: "config", "{}", err); 67 std::process::exit(6); 68 } 69 }; 70 71 // Setup async runtime 72 let runtime = tokio::runtime::Builder::new_multi_thread() 73 .enable_all() 74 .build() 75 .unwrap(); 76 77 // Run app 78 let result = runtime.block_on(app(&cfg)); 79 if let Err(err) = result { 80 error!(target: "cli", "{}", err); 81 std::process::exit(error_exit_status(&err)); 82 } 83 } 84 85 /// DD102: only diagnosed configuration errors suppress service recovery. 86 fn error_exit_status(err: &anyhow::Error) -> i32 { 87 if err.chain().any(|cause| { 88 cause.is::<config::parser::ParserErr>() 89 || cause.is::<config::ValueErr>() 90 || cause.is::<config::PathsubErr>() 91 }) { 92 6 93 } else { 94 1 95 } 96 } 97 98 #[cfg(test)] 99 mod exit_status_tests { 100 use super::*; 101 102 #[test] 103 fn configuration_errors_keep_their_status_through_context() { 104 let cfg = Config::from_mem("[test]\nport = invalid\n").unwrap(); 105 let err = cfg 106 .section("test") 107 .number::<u16>("port") 108 .require() 109 .unwrap_err(); 110 assert_eq!( 111 6, 112 error_exit_status(&anyhow::Error::new(err).context("starting server")) 113 ); 114 let err = cfg.section("test").str("missing").require().unwrap_err(); 115 assert_eq!(6, error_exit_status(&anyhow::Error::new(err))); 116 let err = Config::from_mem("not a configuration entry").unwrap_err(); 117 assert_eq!(6, error_exit_status(&anyhow::Error::new(err))); 118 } 119 120 #[test] 121 fn unavailable_dependencies_remain_restartable() { 122 let err = std::io::Error::from(std::io::ErrorKind::ConnectionRefused); 123 assert_eq!( 124 1, 125 error_exit_status(&anyhow::Error::new(err).context("database")) 126 ); 127 } 128 } 129 130 /// Infinite exponential backoff with decorrelated jitter 131 pub struct ExpoBackoffDecorr { 132 base: u32, 133 max: u32, 134 factor: f32, 135 sleep: u32, 136 } 137 138 impl ExpoBackoffDecorr { 139 pub fn new(base: Duration, max: Duration, factor: f32) -> Self { 140 Self { 141 base: base.as_millis() as u32, 142 max: max.as_millis() as u32, 143 factor, 144 sleep: base.as_millis() as u32, 145 } 146 } 147 148 pub fn backoff(&mut self) -> Duration { 149 self.sleep = 150 rand::random_range(self.base..(self.sleep as f32 * self.factor) as u32).min(self.max); 151 Duration::from_millis(self.sleep as u64) 152 } 153 154 pub fn reset(&mut self) { 155 self.sleep = self.base 156 } 157 } 158 159 impl Default for ExpoBackoffDecorr { 160 fn default() -> Self { 161 Self::new(Duration::from_millis(400), Duration::from_secs(30), 2.5) 162 } 163 }