#include "monitor.h" #include #include #include #include #include #include #include #include #include #include #include #include #include namespace vdi { namespace { Monitor* g_monitor = nullptr; void onSignal(int) { if (g_monitor) { g_monitor->requestStop(); } } // 主进程写心跳文件;Monitor 读取其修改时间判断是否假死 bool heartbeatAgeMs(const std::string& path, long long& ageMs) { struct stat st {}; if (stat(path.c_str(), &st) != 0) { return false; } const auto now = std::chrono::system_clock::now(); const auto mtime = std::chrono::system_clock::from_time_t(st.st_mtime); ageMs = std::chrono::duration_cast(now - mtime).count(); return true; } } // namespace Monitor::Monitor(MonitorConfig config) : config_(std::move(config)) {} void Monitor::requestStop() { stopRequested_ = true; } void Monitor::log(const std::string& message) const { if (!config_.verbose) { return; } const std::time_t now = std::time(nullptr); char timeBuf[32] = {0}; std::strftime(timeBuf, sizeof(timeBuf), "%Y-%m-%d %H:%M:%S", std::localtime(&now)); std::fprintf(stderr, "[monitor %s] %s\n", timeBuf, message.c_str()); std::fflush(stderr); } bool Monitor::isProcessAlive(pid_t pid) const { if (pid <= 0) { return false; } return kill(pid, 0) == 0 || errno == EPERM; } // 结束主进程及其整棵进程树。 // 主进程通常还会再 fork 子进程(例如启动器拉起的串流客户端), // 只杀直接子进程会留下孤儿进程继续占用显示设备和串流会话, // 因此子进程启动时独立成组,结束时按进程组终止。 namespace { void killTree(pid_t pid, int sig) { if (pid <= 0) { return; } if (kill(-pid, sig) != 0) { kill(pid, sig); } } } // namespace bool Monitor::isHung() const { long long ageMs = 0; if (!heartbeatAgeMs(config_.heartbeatFile, ageMs)) { // 没有心跳文件:主进程还没写过心跳 return false; } return ageMs > config_.heartbeatTimeoutMs; } int Monitor::run(const std::string& command) { if (command.empty()) { log("用法: monitor -- <主进程命令行>"); return 2; } g_monitor = this; signal(SIGTERM, onSignal); signal(SIGINT, onSignal); // 子进程退出时不发送 SIGPIPE,避免 Monitor 自身被杀 signal(SIGPIPE, SIG_IGN); log("看门狗启动,监控: " + command); pid_t child = -1; auto startTime = std::chrono::steady_clock::now(); while (!stopRequested_) { if (child <= 0) { // 启动主进程 child = fork(); if (child == 0) { // 子进程:恢复默认信号处理并 exec signal(SIGTERM, SIG_DFL); signal(SIGINT, SIG_DFL); signal(SIGPIPE, SIG_DFL); // 独立成组,便于结束整棵进程树 setpgid(0, 0); execl("/bin/sh", "sh", "-c", command.c_str(), nullptr); _exit(127); } if (child < 0) { log(std::string("fork 失败: ") + std::strerror(errno)); return 1; } // 父子都调用一次 setpgid,避免父子竞争导致进程组 ID 未及时生效 setpgid(child, child); startTime = std::chrono::steady_clock::now(); log("主进程已启动, pid=" + std::to_string(child)); } std::this_thread::sleep_for(std::chrono::milliseconds(config_.checkIntervalMs)); if (stopRequested_) { log("收到退出信号,终止主进程"); if (child > 0) { killTree(child, SIGTERM); // 等待退出,超时则强杀 for (int i = 0; i < 20 && isProcessAlive(child); ++i) { std::this_thread::sleep_for(std::chrono::milliseconds(200)); } if (isProcessAlive(child)) { killTree(child, SIGKILL); } waitpid(child, nullptr, 0); } log("看门狗退出"); return 0; } // 回收已退出的子进程 int status = 0; const pid_t reaped = waitpid(child, &status, WNOHANG); if (reaped == child) { const int exitCode = WIFEXITED(status) ? WEXITSTATUS(status) : -1; const int signalNum = WIFSIGNALED(status) ? WTERMSIG(status) : 0; log("主进程已退出, code=" + std::to_string(exitCode) + (signalNum ? (", signal=" + std::to_string(signalNum)) : "")); // shell 主进程退出时 Moonlight 等后代可能仍留在原进程组中。 // 重启前清理整个组,避免旧串流窗口与新实例并存。 killTree(child, SIGTERM); std::this_thread::sleep_for(std::chrono::milliseconds(300)); killTree(child, SIGKILL); if (++restartAttempts_ > config_.maxRestartAttempts) { log("连续重启超过 " + std::to_string(config_.maxRestartAttempts) + " 次,放弃重启(熔断)"); return 1; } log(std::to_string(config_.restartBackoffMs) + "ms 后重启"); std::this_thread::sleep_for(std::chrono::milliseconds(config_.restartBackoffMs)); child = -1; continue; } // 假死检测 if (isHung()) { const auto elapsed = std::chrono::duration_cast( std::chrono::steady_clock::now() - startTime) .count(); // 启动宽限期内不判定假死 if (elapsed > config_.startupGraceMs) { log("检测到主进程假死(心跳超时),执行 kill 并重启"); killTree(child, SIGKILL); waitpid(child, &status, 0); child = -1; if (++restartAttempts_ > config_.maxRestartAttempts) { log("连续重启超过 " + std::to_string(config_.maxRestartAttempts) + " 次,放弃重启(熔断)"); return 1; } std::this_thread::sleep_for(std::chrono::milliseconds(config_.restartBackoffMs)); continue; } } else { // 进程健康,重置重启计数 restartAttempts_ = 0; } } return 0; } } // namespace vdi