use tikv_jemalloc_ctl::raw;
pub use tikv_jemallocator;
async fn blocking<T: Send + 'static>(work: impl FnOnce() -> T + Send + 'static) -> Result<T, tokio::task::JoinError> {
tokio::task::spawn_blocking(work).await
}
fn register_signal() -> std::io::Result<tokio::signal::unix::Signal> {
tokio::signal::unix::signal(tokio::signal::unix::SignalKind::user_defined1())
}
#[cfg(test)]
fn signal_ready() -> &'static tokio::sync::Notify {
static READY: std::sync::OnceLock<tokio::sync::Notify> = std::sync::OnceLock::new();
READY.get_or_init(tokio::sync::Notify::new)
}
pub async fn run() -> crate::Result<()> {
let mut sig = register_signal()?;
#[cfg(test)]
signal_ready().notify_one();
let active = match unsafe { raw::read::<bool>(b"prof.active\0") } {
Ok(true) => {
tracing::info!("jemalloc heap profiling is active");
true
}
Ok(false) => {
tracing::info!(
"jemalloc profiling compiled in but not active. Set MALLOC_CONF=prof:true,prof_active:true at startup to enable"
);
false
}
Err(err) => {
tracing::debug!(%err, "jemalloc profiling not available");
false
}
};
loop {
sig.recv().await;
if !active {
tracing::warn!("heap profile requested while jemalloc profiling is inactive");
continue;
}
match blocking(|| unsafe { raw::write(b"prof.dump\0", std::ptr::null::<u8>()) }).await {
Ok(Ok(())) => tracing::info!("heap profile dumped"),
Ok(Err(err)) => tracing::error!(%err, "failed to dump heap profile"),
Err(err) => tracing::error!(%err, "heap profile task failed"),
}
}
}
#[cfg(test)]
mod tests {
const USR1_CHILD: &str = "MOQ_NATIVE_USR1_CHILD";
#[tokio::test(flavor = "current_thread")]
async fn blocking_work_leaves_the_runtime_thread() {
let runtime = std::thread::current().id();
let worker = super::blocking(|| std::thread::current().id()).await.unwrap();
assert_ne!(runtime, worker);
}
#[test]
fn usr1_is_caught_without_active_profiling() {
let output = std::process::Command::new(std::env::current_exe().unwrap())
.args(["--exact", "jemalloc::tests::usr1_child_survives_without_profiling"])
.env(USR1_CHILD, "1")
.env("MALLOC_CONF", "prof:false,prof_active:false")
.output()
.unwrap();
assert!(
output.status.success(),
"child did not survive SIGUSR1:\n{}",
String::from_utf8_lossy(&output.stderr)
);
}
#[test]
fn usr1_child_survives_without_profiling() {
if std::env::var_os(USR1_CHILD).is_none() {
return;
}
assert!(!matches!(
unsafe { super::raw::read::<bool>(b"prof.active\0") },
Ok(true)
));
tokio::runtime::Builder::new_current_thread()
.enable_all()
.build()
.unwrap()
.block_on(async {
let runner = tokio::spawn(super::run());
super::signal_ready().notified().await;
unsafe { libc::raise(libc::SIGUSR1) };
tokio::task::yield_now().await;
assert!(!runner.is_finished());
runner.abort();
});
}
}