2026-01-16 14:30:42 +08:00
|
|
|
|
using Ayay.SerilogLogs;
|
2026-01-05 14:54:06 +08:00
|
|
|
|
using Microsoft.Extensions.Hosting;
|
2026-01-16 14:30:42 +08:00
|
|
|
|
using Serilog;
|
2026-01-05 14:54:06 +08:00
|
|
|
|
using SHH.CameraSdk;
|
2026-01-16 14:30:42 +08:00
|
|
|
|
using System.Diagnostics;
|
2026-01-05 14:54:06 +08:00
|
|
|
|
|
|
|
|
|
|
namespace SHH.CameraService;
|
|
|
|
|
|
|
2026-01-16 14:30:42 +08:00
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// 父进程守护服务 (BackgroundService)
|
|
|
|
|
|
/// <para>核心逻辑:定期检查启动本服务的父进程是否存活,若父进程退出(如 UI 崩溃),则触发本服务自动退出,避免孤儿进程占用相机硬件资源。</para>
|
|
|
|
|
|
/// </summary>
|
2026-01-05 14:54:06 +08:00
|
|
|
|
public class ParentProcessSentinel : BackgroundService
|
|
|
|
|
|
{
|
|
|
|
|
|
private readonly ServiceConfig _config;
|
|
|
|
|
|
private readonly IHostApplicationLifetime _lifetime;
|
2026-01-16 14:30:42 +08:00
|
|
|
|
private static ILogger _sysLog = Log.ForContext("SourceContext", LogModules.Core);
|
2026-01-05 14:54:06 +08:00
|
|
|
|
|
2026-01-16 14:30:42 +08:00
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// 使用统一的结构化日志记录器,SourceContext 设置为 Core 模块
|
|
|
|
|
|
/// </summary>
|
2026-01-05 14:54:06 +08:00
|
|
|
|
public ParentProcessSentinel(
|
|
|
|
|
|
ServiceConfig config,
|
2026-01-16 14:30:42 +08:00
|
|
|
|
IHostApplicationLifetime lifetime)
|
2026-01-05 14:54:06 +08:00
|
|
|
|
{
|
|
|
|
|
|
_config = config;
|
|
|
|
|
|
_lifetime = lifetime;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-01-16 14:30:42 +08:00
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// 执行后台守护逻辑
|
|
|
|
|
|
/// </summary>
|
2026-01-05 14:54:06 +08:00
|
|
|
|
protected override async Task ExecuteAsync(CancellationToken stoppingToken)
|
|
|
|
|
|
{
|
|
|
|
|
|
int pid = _config.ParentPid;
|
2026-01-16 14:30:42 +08:00
|
|
|
|
// 1. 验证 PID 合法性。如果 PID 为 0 或负数,可能是手动启动调试模式,不执行守护逻辑
|
2026-01-05 14:54:06 +08:00
|
|
|
|
if (pid <= 0)
|
|
|
|
|
|
{
|
2026-01-16 14:30:42 +08:00
|
|
|
|
_sysLog.Warning("[Sentinel] 未指定有效的父进程 PID ({ParentPid}),守护模式已禁用,服务将持续运行.", pid);
|
2026-01-05 14:54:06 +08:00
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-01-16 14:30:42 +08:00
|
|
|
|
_sysLog.Information("[Sentinel] 父进程守护已启动,正在监控目标 PID: {ParentPid}", pid);
|
2026-01-05 14:54:06 +08:00
|
|
|
|
|
|
|
|
|
|
while (!stoppingToken.IsCancellationRequested)
|
|
|
|
|
|
{
|
|
|
|
|
|
if (!IsParentRunning(pid))
|
|
|
|
|
|
{
|
2026-01-16 14:30:42 +08:00
|
|
|
|
_sysLog.Warning("[Sentinel] ### ALERT ### 检测到父进程 (PID:{ParentPid}) 已退出!正在下发系统终止信号...", pid);
|
2026-01-05 14:54:06 +08:00
|
|
|
|
|
|
|
|
|
|
// 触发程序优雅退出
|
|
|
|
|
|
_lifetime.StopApplication();
|
|
|
|
|
|
|
|
|
|
|
|
// 强制跳出循环
|
|
|
|
|
|
break;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 每 2 秒检查一次,避免 CPU 浪费
|
|
|
|
|
|
await Task.Delay(2000, stoppingToken);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-01-16 14:30:42 +08:00
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// 核心状态判定:通过 PID 获取进程快照并检查存活状态
|
|
|
|
|
|
/// </summary>
|
|
|
|
|
|
/// <param name="pid">父进程 ID</param>
|
|
|
|
|
|
/// <returns>存活返回 True,已消亡返回 False</returns>
|
2026-01-05 14:54:06 +08:00
|
|
|
|
private bool IsParentRunning(int pid)
|
|
|
|
|
|
{
|
|
|
|
|
|
try
|
|
|
|
|
|
{
|
|
|
|
|
|
// 尝试获取进程对象
|
|
|
|
|
|
var process = Process.GetProcessById(pid);
|
|
|
|
|
|
|
|
|
|
|
|
// 检查是否已退出
|
|
|
|
|
|
if (process.HasExited) return false;
|
|
|
|
|
|
|
|
|
|
|
|
return true;
|
|
|
|
|
|
}
|
|
|
|
|
|
catch (ArgumentException)
|
|
|
|
|
|
{
|
|
|
|
|
|
// GetProcessById 在找不到 PID 时会抛出 ArgumentException
|
|
|
|
|
|
// 说明进程已经不存在了
|
|
|
|
|
|
return false;
|
|
|
|
|
|
}
|
|
|
|
|
|
catch (Exception ex)
|
|
|
|
|
|
{
|
2026-01-16 14:30:42 +08:00
|
|
|
|
_sysLog.Debug("[Sentinel] 无法定位 PID 为 {ParentPid} 的进程,判定为已退出.", pid);
|
2026-01-05 14:54:06 +08:00
|
|
|
|
return true; // 发生未知错误时,保守起见认为它还活着
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|