vdi-cloud/terminal/kiosk-shell/src/monitor.cpp

204 lines
6.3 KiB
C++
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

#include "monitor.h"
#include <signal.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <unistd.h>
#include <cerrno>
#include <chrono>
#include <cstdio>
#include <cstring>
#include <ctime>
#include <string>
#include <thread>
#include <vector>
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<std::chrono::milliseconds>(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)) : ""));
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::milliseconds>(
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