258 lines
8.8 KiB
C#
258 lines
8.8 KiB
C#
using System;
|
||
using System.Diagnostics;
|
||
using System.IO;
|
||
using System.Threading;
|
||
using XP.Common.Logging.Interfaces;
|
||
using XP.Hardware.RaySource.Config;
|
||
|
||
namespace XP.Hardware.RaySource.Implementations
|
||
{
|
||
/// <summary>
|
||
/// Host 进程生命周期管理器
|
||
/// 负责启动、监控和关闭 .NET Framework 4.8 Host 子进程
|
||
/// </summary>
|
||
public class CometHostManager : IDisposable
|
||
{
|
||
private readonly ILoggerService _logger;
|
||
private readonly RaySourceConfig _config;
|
||
private Process _hostProcess;
|
||
private bool _isDisposed;
|
||
|
||
/// <summary>
|
||
/// Host 可执行文件名
|
||
/// </summary>
|
||
private const string HostExeName = "XP.Hardware.RaySource.Comet.Host.exe";
|
||
|
||
/// <summary>
|
||
/// 命令管道名称(客户端写 → Host 读)
|
||
/// </summary>
|
||
private const string CmdPipeName = "XP.Hardware.RaySource.Comet.Cmd";
|
||
|
||
/// <summary>
|
||
/// 响应管道名称(Host 写 → 客户端读)
|
||
/// </summary>
|
||
private const string RspPipeName = "XP.Hardware.RaySource.Comet.Rsp";
|
||
|
||
/// <summary>
|
||
/// 等待管道就绪的超时时间(毫秒)
|
||
/// </summary>
|
||
private const int PipeReadyTimeoutMs = 10000;
|
||
|
||
/// <summary>
|
||
/// 等待 Host 进程退出的超时时间(毫秒)
|
||
/// </summary>
|
||
private const int ShutdownTimeoutMs = 5000;
|
||
|
||
/// <summary>
|
||
/// 构造函数
|
||
/// </summary>
|
||
/// <param name="loggerService">日志服务</param>
|
||
/// <param name="config">射线源配置</param>
|
||
public CometHostManager(ILoggerService loggerService, RaySourceConfig config)
|
||
{
|
||
_logger = loggerService?.ForModule<CometHostManager>() ?? throw new ArgumentNullException(nameof(loggerService));
|
||
_config = config ?? throw new ArgumentNullException(nameof(config));
|
||
}
|
||
|
||
/// <summary>
|
||
/// Host 进程是否正在运行
|
||
/// </summary>
|
||
public bool IsRunning => _hostProcess != null && !_hostProcess.HasExited;
|
||
|
||
/// <summary>
|
||
/// 确保 Host 进程正在运行
|
||
/// 优先检查是否已有同路径的 Host 进程在运行,有则直接关联;否则启动新进程
|
||
/// </summary>
|
||
public void EnsureRunning()
|
||
{
|
||
if (IsRunning)
|
||
{
|
||
_logger.Debug("Host 进程已在运行,PID={Pid}", _hostProcess.Id);
|
||
return;
|
||
}
|
||
|
||
var hostExePath = GetHostExePath();
|
||
|
||
// 检查是否已有 Host 进程在运行(例如上次异常退出后残留的进程),杀掉后重新启动
|
||
var hostProcessName = Path.GetFileNameWithoutExtension(HostExeName);
|
||
var existingProcesses = Process.GetProcessesByName(hostProcessName);
|
||
foreach (var proc in existingProcesses)
|
||
{
|
||
try
|
||
{
|
||
_logger.Info("发现残留的 Host 进程,正在终止,PID={Pid}", proc.Id);
|
||
proc.Kill();
|
||
proc.WaitForExit(1500);
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
_logger.Warn("终止残留 Host 进程失败,PID={Pid},{Message}", proc.Id, ex.Message);
|
||
}
|
||
}
|
||
|
||
_logger.Info("启动 Host 进程:{Path}", hostExePath);
|
||
|
||
if (!File.Exists(hostExePath))
|
||
{
|
||
throw new FileNotFoundException($"Host 可执行文件未找到:{hostExePath}", hostExePath);
|
||
}
|
||
|
||
var startInfo = new ProcessStartInfo
|
||
{
|
||
FileName = hostExePath,
|
||
UseShellExecute = false,
|
||
CreateNoWindow = true,
|
||
RedirectStandardError = true
|
||
};
|
||
|
||
_hostProcess = new Process { StartInfo = startInfo, EnableRaisingEvents = true };
|
||
_hostProcess.Exited += OnHostProcessExited;
|
||
_hostProcess.ErrorDataReceived += OnHostErrorDataReceived;
|
||
_hostProcess.Start();
|
||
_hostProcess.BeginErrorReadLine();
|
||
|
||
_logger.Info("Host 进程已启动,PID={Pid}", _hostProcess.Id);
|
||
|
||
// 等待 NamedPipe 就绪
|
||
WaitForPipeReady();
|
||
}
|
||
|
||
/// <summary>
|
||
/// 安全关闭 Host 进程
|
||
/// 等待进程退出,超时则强制终止(DisconnectCommand 已由 CometIpcClient 发送)
|
||
/// </summary>
|
||
public void Shutdown()
|
||
{
|
||
if (!IsRunning)
|
||
{
|
||
_logger.Debug("Host 进程未运行,无需关闭");
|
||
return;
|
||
}
|
||
|
||
_logger.Info("正在关闭 Host 进程,PID={Pid}", _hostProcess.Id);
|
||
|
||
try
|
||
{
|
||
// 等待进程退出(DisconnectCommand 已由 CometIpcClient 发送)
|
||
if (!_hostProcess.WaitForExit(ShutdownTimeoutMs))
|
||
{
|
||
_logger.Warn("Host 进程未在超时时间内退出,强制终止");
|
||
_hostProcess.Kill();
|
||
_hostProcess.WaitForExit(1000);
|
||
}
|
||
|
||
_logger.Info("Host 进程已关闭");
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
_logger.Warn("关闭 Host 进程时发生异常:{Message}", ex.Message);
|
||
}
|
||
finally
|
||
{
|
||
CleanupProcess();
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// 获取 Host 可执行文件路径
|
||
/// 优先使用配置的路径,为空时默认在主程序输出目录下查找
|
||
/// </summary>
|
||
private string GetHostExePath()
|
||
{
|
||
// 优先使用配置的路径
|
||
if (!string.IsNullOrWhiteSpace(_config.HostExePath))
|
||
{
|
||
return _config.HostExePath;
|
||
}
|
||
|
||
// 默认在主程序输出目录的 Host 子目录下查找
|
||
var baseDir = AppDomain.CurrentDomain.BaseDirectory;
|
||
return Path.Combine(baseDir, "Host", HostExeName);
|
||
}
|
||
|
||
/// <summary>
|
||
/// 等待 NamedPipe 就绪
|
||
/// 通过短暂延迟和进程存活检查确认 Host 进程已启动并创建了管道
|
||
/// 使用临时 NamedPipeClientStream 尝试连接来检测管道是否就绪
|
||
/// </summary>
|
||
private void WaitForPipeReady()
|
||
{
|
||
var startTime = DateTime.UtcNow;
|
||
while ((DateTime.UtcNow - startTime).TotalMilliseconds < PipeReadyTimeoutMs)
|
||
{
|
||
if (_hostProcess.HasExited)
|
||
{
|
||
throw new InvalidOperationException($"Host 进程启动后立即退出,退出码={_hostProcess.ExitCode}");
|
||
}
|
||
|
||
// 短暂等待,让 Host 进程有时间创建管道
|
||
Thread.Sleep(200);
|
||
|
||
// 进程仍在运行,认为管道即将就绪或已就绪
|
||
if (!_hostProcess.HasExited)
|
||
{
|
||
_logger.Info("Host 进程正在运行,管道应已就绪");
|
||
return;
|
||
}
|
||
}
|
||
|
||
// 超时但进程仍在运行,假设管道已就绪
|
||
if (!_hostProcess.HasExited)
|
||
{
|
||
_logger.Warn("等待管道就绪超时,但 Host 进程仍在运行,继续");
|
||
return;
|
||
}
|
||
|
||
throw new TimeoutException($"等待 Host 进程 NamedPipe 就绪超时({PipeReadyTimeoutMs}ms)");
|
||
}
|
||
|
||
/// <summary>
|
||
/// Host 进程退出事件处理
|
||
/// </summary>
|
||
private void OnHostProcessExited(object sender, EventArgs e)
|
||
{
|
||
if (_hostProcess != null)
|
||
{
|
||
_logger.Warn("Host 进程已退出,退出码={ExitCode}", _hostProcess.ExitCode);
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// Host 进程 stderr 输出事件处理,转发到主进程日志
|
||
/// </summary>
|
||
private void OnHostErrorDataReceived(object sender, DataReceivedEventArgs e)
|
||
{
|
||
if (!string.IsNullOrEmpty(e.Data))
|
||
{
|
||
_logger.Debug("[Host stderr] {Message}", e.Data);
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// 清理进程资源
|
||
/// </summary>
|
||
private void CleanupProcess()
|
||
{
|
||
if (_hostProcess != null)
|
||
{
|
||
_hostProcess.Exited -= OnHostProcessExited;
|
||
_hostProcess.ErrorDataReceived -= OnHostErrorDataReceived;
|
||
_hostProcess.Dispose();
|
||
_hostProcess = null;
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// 释放资源
|
||
/// </summary>
|
||
public void Dispose()
|
||
{
|
||
if (!_isDisposed)
|
||
{
|
||
Shutdown();
|
||
_isDisposed = true;
|
||
}
|
||
}
|
||
}
|
||
}
|