blob: ba6a59a757657064a23a37fe65fcec3ba39b9052 [file]
// Copyright 2022 CeresDB Project Authors. Licensed under Apache-2.0.
use std::thread;
use log::error;
/// fork from https://github.com/tikv/tikv/blob/83d173a2c0058246631f0e71de74238ccff670fd/components/tikv_util/src/lib.rs#L429
/// Exit the whole process when panic.
pub fn set_panic_hook(panic_abort: bool) {
use std::{panic, process};
// HACK! New a backtrace ahead for caching necessary elf sections of this
// tikv-server, in case it can not open more files during panicking
// which leads to no stack info (0x5648bdfe4ff2 - <no info>).
//
// Crate backtrace caches debug info in a static variable `STATE`,
// and the `STATE` lives forever once it has been created.
// See more: https://github.com/alexcrichton/backtrace-rs/blob/\
// 597ad44b131132f17ed76bf94ac489274dd16c7f/\
// src/symbolize/libbacktrace.rs#L126-L159
// Caching is slow, spawn it in another thread to speed up.
thread::Builder::new()
.name("backtrace-loader".to_owned())
.spawn(backtrace::Backtrace::new)
.unwrap();
panic::set_hook(Box::new(move |info: &panic::PanicInfo<'_>| {
let msg = match info.payload().downcast_ref::<&'static str>() {
Some(s) => *s,
None => match info.payload().downcast_ref::<String>() {
Some(s) => &s[..],
None => "Box<Any>",
},
};
let thread = thread::current();
let name = thread.name().unwrap_or("<unnamed>");
let loc = info
.location()
.map(|l| format!("{}:{}", l.file(), l.line()));
let bt = backtrace::Backtrace::new();
error!(
"thread '{}' panicked '{}' at {:?}\n{:?}",
name,
msg,
loc.unwrap_or_else(|| "<unknown>".to_owned()),
bt
);
// There might be remaining logs in the async logger.
// To collect remaining logs and also collect future logs, replace the old one
// with a terminal logger.
// When the old global async logger is replaced, the old async guard will be
// taken and dropped. In the drop() the async guard, it waits for the
// finish of the remaining logs in the async logger.
if let Some(level) = ::log::max_level().to_level() {
let drainer = logger::term_drainer();
let _ = logger::init_log_from_drain(
drainer,
logger::convert_log_level_to_slog_level(level),
false, // Use sync logger to avoid an unnecessary log thread.
0,
false, // It is initialized already.
);
}
if panic_abort {
process::abort();
} else {
unsafe {
// Calling process::exit would trigger global static to destroy, like C++
// static variables of RocksDB, which may cause other threads encounter
// pure virtual method call. So calling libc::_exit() instead to skip the
// cleanup process.
libc::_exit(1);
}
}
}))
}
#[cfg(test)]
mod tests {
use std::{io::Read, time::Duration};
use nix::{
sys::wait::{wait, WaitStatus},
unistd::{fork, ForkResult},
};
use slog::{self, Drain, Level, OwnedKVList, Record};
use crate::panic::set_panic_hook;
/// Create a child process and wait to get its exit code.
fn run_and_wait_child_process(child: impl Fn()) -> Result<i32, String> {
match unsafe { fork() } {
Ok(ForkResult::Parent { .. }) => match wait().unwrap() {
WaitStatus::Exited(_, status) => Ok(status),
v => Err(format!("{v:?}")),
},
Ok(ForkResult::Child) => {
child();
std::process::exit(0);
}
Err(e) => Err(format!("Fork failed: {e}")),
}
}
#[ignore = "This test will fail on github ubuntu runner"]
#[test]
fn test_panic_hook() {
use gag::BufferRedirect;
struct DelayDrain<D>(D);
impl<D> Drain for DelayDrain<D>
where
D: Drain,
<D as Drain>::Err: std::fmt::Display,
{
type Err = <D as Drain>::Err;
type Ok = <D as Drain>::Ok;
fn log(
&self,
record: &Record<'_>,
values: &OwnedKVList,
) -> Result<Self::Ok, Self::Err> {
std::thread::sleep(Duration::from_millis(100));
self.0.log(record, values)
}
}
let mut stderr = BufferRedirect::stderr().unwrap();
let status = run_and_wait_child_process(|| {
set_panic_hook(false);
let drainer = logger::term_drainer();
let _ = logger::init_log_from_drain(
drainer,
Level::Debug,
true, // use async drainer
0,
true, // init std log
);
let _ = std::thread::spawn(|| {
// let the global logger is held by the other thread, so the
// drop() of the async drain is not called in time.
let _guard = slog_global::borrow_global();
std::thread::sleep(Duration::from_secs(1));
});
panic!("test");
})
.unwrap();
assert_eq!(status, 1);
let mut panic = String::new();
stderr.read_to_string(&mut panic).unwrap();
assert!(!panic.is_empty());
}
}