feat(batch3): implement service feature group
This commit is contained in:
180
dotnet/src/ZB.MOM.NatsNet.Server/Internal/ServiceManager.cs
Normal file
180
dotnet/src/ZB.MOM.NatsNet.Server/Internal/ServiceManager.cs
Normal file
@@ -0,0 +1,180 @@
|
||||
// Copyright 2012-2026 The NATS Authors
|
||||
// Licensed under the Apache License, Version 2.0
|
||||
//
|
||||
// Adapted from server/service.go and server/service_windows.go in the NATS server Go source.
|
||||
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
namespace ZB.MOM.NatsNet.Server.Internal;
|
||||
|
||||
/// <summary>
|
||||
/// Service wrappers for platform-specific server startup behavior.
|
||||
/// </summary>
|
||||
public static class ServiceManager
|
||||
{
|
||||
private static readonly Lock ServiceNameLock = new();
|
||||
private static string _serviceName = "nats-server";
|
||||
private static bool _dockerized;
|
||||
|
||||
/// <summary>
|
||||
/// Allows overriding the service name.
|
||||
/// Mirrors Go <c>SetServiceName</c>.
|
||||
/// </summary>
|
||||
public static void SetServiceName(string name)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(name))
|
||||
return;
|
||||
|
||||
lock (ServiceNameLock)
|
||||
{
|
||||
_serviceName = name;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes service-related environment flags.
|
||||
/// Mirrors Go package <c>init()</c> behavior in service_windows.go.
|
||||
/// </summary>
|
||||
public static void Init(Func<string, string?>? envLookup = null)
|
||||
{
|
||||
var lookup = envLookup ?? Environment.GetEnvironmentVariable;
|
||||
_dockerized = string.Equals(lookup("NATS_DOCKERIZED"), "1", StringComparison.Ordinal);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Runs the server startup action.
|
||||
/// Mirrors Go <c>Run</c> behavior from service.go/service_windows.go.
|
||||
/// </summary>
|
||||
public static Exception? Run(Action startServer, Func<bool>? windowsServiceProbe = null)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(startServer);
|
||||
|
||||
if (_dockerized)
|
||||
{
|
||||
startServer();
|
||||
return null;
|
||||
}
|
||||
|
||||
if (!RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
|
||||
{
|
||||
startServer();
|
||||
return null;
|
||||
}
|
||||
|
||||
if (!IsWindowsService(windowsServiceProbe))
|
||||
{
|
||||
startServer();
|
||||
return null;
|
||||
}
|
||||
|
||||
return new PlatformNotSupportedException(
|
||||
"Windows service hosting is managed by Microsoft.Extensions.Hosting.WindowsServices.");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns true when running as a Windows service.
|
||||
/// Mirrors Go <c>isWindowsService</c>.
|
||||
/// </summary>
|
||||
public static bool IsWindowsService(Func<bool>? windowsServiceProbe = null)
|
||||
{
|
||||
if (_dockerized)
|
||||
return false;
|
||||
|
||||
if (!RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
|
||||
return false;
|
||||
|
||||
if (windowsServiceProbe is not null)
|
||||
return windowsServiceProbe();
|
||||
|
||||
return !Environment.UserInteractive;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Windows service execution wrapper.
|
||||
/// Mirrors Go <c>winServiceWrapper.Execute</c> control loop semantics.
|
||||
/// </summary>
|
||||
public static (bool serviceSpecificExitCode, uint exitCode) Execute(
|
||||
Action startServer,
|
||||
Func<TimeSpan, bool> readyForConnections,
|
||||
IEnumerable<ServiceControlCommand> changes,
|
||||
Action reloadConfig,
|
||||
Action shutdown,
|
||||
Action reopenLogFile,
|
||||
Action enterLameDuckMode,
|
||||
Func<string, string?>? envLookup = null,
|
||||
Action<string>? logError = null)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(startServer);
|
||||
ArgumentNullException.ThrowIfNull(readyForConnections);
|
||||
ArgumentNullException.ThrowIfNull(changes);
|
||||
ArgumentNullException.ThrowIfNull(reloadConfig);
|
||||
ArgumentNullException.ThrowIfNull(shutdown);
|
||||
ArgumentNullException.ThrowIfNull(reopenLogFile);
|
||||
ArgumentNullException.ThrowIfNull(enterLameDuckMode);
|
||||
|
||||
var startupDelay = TimeSpan.FromSeconds(10);
|
||||
var lookup = envLookup ?? Environment.GetEnvironmentVariable;
|
||||
var configuredDelay = lookup("NATS_STARTUP_DELAY");
|
||||
if (!string.IsNullOrEmpty(configuredDelay))
|
||||
{
|
||||
if (TimeSpan.TryParse(configuredDelay, out var parsedDelay))
|
||||
{
|
||||
startupDelay = parsedDelay;
|
||||
}
|
||||
else
|
||||
{
|
||||
logError?.Invoke($"Failed to parse \"{configuredDelay}\" as a duration for startup.");
|
||||
}
|
||||
}
|
||||
|
||||
Task.Run(startServer);
|
||||
|
||||
if (!readyForConnections(startupDelay))
|
||||
return (false, 1);
|
||||
|
||||
foreach (var change in changes)
|
||||
{
|
||||
switch (change)
|
||||
{
|
||||
case ServiceControlCommand.Interrogate:
|
||||
continue;
|
||||
case ServiceControlCommand.Stop:
|
||||
case ServiceControlCommand.Shutdown:
|
||||
shutdown();
|
||||
return (false, 0);
|
||||
case ServiceControlCommand.ReopenLog:
|
||||
reopenLogFile();
|
||||
break;
|
||||
case ServiceControlCommand.LameDuckMode:
|
||||
Task.Run(enterLameDuckMode);
|
||||
break;
|
||||
case ServiceControlCommand.ParamChange:
|
||||
reloadConfig();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return (false, 0);
|
||||
}
|
||||
|
||||
internal static string CurrentServiceName
|
||||
{
|
||||
get
|
||||
{
|
||||
lock (ServiceNameLock)
|
||||
{
|
||||
return _serviceName;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public enum ServiceControlCommand
|
||||
{
|
||||
Interrogate,
|
||||
Stop,
|
||||
Shutdown,
|
||||
ReopenLog,
|
||||
LameDuckMode,
|
||||
ParamChange
|
||||
}
|
||||
@@ -220,12 +220,17 @@ public static class SignalHandler
|
||||
/// <summary>
|
||||
/// Runs the server (non-Windows). Mirrors <c>Run</c> in service.go.
|
||||
/// </summary>
|
||||
public static void Run(Action startServer) => startServer();
|
||||
public static void Run(Action startServer)
|
||||
{
|
||||
var error = ServiceManager.Run(startServer);
|
||||
if (error is not null)
|
||||
throw error;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns false on non-Windows. Mirrors <c>isWindowsService</c>.
|
||||
/// </summary>
|
||||
public static bool IsWindowsService() => false;
|
||||
public static bool IsWindowsService() => ServiceManager.IsWindowsService();
|
||||
}
|
||||
|
||||
/// <summary>Unix signal codes for NATS command mapping.</summary>
|
||||
|
||||
Reference in New Issue
Block a user