Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 12 additions & 1 deletion MSLX.Daemon/Program.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
using Microsoft.AspNetCore.HttpOverrides;
using Microsoft.AspNetCore.HttpOverrides;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.FileProviders;
using MSLX.Daemon.Hubs;
Expand All @@ -9,9 +9,20 @@
using MSLX.Daemon.Utils.BackgroundTasks;
using MSLX.Daemon.Utils.ConfigUtils;
using System.Reflection;
using System.Runtime.InteropServices;

System.Text.Encoding.RegisterProvider(System.Text.CodePagesEncodingProvider.Instance);

// 控制台关闭事件处理
if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
{
WindowsConsoleHandler.Initialize();
}
else if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux) || RuntimeInformation.IsOSPlatform(OSPlatform.OSX))
{
UnixConsoleHandler.Initialize();
}

var builder = WebApplication.CreateBuilder(args);

// 日志级别配置
Expand Down
59 changes: 54 additions & 5 deletions MSLX.Daemon/Services/FrpProcessService.cs
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
using System.Collections.Concurrent;
using System.Collections.Concurrent;
using System.Diagnostics;
using System.IO.Compression;
using System.Formats.Tar;
using System.Runtime.InteropServices;
using System.Text;
using Downloader;
using Microsoft.AspNetCore.SignalR;
Expand Down Expand Up @@ -30,6 +31,7 @@ public class FrpContext

private readonly ConcurrentDictionary<int, FrpContext> _activeProcesses = new();
private const int MaxLogLines = 200;
private int _isStopping = 0; // 防重复停止标志 (0=未停止, 1=正在停止)

private readonly string _frpcExecutablePath;
private readonly string _toolsDir;
Expand All @@ -49,6 +51,16 @@ public FrpProcessService(ILogger<FrpProcessService> logger, IHubContext<FrpConso
// 生命周期事件(启动/退出)
_appLifetime.ApplicationStopping.Register(StopAllFrp);
_appLifetime.ApplicationStarted.Register(OnAppStarted);

// 注册控制台关闭事件处理
if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
{
WindowsConsoleHandler.RegisterCleanupAction(StopAllFrp);
}
else if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux) || RuntimeInformation.IsOSPlatform(OSPlatform.OSX))
{
UnixConsoleHandler.RegisterCleanupAction(StopAllFrp);
}
}

public bool IsFrpRunning(int id)
Expand Down Expand Up @@ -397,17 +409,54 @@ public List<string> GetLogs(int id)

private void StopAllFrp()
{
if (_activeProcesses.IsEmpty) return;
// 防重复执行检查
if (Interlocked.CompareExchange(ref _isStopping, 1, 0) != 0)
{
_logger.LogDebug("StopAllFrp 已在执行中,跳过重复调用");
return;
}

if (_activeProcesses.IsEmpty)
{
_logger.LogInformation("没有正在运行的 FRP 隧道需要停止");
return;
}

_logger.LogInformation($"正在停止 {_activeProcesses.Count} 个 FRP 隧道...");
int stoppedCount = 0;
int failedCount = 0;

foreach (var kvp in _activeProcesses)
{
int id = kvp.Key;
var context = kvp.Value;
try
{
var context = kvp.Value;
if (context.Process != null && !context.Process.HasExited) context.Process.Kill(true);
if (context.Process != null && !context.Process.HasExited)
{
context.Process.Kill(true);
context.Process.WaitForExit(2000); // 等待最多2秒
stoppedCount++;
_logger.LogInformation($"FRP 隧道 [{id}] 已停止 (PID: {context.Process.Id})");
}
}
catch (InvalidOperationException ex)
{
// 进程已经退出
_logger.LogDebug($"FRP 隧道 [{id}] 进程已经退出");
}
catch (Exception ex)
{
failedCount++;
_logger.LogError(ex, $"停止 FRP 隧道 [{id}] 时发生错误");
}
catch { }
}

_activeProcesses.Clear();
_logger.LogInformation($"FRP 隧道停止完成: 成功 {stoppedCount} 个, 失败 {failedCount} 个");

// 重置停止标志,允许下次调用
Interlocked.Exchange(ref _isStopping, 0);
}

private void OnAppStarted()
Expand Down
132 changes: 132 additions & 0 deletions MSLX.Daemon/Utils/UnixConsoleHandler.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,132 @@
using System.Runtime.InteropServices;
using System.Diagnostics;

namespace MSLX.Daemon.Utils;

