pub mod selftest;
pub use mimalloc::MiMalloc;
const MI_OPTION_PURGE_DELAY: i32 = 15;
const MI_OPTION_ARENA_PURGE_MULT: i32 = 24;
pub const DEFAULT_PURGE_DELAY_MS: i64 = 10;
pub const GENERIC_PURGE_DELAY_ENV: &str = "NORDISK_MIMALLOC_PURGE_DELAY_MS";
pub fn product_purge_delay_env(product: &str) -> String {
let stem: String = product
.chars()
.map(|c| if c == '-' || c == '.' { '_' } else { c })
.collect();
format!("{}_MIMALLOC_PURGE_DELAY_MS", stem.to_uppercase())
}
pub const MIMALLOC_V3_DEFAULT_PURGE_DELAY_MS: i64 = 1000;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum PurgeDelayPlan {
Set(i64),
LeaveAlone,
}
pub fn purge_delay_plan(env: Option<&str>) -> PurgeDelayPlan {
let raw = match env.map(str::trim) {
None | Some("") => return PurgeDelayPlan::Set(DEFAULT_PURGE_DELAY_MS),
Some(v) => v,
};
if raw.eq_ignore_ascii_case("off") {
return PurgeDelayPlan::LeaveAlone;
}
match raw.parse::<i64>() {
Ok(ms) if ms >= 0 => PurgeDelayPlan::Set(ms),
_ => PurgeDelayPlan::Set(DEFAULT_PURGE_DELAY_MS),
}
}
pub fn purge_delay_plan_from(product: Option<&str>, generic: Option<&str>) -> PurgeDelayPlan {
fn said_something(v: Option<&str>) -> Option<&str> {
v.map(str::trim).filter(|s| !s.is_empty())
}
purge_delay_plan(said_something(product).or_else(|| said_something(generic)))
}
pub fn purge_delay_ms(product: &str) -> PurgeDelayPlan {
let specific = std::env::var(product_purge_delay_env(product)).ok();
let generic = std::env::var(GENERIC_PURGE_DELAY_ENV).ok();
purge_delay_plan_from(specific.as_deref(), generic.as_deref())
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct AllocTuning {
pub product: String,
pub purge_delay_before_ms: i64,
pub purge_delay_after_ms: i64,
pub arena_purge_mult: i64,
pub applied: bool,
}
impl AllocTuning {
pub fn log_line(&self) -> String {
if !self.applied {
return format!(
"{}: allocator purge_delay left at {} ms (`{}`/`{GENERIC_PURGE_DELAY_ENV}` \
said off, or an unrecognised mimalloc option layout) — a dropped \
transient may stay resident; see nordisk_alloc",
self.product,
self.purge_delay_before_ms,
product_purge_delay_env(&self.product),
);
}
format!(
"{}: allocator purge_delay {} ms → {} ms (arena_purge_mult {}) — freed \
transients return to the OS",
self.product,
self.purge_delay_before_ms,
self.purge_delay_after_ms,
self.arena_purge_mult,
)
}
}
pub fn tune_for_long_lived_server(product: &str) -> AllocTuning {
let before = unsafe { libmimalloc_sys::mi_option_get(MI_OPTION_PURGE_DELAY) } as i64;
let mult = || unsafe { libmimalloc_sys::mi_option_get(MI_OPTION_ARENA_PURGE_MULT) } as i64;
let untouched = |arena_purge_mult| AllocTuning {
product: product.to_string(),
purge_delay_before_ms: before,
purge_delay_after_ms: before,
arena_purge_mult,
applied: false,
};
let want = match purge_delay_ms(product) {
PurgeDelayPlan::LeaveAlone => return untouched(mult()),
PurgeDelayPlan::Set(ms) => ms,
};
if !(before == MIMALLOC_V3_DEFAULT_PURGE_DELAY_MS || before == want) {
return untouched(mult());
}
unsafe {
libmimalloc_sys::mi_option_set(MI_OPTION_PURGE_DELAY, want as std::os::raw::c_long);
libmimalloc_sys::mi_option_set(MI_OPTION_ARENA_PURGE_MULT, 1);
}
AllocTuning {
product: product.to_string(),
purge_delay_before_ms: before,
purge_delay_after_ms: current_purge_delay_ms(),
arena_purge_mult: mult(),
applied: true,
}
}
pub fn current_purge_delay_ms() -> i64 {
unsafe { libmimalloc_sys::mi_option_get(MI_OPTION_PURGE_DELAY) as i64 }
}
pub fn self_rss_kb() -> Option<u64> {
let statm = std::fs::read_to_string("/proc/self/statm").ok()?;
let pages: u64 = statm.split_whitespace().nth(1)?.parse().ok()?;
let page_kb = 4u64; Some(pages * page_kb)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn the_purge_delay_ladder_never_falls_back_to_the_default_that_caused_the_oom() {
assert_eq!(
purge_delay_plan(None),
PurgeDelayPlan::Set(DEFAULT_PURGE_DELAY_MS)
);
assert_eq!(
purge_delay_plan(Some(" ")),
PurgeDelayPlan::Set(DEFAULT_PURGE_DELAY_MS)
);
assert_eq!(purge_delay_plan(Some("0")), PurgeDelayPlan::Set(0));
assert_eq!(purge_delay_plan(Some(" 25 ")), PurgeDelayPlan::Set(25));
assert_eq!(purge_delay_plan(Some("OFF")), PurgeDelayPlan::LeaveAlone);
assert_eq!(purge_delay_plan(Some("off")), PurgeDelayPlan::LeaveAlone);
assert_eq!(
purge_delay_plan(Some("ten")),
PurgeDelayPlan::Set(DEFAULT_PURGE_DELAY_MS)
);
assert_eq!(
purge_delay_plan(Some("-1")),
PurgeDelayPlan::Set(DEFAULT_PURGE_DELAY_MS)
);
assert_ne!(
DEFAULT_PURGE_DELAY_MS, MIMALLOC_V3_DEFAULT_PURGE_DELAY_MS,
"installing v3's own default would be a no-op fix"
);
}
#[test]
fn the_products_own_variable_still_works_and_an_empty_one_does_not_shadow_the_fleet() {
assert_eq!(
product_purge_delay_env("dwarves"),
"DWARVES_MIMALLOC_PURGE_DELAY_MS"
);
assert_eq!(
product_purge_delay_env("nornir"),
"NORNIR_MIMALLOC_PURGE_DELAY_MS"
);
assert_eq!(
product_purge_delay_env("korp-server"),
"KORP_SERVER_MIMALLOC_PURGE_DELAY_MS"
);
assert_eq!(
purge_delay_plan_from(Some("off"), Some("25")),
PurgeDelayPlan::LeaveAlone
);
assert_eq!(
purge_delay_plan_from(Some("40"), Some("25")),
PurgeDelayPlan::Set(40)
);
assert_eq!(
purge_delay_plan_from(None, Some("25")),
PurgeDelayPlan::Set(25)
);
assert_eq!(
purge_delay_plan_from(Some(""), Some("off")),
PurgeDelayPlan::LeaveAlone
);
assert_eq!(
purge_delay_plan_from(Some(" "), Some("25")),
PurgeDelayPlan::Set(25)
);
assert_eq!(
purge_delay_plan_from(None, None),
PurgeDelayPlan::Set(DEFAULT_PURGE_DELAY_MS)
);
}
#[test]
fn the_named_option_row_is_purge_delay_and_the_tuning_applies_idempotently() {
let got = unsafe { libmimalloc_sys::mi_option_get(MI_OPTION_PURGE_DELAY) } as i64;
assert_eq!(
got, MIMALLOC_V3_DEFAULT_PURGE_DELAY_MS,
"option row {MI_OPTION_PURGE_DELAY} no longer reads mimalloc v3's \
purge_delay default ({MIMALLOC_V3_DEFAULT_PURGE_DELAY_MS} ms) but {got} — \
the option table moved; re-derive the index from \
libmimalloc-sys/c_src/mimalloc/v3/src/options.c before trusting it"
);
let mult = unsafe { libmimalloc_sys::mi_option_get(MI_OPTION_ARENA_PURGE_MULT) } as i64;
assert_eq!(
mult, 1,
"option row {MI_OPTION_ARENA_PURGE_MULT} no longer reads arena_purge_mult's \
v3 default (1) but {mult}"
);
let first = tune_for_long_lived_server("nordisk-alloc-selftest");
assert!(first.applied, "{}", first.log_line());
assert_eq!(first.purge_delay_after_ms, DEFAULT_PURGE_DELAY_MS);
assert_eq!(first.arena_purge_mult, 1);
assert!(
first.log_line().contains("return to the OS"),
"{}",
first.log_line()
);
let second = tune_for_long_lived_server("nordisk-alloc-selftest");
assert!(
second.applied,
"a second call is a no-op write, not a refusal"
);
assert_eq!(second.purge_delay_before_ms, DEFAULT_PURGE_DELAY_MS);
assert_eq!(second.purge_delay_after_ms, DEFAULT_PURGE_DELAY_MS);
}
#[test]
fn the_opt_out_is_reported_and_never_silent() {
let untuned = AllocTuning {
product: "nornir".into(),
purge_delay_before_ms: MIMALLOC_V3_DEFAULT_PURGE_DELAY_MS,
purge_delay_after_ms: MIMALLOC_V3_DEFAULT_PURGE_DELAY_MS,
arena_purge_mult: 1,
applied: false,
};
let line = untuned.log_line();
assert!(line.contains("may stay resident"), "{line}");
assert!(
line.contains("1000"),
"the line must name the value in force: {line}"
);
assert!(
line.contains("NORNIR_MIMALLOC_PURGE_DELAY_MS"),
"the line must name the variable that turns it back on FOR THIS PRODUCT: {line}"
);
assert!(line.starts_with("nornir:"), "{line}");
}
#[test]
fn self_rss_is_readable_and_nonzero() {
let rss = self_rss_kb().expect("/proc/self/statm on linux");
assert!(rss > 1024, "an rss of {rss} KiB is not a running process");
}
}