From 5b12c36da58aed67924ce8b18eec9860691d77b1 Mon Sep 17 00:00:00 2001 From: 2dust <31833384+2dust@users.noreply.github.com> Date: Wed, 1 Oct 2025 19:49:28 +0800 Subject: [PATCH] Optimize and improve, encapsulate ProcessService --- v2rayN/ServiceLib/Manager/CoreAdminManager.cs | 54 ++--- v2rayN/ServiceLib/Manager/CoreManager.cs | 112 ++++------ v2rayN/ServiceLib/Services/ProcessService.cs | 194 ++++++++++++++++++ .../ServiceLib/Services/SpeedtestService.cs | 22 +- 4 files changed, 255 insertions(+), 127 deletions(-) create mode 100644 v2rayN/ServiceLib/Services/ProcessService.cs diff --git a/v2rayN/ServiceLib/Manager/CoreAdminManager.cs b/v2rayN/ServiceLib/Manager/CoreAdminManager.cs index 90b47106..4143e78a 100644 --- a/v2rayN/ServiceLib/Manager/CoreAdminManager.cs +++ b/v2rayN/ServiceLib/Manager/CoreAdminManager.cs @@ -1,4 +1,3 @@ -using System.Diagnostics; using System.Text; using CliWrap; using CliWrap.Buffered; @@ -31,7 +30,7 @@ public class CoreAdminManager await _updateFunc?.Invoke(notify, msg); } - public async Task RunProcessAsLinuxSudo(string fileName, CoreInfo coreInfo, string configPath) + public async Task RunProcessAsLinuxSudo(string fileName, CoreInfo coreInfo, string configPath) { StringBuilder sb = new(); sb.AppendLine("#!/bin/bash"); @@ -39,50 +38,25 @@ public class CoreAdminManager sb.AppendLine($"sudo -S {cmdLine}"); var shFilePath = await FileManager.CreateLinuxShellFile("run_as_sudo.sh", sb.ToString(), true); - Process proc = new() - { - StartInfo = new() - { - FileName = shFilePath, - Arguments = "", - WorkingDirectory = Utils.GetBinConfigPath(), - UseShellExecute = false, - RedirectStandardInput = true, - RedirectStandardOutput = true, - RedirectStandardError = true, - CreateNoWindow = true, - StandardOutputEncoding = Encoding.UTF8, - StandardErrorEncoding = Encoding.UTF8, - } - }; + var procService = new ProcessService( + fileName: shFilePath, + arguments: "", + workingDirectory: Utils.GetBinConfigPath(), + displayLog: true, + redirectInput: true, + environmentVars: null, + updateFunc: _updateFunc + ); - void dataHandler(object sender, DataReceivedEventArgs e) - { - if (e.Data.IsNotEmpty()) - { - _ = UpdateFunc(false, e.Data + Environment.NewLine); - } - } + await procService.StartAsync(AppManager.Instance.LinuxSudoPwd); - proc.OutputDataReceived += dataHandler; - proc.ErrorDataReceived += dataHandler; - - proc.Start(); - proc.BeginOutputReadLine(); - proc.BeginErrorReadLine(); - - await Task.Delay(10); - await proc.StandardInput.WriteLineAsync(AppManager.Instance.LinuxSudoPwd); - - await Task.Delay(100); - if (proc is null or { HasExited: true }) + if (procService is null or { HasExited: true }) { throw new Exception(ResUI.FailedToRunCore); } + _linuxSudoPid = procService.Id; - _linuxSudoPid = proc.Id; - - return proc; + return procService; } public async Task KillProcessAsLinuxSudo() diff --git a/v2rayN/ServiceLib/Manager/CoreManager.cs b/v2rayN/ServiceLib/Manager/CoreManager.cs index 695508c2..04f34d75 100644 --- a/v2rayN/ServiceLib/Manager/CoreManager.cs +++ b/v2rayN/ServiceLib/Manager/CoreManager.cs @@ -1,6 +1,3 @@ -using System.Diagnostics; -using System.Text; - namespace ServiceLib.Manager; /// @@ -11,8 +8,8 @@ public class CoreManager private static readonly Lazy _instance = new(() => new()); public static CoreManager Instance => _instance.Value; private Config _config; - private Process? _process; - private Process? _processPre; + private ProcessService? _processService; + private ProcessService? _processPreService; private bool _linuxSudo = false; private Func? _updateFunc; private const string _tag = "CoreHandler"; @@ -89,13 +86,13 @@ public class CoreManager await CoreStart(node); await CoreStartPreService(node); - if (_process != null) + if (_processService != null) { await UpdateFunc(true, $"{node.GetSummary()}"); } } - public async Task LoadCoreConfigSpeedtest(List selecteds) + public async Task LoadCoreConfigSpeedtest(List selecteds) { var coreType = selecteds.Exists(t => t.ConfigType is EConfigType.Hysteria2 or EConfigType.TUIC or EConfigType.Anytls) ? ECoreType.sing_box : ECoreType.Xray; var fileName = string.Format(Global.CoreSpeedtestConfigFileName, Utils.GetGuid(false)); @@ -104,28 +101,22 @@ public class CoreManager await UpdateFunc(false, result.Msg); if (result.Success != true) { - return -1; + return null; } await UpdateFunc(false, string.Format(ResUI.StartService, DateTime.Now.ToString("yyyy/MM/dd HH:mm:ss"))); await UpdateFunc(false, configPath); var coreInfo = CoreInfoManager.Instance.GetCoreInfo(coreType); - var proc = await RunProcess(coreInfo, fileName, true, false); - if (proc is null) - { - return -1; - } - - return proc.Id; + return await RunProcess(coreInfo, fileName, true, false); } - public async Task LoadCoreConfigSpeedtest(ServerTestItem testItem) + public async Task LoadCoreConfigSpeedtest(ServerTestItem testItem) { var node = await AppManager.Instance.GetProfileItem(testItem.IndexId); if (node is null) { - return -1; + return null; } var fileName = string.Format(Global.CoreSpeedtestConfigFileName, Utils.GetGuid(false)); @@ -133,18 +124,12 @@ public class CoreManager var result = await CoreConfigHandler.GenerateClientSpeedtestConfig(_config, node, testItem, configPath); if (result.Success != true) { - return -1; + return null; } var coreType = AppManager.Instance.GetCoreType(node, node.ConfigType); var coreInfo = CoreInfoManager.Instance.GetCoreInfo(coreType); - var proc = await RunProcess(coreInfo, fileName, true, false); - if (proc is null) - { - return -1; - } - - return proc.Id; + return await RunProcess(coreInfo, fileName, true, false); } public async Task CoreStop() @@ -157,16 +142,18 @@ public class CoreManager _linuxSudo = false; } - if (_process != null) + if (_processService != null) { - await ProcUtils.ProcessKill(_process, Utils.IsWindows()); - _process = null; + await _processService.StopAsync(); + _processService.Dispose(); + _processService = null; } - if (_processPre != null) + if (_processPreService != null) { - await ProcUtils.ProcessKill(_processPre, Utils.IsWindows()); - _processPre = null; + await _processPreService.StopAsync(); + _processPreService.Dispose(); + _processPreService = null; } } catch (Exception ex) @@ -188,12 +175,12 @@ public class CoreManager { return; } - _process = proc; + _processService = proc; } private async Task CoreStartPreService(ProfileItem node) { - if (_process != null && !_process.HasExited) + if (_processService != null && !_processService.HasExited) { var coreType = AppManager.Instance.GetCoreType(node, node.ConfigType); var itemSocks = await ConfigHandler.GetPreSocksItem(_config, node, coreType); @@ -210,7 +197,7 @@ public class CoreManager { return; } - _processPre = proc; + _processPreService = proc; } } } @@ -225,7 +212,7 @@ public class CoreManager #region Process - private async Task RunProcess(CoreInfo? coreInfo, string configPath, bool displayLog, bool mayNeedSudo) + private async Task RunProcess(CoreInfo? coreInfo, string configPath, bool displayLog, bool mayNeedSudo) { var fileName = CoreInfoManager.Instance.GetCoreExecFile(coreInfo, out var msg); if (fileName.IsNullOrEmpty()) @@ -256,55 +243,34 @@ public class CoreManager } } - private async Task RunProcessNormal(string fileName, CoreInfo? coreInfo, string configPath, bool displayLog) + private async Task RunProcessNormal(string fileName, CoreInfo? coreInfo, string configPath, bool displayLog) { - Process proc = new() - { - StartInfo = new() - { - FileName = fileName, - Arguments = string.Format(coreInfo.Arguments, coreInfo.AbsolutePath ? Utils.GetBinConfigPath(configPath).AppendQuotes() : configPath), - WorkingDirectory = Utils.GetBinConfigPath(), - UseShellExecute = false, - RedirectStandardOutput = displayLog, - RedirectStandardError = displayLog, - CreateNoWindow = true, - StandardOutputEncoding = displayLog ? Encoding.UTF8 : null, - StandardErrorEncoding = displayLog ? Encoding.UTF8 : null, - } - }; + var environmentVars = new Dictionary(); foreach (var kv in coreInfo.Environment) { - proc.StartInfo.Environment[kv.Key] = string.Format(kv.Value, coreInfo.AbsolutePath ? Utils.GetBinConfigPath(configPath).AppendQuotes() : configPath); + environmentVars[kv.Key] = string.Format(kv.Value, coreInfo.AbsolutePath ? Utils.GetBinConfigPath(configPath).AppendQuotes() : configPath); } - if (displayLog) - { - void dataHandler(object sender, DataReceivedEventArgs e) - { - if (e.Data.IsNotEmpty()) - { - _ = UpdateFunc(false, e.Data + Environment.NewLine); - } - } - proc.OutputDataReceived += dataHandler; - proc.ErrorDataReceived += dataHandler; - } - proc.Start(); + var procService = new ProcessService( + fileName: fileName, + arguments: string.Format(coreInfo.Arguments, coreInfo.AbsolutePath ? Utils.GetBinConfigPath(configPath).AppendQuotes() : configPath), + workingDirectory: Utils.GetBinConfigPath(), + displayLog: displayLog, + redirectInput: false, + environmentVars: environmentVars, + updateFunc: _updateFunc + ); - if (displayLog) - { - proc.BeginOutputReadLine(); - proc.BeginErrorReadLine(); - } + await procService.StartAsync(); await Task.Delay(100); - AppManager.Instance.AddProcess(proc.Handle); - if (proc is null or { HasExited: true }) + AppManager.Instance.AddProcess(procService.Handle); + if (procService is null or { HasExited: true }) { throw new Exception(ResUI.FailedToRunCore); } - return proc; + + return procService; } #endregion Process diff --git a/v2rayN/ServiceLib/Services/ProcessService.cs b/v2rayN/ServiceLib/Services/ProcessService.cs new file mode 100644 index 00000000..6b964831 --- /dev/null +++ b/v2rayN/ServiceLib/Services/ProcessService.cs @@ -0,0 +1,194 @@ +using System.Diagnostics; +using System.Text; + +namespace ServiceLib.Services; + +public class ProcessService : IDisposable +{ + private readonly Process _process; + private readonly Func? _updateFunc; + private bool _isDisposed; + + public int Id => _process.Id; + public IntPtr Handle => _process.Handle; + public bool HasExited => _process.HasExited; + + public ProcessService( + string fileName, + string arguments, + string workingDirectory, + bool displayLog, + bool redirectInput, + Dictionary? environmentVars, + Func? updateFunc) + { + _updateFunc = updateFunc; + + _process = new Process + { + StartInfo = new ProcessStartInfo + { + FileName = fileName, + Arguments = arguments, + WorkingDirectory = workingDirectory, + UseShellExecute = false, + RedirectStandardInput = redirectInput, + RedirectStandardOutput = displayLog, + RedirectStandardError = displayLog, + CreateNoWindow = true, + StandardOutputEncoding = displayLog ? Encoding.UTF8 : null, + StandardErrorEncoding = displayLog ? Encoding.UTF8 : null, + }, + EnableRaisingEvents = true + }; + + if (environmentVars != null) + { + foreach (var kv in environmentVars) + { + _process.StartInfo.Environment[kv.Key] = kv.Value; + } + } + + if (displayLog) + { + RegisterEventHandlers(); + } + } + + public async Task StartAsync(string pwd = null) + { + _process.Start(); + + if (_process.StartInfo.RedirectStandardOutput) + { + _process.BeginOutputReadLine(); + _process.BeginErrorReadLine(); + } + + if (_process.StartInfo.RedirectStandardInput) + { + await Task.Delay(10); + await _process.StandardInput.WriteLineAsync(pwd); + } + } + + public async Task StopAsync() + { + if (_process.HasExited) + { + return; + } + + try + { + if (_process.StartInfo.RedirectStandardOutput) + { + try + { + _process.CancelOutputRead(); + } + catch + { + throw; + } + try + { + _process.CancelErrorRead(); + } + catch + { + throw; + } + } + + try + { + if (Utils.IsNonWindows()) + { + _process.Kill(true); + } + } + catch + { + throw; + } + + try + { + _process.Kill(); + } + catch + { + throw; + } + + await Task.Delay(100); + } + catch (Exception ex) + { + await _updateFunc?.Invoke(true, ex.Message); + } + } + + private void RegisterEventHandlers() + { + void dataHandler(object sender, DataReceivedEventArgs e) + { + if (e.Data.IsNotEmpty()) + { + _ = _updateFunc?.Invoke(false, e.Data + Environment.NewLine); + } + } + + _process.OutputDataReceived += dataHandler; + _process.ErrorDataReceived += dataHandler; + + _process.Exited += (s, e) => + { + try + { + _process.OutputDataReceived -= dataHandler; + _process.ErrorDataReceived -= dataHandler; + } + catch + { + } + }; + } + + public void Dispose() + { + if (_isDisposed) + { + return; + } + + try + { + if (!_process.HasExited) + { + try + { + _process.CancelOutputRead(); + } + catch { } + try + { + _process.CancelErrorRead(); + } + catch { } + + _process.Kill(); + } + + _process.Dispose(); + } + catch (Exception ex) + { + _updateFunc?.Invoke(true, ex.Message); + } + + _isDisposed = true; + } +} diff --git a/v2rayN/ServiceLib/Services/SpeedtestService.cs b/v2rayN/ServiceLib/Services/SpeedtestService.cs index 5266f4d0..1fff0dbe 100644 --- a/v2rayN/ServiceLib/Services/SpeedtestService.cs +++ b/v2rayN/ServiceLib/Services/SpeedtestService.cs @@ -182,11 +182,11 @@ public class SpeedtestService(Config config, Func updateF private async Task RunRealPingAsync(List selecteds, string exitLoopKey) { - var pid = -1; + ProcessService processService = null; try { - pid = await CoreManager.Instance.LoadCoreConfigSpeedtest(selecteds); - if (pid < 0) + processService = await CoreManager.Instance.LoadCoreConfigSpeedtest(selecteds); + if (processService is null) { return false; } @@ -216,10 +216,7 @@ public class SpeedtestService(Config config, Func updateF } finally { - if (pid > 0) - { - await ProcUtils.ProcessKill(pid); - } + await processService?.StopAsync(); } return true; } @@ -244,11 +241,11 @@ public class SpeedtestService(Config config, Func updateF tasks.Add(Task.Run(async () => { - var pid = -1; + ProcessService processService = null; try { - pid = await CoreManager.Instance.LoadCoreConfigSpeedtest(it); - if (pid < 0) + processService = await CoreManager.Instance.LoadCoreConfigSpeedtest(it); + if (processService is null) { await UpdateFunc(it.IndexId, "", ResUI.FailedToRunCore); } @@ -275,10 +272,7 @@ public class SpeedtestService(Config config, Func updateF } finally { - if (pid > 0) - { - await ProcUtils.ProcessKill(pid); - } + await processService?.StopAsync(); concurrencySemaphore.Release(); } }));