Refactor Guard.cs

This commit is contained in:
ChsBuffer
2021-06-26 06:14:08 +08:00
parent e7d04e36ac
commit 62dc9166ce
10 changed files with 195 additions and 332 deletions

View File

@@ -1,6 +1,3 @@
using Netch.Models;
using Netch.Utils;
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Diagnostics;
@@ -9,83 +6,157 @@ using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using Netch.Models;
using Netch.Utils;
using Serilog;
using Timer = System.Timers.Timer;
namespace Netch.Controllers
{
public abstract class Guard
{
private readonly Timer _flushFileStreamTimer = new(300) { AutoReset = true };
private FileStream? _logFileStream;
private StreamWriter? _logStreamWriter;
private bool _redirectToFile = true;
/// <summary>
/// 日志文件(重定向输出文件)
/// </summary>
/// <param name="mainFile">application path relative of Netch\bin</param>
/// <param name="redirectOutput"></param>
/// <param name="encoding">application output encode</param>
protected Guard(string mainFile, bool redirectOutput = true, Encoding? encoding = null)
{
RedirectOutput = redirectOutput;
var fileName = Path.GetFullPath($"bin\\{mainFile}");
if (!File.Exists(fileName))
throw new MessageException(i18N.Translate($"bin\\{mainFile} file not found!"));
Instance = new Process
{
StartInfo =
{
FileName = fileName,
WorkingDirectory = $"{Global.NetchDir}\\bin",
CreateNoWindow = true,
UseShellExecute = !RedirectOutput,
RedirectStandardOutput = RedirectOutput,
StandardOutputEncoding = RedirectOutput ? encoding : null,
RedirectStandardError = RedirectOutput,
StandardErrorEncoding = RedirectOutput ? encoding : null,
WindowStyle = ProcessWindowStyle.Hidden
}
};
}
protected string LogPath => Path.Combine(Global.NetchDir, $"logging\\{Name}.log");
/// <summary>
/// 成功启动关键词
/// </summary>
protected virtual IEnumerable<string> StartedKeywords { get; set; } = new List<string>();
protected virtual IEnumerable<string> StartedKeywords { get; } = new List<string>();
/// <summary>
/// 启动失败关键词
/// </summary>
protected virtual IEnumerable<string> StoppedKeywords { get; set; } = new List<string>();
protected virtual IEnumerable<string> FailedKeywords { get; } = new List<string>();
public abstract string Name { get; }
/// <summary>
/// 主程序名
/// </summary>
public abstract string MainFile { get; protected set; }
private State State { get; set; } = State.Waiting;
protected State State { get; set; } = State.Waiting;
private bool RedirectOutput { get; }
/// <summary>
/// 进程是否可以重定向输出
/// </summary>
protected bool RedirectStd { get; set; } = true;
public Process Instance { get; }
protected bool RedirectToFile
~Guard()
{
get => RedirectStd && _redirectToFile;
set => _redirectToFile = value;
_logFileStream?.Dispose();
_logStreamWriter?.Dispose();
Instance.Dispose();
}
/// <summary>
/// 进程实例
/// </summary>
public Process? Instance { get; private set; }
/// <summary>
/// 程序输出的编码,
/// </summary>
protected virtual Encoding? InstanceOutputEncoding { get; } = null;
public abstract void Stop();
/// <summary>
/// 停止进程
/// </summary>
protected void StopInstance()
protected void StartGuard(string argument, ProcessPriorityClass priority = ProcessPriorityClass.Normal)
{
State = State.Starting;
_logFileStream = File.Open(LogPath, FileMode.Create, FileAccess.ReadWrite, FileShare.Read);
_logStreamWriter = new StreamWriter(_logFileStream) { AutoFlush = true };
Instance.StartInfo.Arguments = argument;
Instance.Start();
if (priority != ProcessPriorityClass.Normal)
Instance.PriorityClass = priority;
if (RedirectOutput)
{
Task.Run(() => ReadOutput(Instance.StandardOutput));
Task.Run(() => ReadOutput(Instance.StandardError));
if (!StartedKeywords.Any())
{
// Skip, No started keyword
State = State.Started;
return;
}
// wait ReadOutput change State
for (var i = 0; i < 1000; i++)
{
Thread.Sleep(10);
switch (State)
{
case State.Started:
OnStarted();
return;
case State.Stopped:
StopGuard();
OnStartFailed();
throw new MessageException($"{Name} 控制器启动失败");
}
}
StopGuard();
throw new MessageException($"{Name} 控制器启动超时");
}
}
private void ReadOutput(TextReader reader)
{
string? line;
while ((line = reader.ReadLine()) != null)
{
_logStreamWriter!.WriteLine(line);
OnReadNewLine(line);
if (State == State.Starting)
{
if (StartedKeywords.Any(s => line.Contains(s)))
State = State.Started;
else if (FailedKeywords.Any(s => line.Contains(s)))
{
OnStartFailed();
State = State.Stopped;
}
}
}
State = State.Stopped;
}
public virtual void Stop()
{
StopGuard();
}
protected void StopGuard()
{
_logStreamWriter?.Close();
_logFileStream?.Close();
try
{
if (Instance == null || Instance.HasExited)
return;
Instance.Kill();
Instance.WaitForExit();
if (Instance is { HasExited: false })
{
Instance.Kill();
Instance.WaitForExit();
}
}
catch (Win32Exception e)
{
Log.Error(e, "停止 {MainFile} 异常", MainFile);
Log.Error(e, "停止 {Name} 异常", Instance.ProcessName);
}
catch
{
@@ -93,191 +164,17 @@ namespace Netch.Controllers
}
}
/// <summary>
/// 仅初始化 <see cref="Instance" />,不设定事件处理方法
/// </summary>
/// <param name="argument"></param>
protected virtual void InitInstance(string argument)
protected virtual void OnStarted()
{
Instance = new Process
{
StartInfo =
{
FileName = Path.GetFullPath($"bin\\{MainFile}"),
WorkingDirectory = $"{Global.NetchDir}\\bin",
Arguments = argument,
CreateNoWindow = true,
UseShellExecute = !RedirectStd,
RedirectStandardOutput = RedirectStd,
StandardOutputEncoding = RedirectStd ? InstanceOutputEncoding : null,
RedirectStandardError = RedirectStd,
StandardErrorEncoding = RedirectStd ? InstanceOutputEncoding : null,
WindowStyle = ProcessWindowStyle.Hidden
}
};
if (!File.Exists(Instance.StartInfo.FileName))
throw new MessageException(i18N.Translate($"bin\\{MainFile} file not found!"));
}
/// <summary>
/// 默认行为启动主程序
/// </summary>
/// <param name="argument">主程序启动参数</param>
/// <param name="priority">进程优先级</param>
/// <returns>是否成功启动</returns>
protected void StartInstanceAuto(string argument, ProcessPriorityClass priority = ProcessPriorityClass.Normal)
{
State = State.Starting;
// 初始化程序
InitInstance(argument);
if (RedirectToFile)
OpenLogFile();
// 启动程序
Instance!.Start();
if (priority != ProcessPriorityClass.Normal)
Instance.PriorityClass = priority;
if (RedirectStd)
{
Task.Run(() => ReadOutput(Instance.StandardOutput));
Task.Run(() => ReadOutput(Instance.StandardError));
if (!StartedKeywords.Any())
{
State = State.Started;
return;
}
}
else
{
return;
}
// 等待启动
for (var i = 0; i < 1000; i++)
{
Thread.Sleep(10);
switch (State)
{
case State.Started:
Task.Run(OnKeywordStarted);
return;
case State.Stopped:
Stop();
CloseLogFile();
OnKeywordStopped();
throw new MessageException($"{Name} 控制器启动失败");
}
}
Stop();
OnKeywordTimeout();
throw new MessageException($"{Name} 控制器启动超时");
}
#region FileStream
private void OpenLogFile()
{
if (!RedirectToFile)
return;
_logFileStream = File.Open(LogPath, FileMode.Create, FileAccess.ReadWrite, FileShare.Read);
_logStreamWriter = new StreamWriter(_logFileStream);
_flushFileStreamTimer.Elapsed += FlushFileStreamTimerEvent;
_flushFileStreamTimer.Enabled = true;
}
private void WriteLog(string line)
{
if (!RedirectToFile)
return;
_logStreamWriter!.WriteLine(line);
}
private readonly object _logStreamLock = new();
private void CloseLogFile()
{
if (!RedirectToFile)
return;
lock (_logStreamLock)
{
if (_logFileStream == null)
return;
_flushFileStreamTimer.Enabled = false;
_logStreamWriter?.Close();
_logFileStream?.Close();
_logStreamWriter = _logStreamWriter = null;
}
}
#endregion
#region virtual
protected virtual void OnReadNewLine(string line)
{
}
protected virtual void OnKeywordStarted()
{
}
protected virtual void OnKeywordStopped()
protected virtual void OnStartFailed()
{
Utils.Utils.Open(LogPath);
}
protected virtual void OnKeywordTimeout()
{
}
#endregion
protected void ReadOutput(TextReader reader)
{
string? line;
while ((line = reader.ReadLine()) != null)
{
WriteLog(line);
OnReadNewLine(line);
// State == State.Started if !StartedKeywords.Any()
if (State == State.Starting)
{
if (StartedKeywords.Any(s => line.Contains(s)))
State = State.Started;
else if (StoppedKeywords.Any(s => line.Contains(s)))
State = State.Stopped;
}
}
CloseLogFile();
State = State.Stopped;
}
/// <summary>
/// 计时器存储日志
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void FlushFileStreamTimerEvent(object sender, EventArgs e)
{
try
{
_logStreamWriter!.Flush();
}
catch (Exception ex)
{
Log.Warning(ex, "写入 {Name} 日志异常", Name);
}
}
}
}