CHAPTER 16
Crash 函数手册
依据源码:include/Crash.h、src/Crash.cpp。
0. 编译器与平台宏(Crash.h L8–15)
在 Crash.h 中根据编译器定义以下宏之一(用于 printCrashInfo 分支与日志中的 Compiler 字段):
| 宏 | 条件 |
|---|---|
COMPILER_MSVC |
_MSC_VER |
COMPILER_MINGW |
__MINGW32__ |
COMPILER_LINUX |
__linux__ |
注意:printCrashInfo 写日志时还引用了 COMPILER_CLANG、COMPILER_GCC(Crash.cpp L284–287),这些由 CMake/工程 定义;若未定义,会落入 Compiler: Unknown。这与头文件三元宏并存,属配置层细节。
1. 类型别名 CleanupCallback
- 定义:
using CleanupCallback = std::function<void()>(Crash.hL39) - 含义:在崩溃或主动
gracefulExit时,在进程退出前执行的一段用户逻辑(例如关闭 socket、刷日志)。 - 线程语义:在
gracefulExit调用栈内同步执行;应简短、避免再次死锁。
2. 类 CrashHandler — 公开接口
2.1 static CrashHandler& instance()
- 位置:
Crash.hL44–47 - 实现:Meyers 单例,
static CrashHandler handler。 - 线程安全:C++11 起静态局部变量初始化线程安全。
- 生命周期:进程级,不手动销毁。
2.2 void init(const CleanupCallback& cleanupFunc)
- 声明:
Crash.hL50 - 定义:
Crash.cppL26–80 - 作用:安装各类崩溃/终止钩子,并保存清理回调。
步骤拆解
- 保存回调:
cleanupCallback_ = cleanupFunc - 重置退出标志:
isExiting_ = false(允许再次init时重新注册语义上“新的一次”;实际项目通常只init一次) std::set_terminate(terminateHandler)
未捕获的 C++ 异常导致std::terminate时进入terminateHandler。- 信号数组(
Crash.cppL34–44)
SIGINT, SIGTERM, SIGSEGV, SIGABRT, SIGILL, SIGFPE,Linux 额外SIGBUS。 - 逐个注册信号
- Windows:
signal(sig, signalHandler),失败打印cerr - 非 Windows:
sigaction,sa_handler = signalHandler,SA_RESTART,失败打印cerr
- Windows:
- 平台扩展
- Windows:
SetUnhandledExceptionFilter(sehExceptionHandler);SetConsoleCtrlHandler(consoleCtrlHandler, TRUE) - Linux:再对
SIGQUIT注册一次sigaction(与上面相同 handler)
- Windows:
- 打印:
Crash handler initialized successfully
可重复调用行为
- 多次
init会再次set_terminate、signal/sigaction、Windows 过滤器注册;行为依赖 CRT/OS,建议进程内只调用一次。
2.3 void gracefulExit(int exitCode = 1, const std::string& reason = "Unknown crash")
- 声明:
Crash.hL53 - 定义:
Crash.cppL83–122 - 作用:统一的“崩溃/强制退出”出口:打印、写诊断、执行清理、立即终止进程。
步骤拆解
isExiting_.exchange(true)
若已为true,直接 return(防止多线程重复进入完整退出流程)。std::lock_guard<std::mutex> lock(exitMutex_)
序列化退出阶段的日志与清理(与exchange配合:只有首个线程做重活)。- stderr 横幅:Reason、Exit code。
printCrashInfo(exitCode, reason)
控制台诊断 + 追加写crash_log.txt(见第 4 节)。- 清理回调
若cleanupCallback_非空:try/catch 包裹调用;清理失败只打日志,不阻止退出。 - 终止进程
- Windows:
ExitProcess(static_cast<UINT>(exitCode)) - Linux:
_exit(exitCode)
- Windows:
与 exit() 的差异
_exit/ExitProcess:不运行完整 C++ 静态析构、不刷全量 stdio;适合崩溃路径,避免在已损坏状态下再执行复杂析构。
面试点
- 首入者执行清理;其它线程若再触发
gracefulExit会在exchange后直接返回,可能仍在跑业务——需业务层配合停服。
3. CrashHandler — 私有成员
| 成员 | 含义 |
|---|---|
CleanupCallback cleanupCallback_ |
init 注入的清理逻辑 |
std::atomic<bool> isExiting_ |
是否已进入/完成退出流程的门闩 |
std::mutex exitMutex_ |
保护退出阶段临界区 |
私有构造 / 析构
CrashHandler():cleanupCallback_(nullptr),isExiting_(false)~CrashHandler() = default:单例进程结束时随静态存储期销毁,通常发生在main返回之后;崩溃路径已_exit,往往走不到这里。
4. static void signalHandler(int sig)
- 声明:
Crash.hL64 - 定义:
Crash.cppL125–141 - 作用:将信号编号映射为可读
reason字符串,调用instance().gracefulExit(sig, reason)。
映射表
| 信号 | reason 文案 |
|---|---|
SIGINT |
Ctrl+C |
SIGTERM |
Kill |
SIGSEGV |
段错误 |
SIGABRT |
abort |
SIGILL |
非法指令 |
SIGFPE |
浮点异常 |
Linux SIGBUS |
总线错误 |
Linux SIGQUIT |
Ctrl+\ |
| default | Unknown signal (编号) |
注意:gracefulExit 的第一个参数此处为 sig,在 Linux 上会作为“退出码”传入 _exit(sig);与业务约定的 0–255 可能不一致,属实现细节。
5. Windows 专有静态函数
5.1 static LONG WINAPI sehExceptionHandler(EXCEPTION_POINTERS* pException)
- 声明:
Crash.hL68 - 定义:
Crash.cppL144–166 - 作用:未处理 SEH 时由系统调用;解析
ExceptionCode,gracefulExit(code, reason),返回EXCEPTION_EXECUTE_HANDLER。
显式映射的异常码
EXCEPTION_ACCESS_VIOLATIONEXCEPTION_INT_DIVIDE_BY_ZEROEXCEPTION_STACK_OVERFLOW- 其他:格式化为十六进制码字符串
空指针:若 pException 为空,exceptionCode = 0。
5.2 static BOOL WINAPI consoleCtrlHandler(DWORD dwCtrlType)
- 声明:
Crash.hL70 - 定义:
Crash.cppL171–188 - 作用:控制台控制事件(关闭窗口、Ctrl+C、关机等)映射为 reason,调用
gracefulExit((int)dwCtrlType, reason),返回TRUE。
6. static void terminateHandler()
- 声明:
Crash.hL74 - 定义:
Crash.cppL192–202 - 作用:
std::terminate钩子;尝试throw;在terminate上下文中捕获std::exception的what,否则标记未知类型;最后gracefulExit(EXIT_FAILURE, reason)。
限制:在部分实现中 terminate 时 throw; 行为依赖标准库;此处用于尽力提取信息。
7. static void printCrashInfo(int sig, const std::string& reason)
- 声明:
Crash.hL77 - 定义:
Crash.cppL220–298 - 参数:
sig:在函数开头(void)sig,控制台分支不使用;仅用于写文件时作为ExitCode字段(L277)。reason:人类可读原因;MinGW 分支会再打一份到cerr。
控制台输出(条件编译)
COMPILER_MSVC:打印 Windows/MSVC 标题与时间;注释称 MSVC 栈回溯逻辑省略。COMPILER_MINGW:时间、reason;SymInitialize+ 模块快照打印前 10 个模块基址;提示 MinGW 无完整 SEH 栈回溯。COMPILER_LINUX:打印 Linux 标题与时间;注释称 Linux 专属回溯省略。
文件输出(跨分支共用)
- 追加打开
crash_log.txt - 一行:
[时间] ExitCode: <sig>, Reason: <reason> - 下一行:Compiler 类型(依赖
COMPILER_*宏组合) - 分隔线
----------------------------------------
辅助函数 formatCurrentTime()(文件内 static)
- 位置:
Crash.cppL205–216 - 作用:
localtime_s(Windows)或localtime_r(Linux),格式%Y-%m-%d %H:%M:%S。
8. 宏 SAFE_ASSERT(expr)
- 位置:
Crash.hL88–93 - 展开:若
!(expr),则CrashHandler::instance().gracefulExit(1, "Assert failed: " #expr)。 - 与
assert()区别:不依赖NDEBUG;失败即走统一崩溃退出与清理(若已init)。
9. 与 Server 的集成(调用链记忆)
Server 构造函数中典型写法:
CrashHandler::instance().init([this]() { this->cleanup(); });
因此 SIGINT/SIGTERM、未处理异常、Windows SEH/控制台事件等路径最终会尝试执行 Server::cleanup()(在仍安全的前提下)。
10. 异步信号安全(面试高频)
在 signalHandler 中调用 gracefulExit,内部大量使用 iostream、锁、回调——不属于 POSIX 异步信号安全函数。
- 工程上常见折中:信号处理器只做
write/置原子标志,由主线程收尾。 - 本实现优先“统一落盘与清理”,适合服务端工具场景;严谨生产可再分层。
下一模块(索引顺序)
DBConnector → docs/modules/detail/DBConnector_FUNCTIONS.md。