/// <summary>
/// Unix/Linux/macOS 控制台关闭事件处理器
/// 用于在进程收到终止信号时执行清理操作
/// </summary>
public static class UnixConsoleHandler
{
private static readonly ILogger Logger;
private static readonly List<Action> CleanupActions = new();
private static readonly object LockObj = new();
private static bool _initialized = false;

// 保持委托引用,防止被 GC 回收
private static SignalHandler? _signalHandler;
private static GCHandle _signalHandlerHandle;

// POSIX 信号常量
private const int SIGINT = 2; // Ctrl+C
private const int SIGTERM = 15; // 终止信号 (kill 默认)
private const int SIGHUP = 1; // 挂起信号 (终端关闭)

// 委托类型
private delegate void SignalHandler(int signum);

[DllImport("libc", SetLastError = true)]
private static extern IntPtr signal(int signum, SignalHandler handler);

[DllImport("libc", SetLastError = true)]
private static extern int getpid();

static UnixConsoleHandler()
{
var loggerFactory = LoggerFactory.Create(builder => builder.AddConsole());
Logger = loggerFactory.CreateLogger("UnixConsoleHandler");
}

/// <summary>
/// 初始化 Unix 信号处理
/// </summary>
public static void Initialize()
{
if (_initialized) return;

lock (LockObj)
{
if (_initialized) return;

// 创建委托实例并保持引用
_signalHandler = OnSignal;
_signalHandlerHandle = GCHandle.Alloc(_signalHandler, GCHandleType.Normal);

// 注册信号处理器
signal(SIGINT, _signalHandler);
signal(SIGTERM, _signalHandler);
signal(SIGHUP, _signalHandler);

_initialized = true;

Logger.LogDebug("Unix 信号处理器已初始化");
}
}

/// <summary>
/// 注册清理操作
/// </summary>
public static void RegisterCleanupAction(Action action)
{
lock (LockObj)
{
CleanupActions.Add(action);
}
}

/// <summary>
/// 取消注册清理操作
/// </summary>
public static void UnregisterCleanupAction(Action action)
{
lock (LockObj)
{
CleanupActions.Remove(action);
}
}

private static void OnSignal(int signum)
{
string signalName = signum switch
{
SIGINT => "SIGINT (Ctrl+C)",
SIGTERM => "SIGTERM",
SIGHUP => "SIGHUP (Terminal closed)",
_ => $"Signal({signum})"
};

Logger.LogInformation($"收到信号: {signalName},正在执行清理操作...");

// 执行所有注册的清理操作
List<Action> actions;
lock (LockObj)
{
actions = new List<Action>(CleanupActions);
}

foreach (var action in actions)
{
try
{
action();
}
catch (Exception ex)
{
Logger.LogError(ex, "执行清理操作时发生错误");
}
}

// 根据信号类型决定是否退出
if (signum == SIGINT)
{
// Ctrl+C 正常退出
Environment.Exit(0);
}
else
{
// 其他信号也正常退出
Environment.Exit(0);
}
}
}
132 changes: 132 additions & 0 deletions MSLX.Daemon/Utils/WindowsConsoleHandler.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,132 @@
using System.Diagnostics;
using System.Runtime.InteropServices;

namespace MSLX.Daemon.Utils;

/// <summary>
/// Windows 控制台关闭事件处理器
/// 用于在控制台窗口关闭时执行清理操作
/// </summary>
public static class WindowsConsoleHandler
{
private static readonly ILogger Logger;
private static readonly List<Action> CleanupActions = new();
private static readonly object LockObj = new();
private static bool _initialized = false;

// 保持委托引用,防止被 GC 回收
private static HandlerRoutine? _handlerRoutine;

// Windows API 常量
private const int CTRL_C_EVENT = 0;
private const int CTRL_BREAK_EVENT = 1;
private const int CTRL_CLOSE_EVENT = 2;
private const int CTRL_LOGOFF_EVENT = 5;
private const int CTRL_SHUTDOWN_EVENT = 6;

// 委托类型
private delegate bool HandlerRoutine(int ctrlType);

[DllImport("kernel32.dll", SetLastError = true)]
private static extern bool SetConsoleCtrlHandler(HandlerRoutine handler, bool add);

[DllImport("kernel32.dll", SetLastError = true)]
private static extern IntPtr GetCurrentProcess();

[DllImport("kernel32.dll", SetLastError = true)]
private static extern bool TerminateProcess(IntPtr hProcess, uint uExitCode);

static WindowsConsoleHandler()
{
// 创建一个简单的日志记录器
var loggerFactory = LoggerFactory.Create(builder => builder.AddConsole());
Logger = loggerFactory.CreateLogger("WindowsConsoleHandler");
}

/// <summary>
/// 初始化控制台关闭事件处理
/// </summary>
public static void Initialize()
{
if (_initialized) return;

lock (LockObj)
{
if (_initialized) return;

// 创建委托实例并保持引用,防止被 GC 回收
_handlerRoutine = OnConsoleCtrlEvent;
SetConsoleCtrlHandler(_handlerRoutine, true);
_initialized = true;

Logger.LogDebug("Windows 控制台关闭事件处理器已初始化");
}
}

/// <summary>
/// 注册清理操作
/// </summary>
public static void RegisterCleanupAction(Action action)
{
lock (LockObj)
{
CleanupActions.Add(action);
}
}

/// <summary>
/// 取消注册清理操作
/// </summary>
public static void UnregisterCleanupAction(Action action)
{
lock (LockObj)
{
CleanupActions.Remove(action);
}
}

private static bool OnConsoleCtrlEvent(int ctrlType)
{
string eventName = ctrlType switch
{
CTRL_C_EVENT => "CTRL_C",
CTRL_BREAK_EVENT => "CTRL_BREAK",
CTRL_CLOSE_EVENT => "CTRL_CLOSE",
CTRL_LOGOFF_EVENT => "CTRL_LOGOFF",
CTRL_SHUTDOWN_EVENT => "CTRL_SHUTDOWN",
_ => $"UNKNOWN({ctrlType})"
};

Logger.LogInformation($"收到控制台关闭事件: {eventName},正在执行清理操作...");

// 执行所有注册的清理操作
List<Action> actions;
lock (LockObj)
{
actions = new List<Action>(CleanupActions);
}

foreach (var action in actions)
{
try
{
action();
}
catch (Exception ex)
{
Logger.LogError(ex, "执行清理操作时发生错误");
}
}

// 对于 CTRL_CLOSE 事件,系统只给大约 5-10 秒的时间
// 我们需要尽快退出
if (ctrlType == CTRL_CLOSE_EVENT)
{
// 强制终止当前进程
var currentProcess = Process.GetCurrentProcess();
TerminateProcess(currentProcess.Handle, 0);
}

return true;
}
}
Loading