Something went wrong. Try again.
fork of hydrant
Something went wrong. Try again.
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114use futures::FutureExt;use hydrant::config::Config;use hydrant::control::{ApiBinds, Hydrant};use std::net::{IpAddr, Ipv4Addr, Ipv6Addr, SocketAddr};
const DEFAULT_API_PORT: u16 = 3000;const DEFAULT_DEBUG_PORT: u16 = DEFAULT_API_PORT + 1;
fn default_api_binds() -> ApiBinds { ApiBinds::try_from_iter([ SocketAddr::new(IpAddr::V4(Ipv4Addr::UNSPECIFIED), DEFAULT_API_PORT), SocketAddr::new(IpAddr::V6(Ipv6Addr::UNSPECIFIED), DEFAULT_API_PORT), ]) .expect("two-element literal is non-empty")}
struct AppConfig { api_binds: ApiBinds, enable_debug: bool, debug_port: u16,}
impl AppConfig { fn from_env() -> miette::Result<Self> { use hydrant::__cfg as cfg; let api_binds = parse_api_binds()?; let enable_debug = cfg!("ENABLE_DEBUG", false); let debug_port_default = api_binds .iter() .next() .expect("ApiBinds is non-empty by construction") .port() .checked_add(1) .unwrap_or(DEFAULT_DEBUG_PORT); let debug_port = cfg!("DEBUG_PORT", debug_port_default); Ok(Self { api_binds, enable_debug, debug_port, }) }}
fn parse_api_binds() -> miette::Result<ApiBinds> { let Ok(raw) = std::env::var("HYDRANT_API_BIND") else { return Ok(default_api_binds()); }; let parsed = raw .split(',') .map(str::trim) .filter(|s| !s.is_empty()) .map(|s| { s.parse::<SocketAddr>() .map_err(|e| miette::miette!("invalid HYDRANT_API_BIND entry `{s}`: {e}")) }) .collect::<miette::Result<Vec<_>>>()?; ApiBinds::try_from_iter(parsed) .ok_or_else(|| miette::miette!("HYDRANT_API_BIND is set but contains no addresses"))}
#[cfg(feature = "alloc-mimalloc")]#[global_allocator]static GLOBAL: mimalloc::MiMalloc = mimalloc::MiMalloc;
#[cfg(feature = "alloc-jemalloc")]#[global_allocator]static GLOBAL: tikv_jemallocator::Jemalloc = tikv_jemallocator::Jemalloc;
// bake the tuned jemalloc runtime config into the binary. the prefixed tikv build reads// the `_rjem_malloc_conf` symbol (plain `malloc_conf` silently no-ops). `percpu_arena`// pins one arena per cpu, bounding per-arena dirty-page retention — the dominant rss lever// here (~2.4x less resident than the default 4*ncpu arenas); `background_thread` purges// asynchronously so `dirty_decay_ms:1000`/`muzzy_decay_ms:0` return pages without sync// munmap churn. measured 1.72 GiB steady vs 4.14 for mimalloc. both options are linux-only// (they no-op or warn elsewhere), so gate to linux; other targets get vanilla jemalloc.// overridable at runtime via _RJEM_MALLOC_CONF.#[cfg(all(feature = "alloc-jemalloc", target_os = "linux"))]#[allow(non_upper_case_globals)]#[unsafe(export_name = "_rjem_malloc_conf")]pub static _rjem_malloc_conf: Option<&'static core::ffi::CStr> = Some(c"background_thread:true,percpu_arena:percpu,dirty_decay_ms:1000,muzzy_decay_ms:0");
#[cfg(feature = "alloc-snmalloc")]#[global_allocator]static GLOBAL: snmalloc_rs::SnMalloc = snmalloc_rs::SnMalloc;
#[tokio::main]async fn main() -> miette::Result<()> { rustls::crypto::aws_lc_rs::default_provider() .install_default() .ok();
let cfg = Config::from_env()?; let app = AppConfig::from_env()?;
let env_filter = tracing_subscriber::EnvFilter::builder() .with_default_directive(tracing::Level::INFO.into()) .from_env_lossy(); tracing_subscriber::fmt().with_env_filter(env_filter).init();
let hydrant = Hydrant::new(cfg).await?;
let debug_fut = app .enable_debug .then(|| hydrant.serve_debug(app.debug_port).boxed()) .unwrap_or_else(|| std::future::pending().boxed());
tokio::select! { r = hydrant.run()? => r, r = hydrant.serve(app.api_binds) => r, r = debug_fut => r, }}