Files

258 lines
8.8 KiB
C#
Raw Permalink 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.
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;
}
}
}
}