From 3972e148405a191ab6c9654db718a3b2281ec9cc Mon Sep 17 00:00:00 2001 From: Alex Date: Mon, 20 Oct 2025 11:05:58 +0100 Subject: [PATCH 1/4] Added NoGCRegion feature --- .../SPT_Data/configs/core.json | 3 ++ .../Models/Spt/Config/CoreConfig.cs | 38 ++++++++++++++ SPTarkov.Server/Program.cs | 51 +++++++++++++++++-- .../Services/RequestTrackingMiddleware.cs | 41 +++++++++++++++ 4 files changed, 128 insertions(+), 5 deletions(-) create mode 100644 SPTarkov.Server/Services/RequestTrackingMiddleware.cs diff --git a/Libraries/SPTarkov.Server.Assets/SPT_Data/configs/core.json b/Libraries/SPTarkov.Server.Assets/SPT_Data/configs/core.json index 01bcb2d9e..909be9159 100644 --- a/Libraries/SPTarkov.Server.Assets/SPT_Data/configs/core.json +++ b/Libraries/SPTarkov.Server.Assets/SPT_Data/configs/core.json @@ -5,6 +5,9 @@ "profileSaveIntervalSeconds": 15, "sptFriendNickname": "SPT", "allowProfileWipe": true, + "enableNoGCRegions": true, + "noGCRegionMaxMemoryGB": 4, + "noGCRegionMaxLOHMemoryGB": 3, "bsgLogging": { "verbosity": 6, "sendToServer": false diff --git a/Libraries/SPTarkov.Server.Core/Models/Spt/Config/CoreConfig.cs b/Libraries/SPTarkov.Server.Core/Models/Spt/Config/CoreConfig.cs index 6c8ba5c7f..e69f4709d 100644 --- a/Libraries/SPTarkov.Server.Core/Models/Spt/Config/CoreConfig.cs +++ b/Libraries/SPTarkov.Server.Core/Models/Spt/Config/CoreConfig.cs @@ -42,6 +42,44 @@ public record CoreConfig : BaseConfig [JsonPropertyName("features")] public required ServerFeatures Features { get; set; } + [JsonPropertyName("enableNoGCRegions")] + // ReSharper disable once InconsistentNaming + public required bool EnableNoGCRegions { get; set; } + + // ReSharper disable once InconsistentNaming + private int _noGCRegionMaxMemoryGB = 4; + [JsonPropertyName("noGCRegionMaxMemoryGB")] + // ReSharper disable once InconsistentNaming + public required int NoGCRegionMaxMemoryGB + { + get => _noGCRegionMaxMemoryGB; + set + { + if (value <= 0) + { + throw new Exception($"Invalid value {nameof(NoGCRegionMaxMemoryGB)}: {value}. Must be greater than zero."); + } + _noGCRegionMaxMemoryGB = value; + } + } + + // ReSharper disable once InconsistentNaming + private int _noGCRegionMaxLOHMemoryGB = 3; + [JsonPropertyName("noGCRegionMaxLOHMemoryGB")] + // ReSharper disable once InconsistentNaming + public required int NoGCRegionMaxLOHMemoryGB + { + get => _noGCRegionMaxLOHMemoryGB; + set + { + if (value <= 0) + { + throw new Exception($"Invalid value {nameof(NoGCRegionMaxLOHMemoryGB)}: {value}. Must be greater than zero."); + } + _noGCRegionMaxLOHMemoryGB = value; + } + } + /// /// Commit hash build server was created from /// diff --git a/SPTarkov.Server/Program.cs b/SPTarkov.Server/Program.cs index c8c948601..d4b20351d 100644 --- a/SPTarkov.Server/Program.cs +++ b/SPTarkov.Server/Program.cs @@ -1,5 +1,6 @@ using System.Net; using System.Net.Sockets; +using System.Runtime; using System.Runtime.InteropServices; using System.Security.Authentication; using System.Text; @@ -118,6 +119,8 @@ public static async Task StartServer(string[] args) forwardedHeadersOptions.KnownProxies.Clear(); app.UseForwardedHeaders(forwardedHeadersOptions); + app.UseRequestTracking(); + SetConsoleOutputMode(); await app.Services.GetRequiredService().Startup(); @@ -147,14 +150,52 @@ private static void ConfigureWebApp(WebApplication app) app.UseMiddleware(); - app.Use( - async (HttpContext context, RequestDelegate next) => + app.Use(async (context, next) => await HandleRequest(context, next)); + + app.UseSptBlazor(); + } + + private static async Task HandleRequest(HttpContext context, RequestDelegate next) + { + var config = context.RequestServices.GetRequiredService().GetConfig(); + + // if no other requests are running, start the no GC region, otherwise dont start it + if (!RequestTrackingMiddleware.OtherRequestsActive) + { + if (config.EnableNoGCRegions && GCSettings.LatencyMode != GCLatencyMode.NoGCRegion) { - await context.RequestServices.GetRequiredService().HandleRequest(context, next); + try + { + GC.TryStartNoGCRegion( + 1024L * 1024L * 1024L * config.NoGCRegionMaxMemoryGB, + 1024L * 1024L * 1024L * config.NoGCRegionMaxLOHMemoryGB, + true + ); + } + catch (Exception) + { + // ignored, we keep going + } } - ); + } - app.UseSptBlazor(); + await context.RequestServices.GetRequiredService().HandleRequest(context, next); + + // if no other requests are running, end the no GC region, otherwise dont stop it as other requests need it still + if (!RequestTrackingMiddleware.OtherRequestsActive) + { + if (config.EnableNoGCRegions && GCSettings.LatencyMode == GCLatencyMode.NoGCRegion) + { + try + { + GC.EndNoGCRegion(); + } + catch (Exception) + { + // ignored, we dont care about handling this + } + } + } } private static void ConfigureKestrel(WebApplicationBuilder builder) diff --git a/SPTarkov.Server/Services/RequestTrackingMiddleware.cs b/SPTarkov.Server/Services/RequestTrackingMiddleware.cs new file mode 100644 index 000000000..d24d1b0ae --- /dev/null +++ b/SPTarkov.Server/Services/RequestTrackingMiddleware.cs @@ -0,0 +1,41 @@ +namespace SPTarkov.Server.Services; + +public class RequestTrackingMiddleware +{ + private static int _activeRequests; + private readonly RequestDelegate _next; + + public static bool OtherRequestsActive + { + get + { + return _activeRequests > 1; + } + } + + public RequestTrackingMiddleware(RequestDelegate next) + { + _next = next; + } + + public async Task InvokeAsync(HttpContext context) + { + Interlocked.Increment(ref _activeRequests); + try + { + await _next(context); + } + finally + { + Interlocked.Decrement(ref _activeRequests); + } + } +} + +public static class RequestTrackingMiddlewareExtensions +{ + public static IApplicationBuilder UseRequestTracking(this IApplicationBuilder builder) + { + return builder.UseMiddleware(); + } +} From af9160c94d70dc345f68ea24bb8103dcc16f4645 Mon Sep 17 00:00:00 2001 From: Chomp Date: Mon, 20 Oct 2025 11:20:14 +0100 Subject: [PATCH 2/4] Use specific exception --- .../Models/Spt/Config/CoreConfig.cs | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/Libraries/SPTarkov.Server.Core/Models/Spt/Config/CoreConfig.cs b/Libraries/SPTarkov.Server.Core/Models/Spt/Config/CoreConfig.cs index e69f4709d..cb4a6171a 100644 --- a/Libraries/SPTarkov.Server.Core/Models/Spt/Config/CoreConfig.cs +++ b/Libraries/SPTarkov.Server.Core/Models/Spt/Config/CoreConfig.cs @@ -48,6 +48,7 @@ public record CoreConfig : BaseConfig // ReSharper disable once InconsistentNaming private int _noGCRegionMaxMemoryGB = 4; + [JsonPropertyName("noGCRegionMaxMemoryGB")] // ReSharper disable once InconsistentNaming public required int NoGCRegionMaxMemoryGB @@ -57,7 +58,9 @@ public required int NoGCRegionMaxMemoryGB { if (value <= 0) { - throw new Exception($"Invalid value {nameof(NoGCRegionMaxMemoryGB)}: {value}. Must be greater than zero."); + throw new ArgumentOutOfRangeException( + $"Invalid value: {nameof(NoGCRegionMaxMemoryGB)}: {value}. Must be greater than zero." + ); } _noGCRegionMaxMemoryGB = value; } @@ -65,6 +68,7 @@ public required int NoGCRegionMaxMemoryGB // ReSharper disable once InconsistentNaming private int _noGCRegionMaxLOHMemoryGB = 3; + [JsonPropertyName("noGCRegionMaxLOHMemoryGB")] // ReSharper disable once InconsistentNaming public required int NoGCRegionMaxLOHMemoryGB @@ -74,7 +78,9 @@ public required int NoGCRegionMaxLOHMemoryGB { if (value <= 0) { - throw new Exception($"Invalid value {nameof(NoGCRegionMaxLOHMemoryGB)}: {value}. Must be greater than zero."); + throw new ArgumentOutOfRangeException( + $"Invalid value {nameof(NoGCRegionMaxLOHMemoryGB)}: {value}. Must be greater than zero." + ); } _noGCRegionMaxLOHMemoryGB = value; } From d672130eebe3b09e6378dd4d52ab89d3d9d16c23 Mon Sep 17 00:00:00 2001 From: Alex Date: Mon, 20 Oct 2025 11:48:35 +0100 Subject: [PATCH 3/4] Moved NoGCRegion into middleware service --- SPTarkov.Server/Program.cs | 49 +----------- .../Services/NoGCRegionMiddleware.cs | 80 +++++++++++++++++++ .../Services/RequestTrackingMiddleware.cs | 41 ---------- 3 files changed, 83 insertions(+), 87 deletions(-) create mode 100644 SPTarkov.Server/Services/NoGCRegionMiddleware.cs delete mode 100644 SPTarkov.Server/Services/RequestTrackingMiddleware.cs diff --git a/SPTarkov.Server/Program.cs b/SPTarkov.Server/Program.cs index d4b20351d..3c7c4e3f0 100644 --- a/SPTarkov.Server/Program.cs +++ b/SPTarkov.Server/Program.cs @@ -119,8 +119,6 @@ public static async Task StartServer(string[] args) forwardedHeadersOptions.KnownProxies.Clear(); app.UseForwardedHeaders(forwardedHeadersOptions); - app.UseRequestTracking(); - SetConsoleOutputMode(); await app.Services.GetRequiredService().Startup(); @@ -150,52 +148,11 @@ private static void ConfigureWebApp(WebApplication app) app.UseMiddleware(); - app.Use(async (context, next) => await HandleRequest(context, next)); - - app.UseSptBlazor(); - } - - private static async Task HandleRequest(HttpContext context, RequestDelegate next) - { - var config = context.RequestServices.GetRequiredService().GetConfig(); - - // if no other requests are running, start the no GC region, otherwise dont start it - if (!RequestTrackingMiddleware.OtherRequestsActive) - { - if (config.EnableNoGCRegions && GCSettings.LatencyMode != GCLatencyMode.NoGCRegion) - { - try - { - GC.TryStartNoGCRegion( - 1024L * 1024L * 1024L * config.NoGCRegionMaxMemoryGB, - 1024L * 1024L * 1024L * config.NoGCRegionMaxLOHMemoryGB, - true - ); - } - catch (Exception) - { - // ignored, we keep going - } - } - } + app.UseNoGCRegions(); - await context.RequestServices.GetRequiredService().HandleRequest(context, next); + app.Use(async (context, next) => await context.RequestServices.GetRequiredService().HandleRequest(context, next)); - // if no other requests are running, end the no GC region, otherwise dont stop it as other requests need it still - if (!RequestTrackingMiddleware.OtherRequestsActive) - { - if (config.EnableNoGCRegions && GCSettings.LatencyMode == GCLatencyMode.NoGCRegion) - { - try - { - GC.EndNoGCRegion(); - } - catch (Exception) - { - // ignored, we dont care about handling this - } - } - } + app.UseSptBlazor(); } private static void ConfigureKestrel(WebApplicationBuilder builder) diff --git a/SPTarkov.Server/Services/NoGCRegionMiddleware.cs b/SPTarkov.Server/Services/NoGCRegionMiddleware.cs new file mode 100644 index 000000000..99c271137 --- /dev/null +++ b/SPTarkov.Server/Services/NoGCRegionMiddleware.cs @@ -0,0 +1,80 @@ +using System.Runtime; +using SPTarkov.Server.Core.Models.Spt.Config; +using SPTarkov.Server.Core.Servers; + +namespace SPTarkov.Server.Services; + +// ReSharper disable once InconsistentNaming +public class NoGCRegionMiddleware(RequestDelegate next) +{ + private static int _activeRequests; + + private static bool OtherRequestsActive + { + get + { + return _activeRequests > 1; + } + } + + public async Task InvokeAsync(HttpContext context) + { + Interlocked.Increment(ref _activeRequests); + + var config = context.RequestServices.GetRequiredService().GetConfig(); + + // if no other requests are running, start the no GC region, otherwise dont start it + if (!OtherRequestsActive) + { + if (config.EnableNoGCRegions && GCSettings.LatencyMode != GCLatencyMode.NoGCRegion) + { + try + { + GC.TryStartNoGCRegion( + 1024L * 1024L * 1024L * config.NoGCRegionMaxMemoryGB, + 1024L * 1024L * 1024L * config.NoGCRegionMaxLOHMemoryGB, + true + ); + } + catch (Exception) + { + // ignored, we keep going + } + } + } + try + { + await next(context); + } + finally + { + Interlocked.Decrement(ref _activeRequests); + } + + // if no other requests are running, end the no GC region, otherwise dont stop it as other requests need it still + if (!OtherRequestsActive) + { + if (config.EnableNoGCRegions && GCSettings.LatencyMode == GCLatencyMode.NoGCRegion) + { + try + { + GC.EndNoGCRegion(); + } + catch (Exception) + { + // ignored, we dont care about handling this + } + } + } + } +} + +// ReSharper disable once InconsistentNaming +public static class NoGCRegionMiddlewareExtensions +{ + // ReSharper disable once InconsistentNaming + public static IApplicationBuilder UseNoGCRegions(this IApplicationBuilder builder) + { + return builder.UseMiddleware(); + } +} diff --git a/SPTarkov.Server/Services/RequestTrackingMiddleware.cs b/SPTarkov.Server/Services/RequestTrackingMiddleware.cs deleted file mode 100644 index d24d1b0ae..000000000 --- a/SPTarkov.Server/Services/RequestTrackingMiddleware.cs +++ /dev/null @@ -1,41 +0,0 @@ -namespace SPTarkov.Server.Services; - -public class RequestTrackingMiddleware -{ - private static int _activeRequests; - private readonly RequestDelegate _next; - - public static bool OtherRequestsActive - { - get - { - return _activeRequests > 1; - } - } - - public RequestTrackingMiddleware(RequestDelegate next) - { - _next = next; - } - - public async Task InvokeAsync(HttpContext context) - { - Interlocked.Increment(ref _activeRequests); - try - { - await _next(context); - } - finally - { - Interlocked.Decrement(ref _activeRequests); - } - } -} - -public static class RequestTrackingMiddlewareExtensions -{ - public static IApplicationBuilder UseRequestTracking(this IApplicationBuilder builder) - { - return builder.UseMiddleware(); - } -} From f5723044444b825645d13a9f6a29d29e34e5da07 Mon Sep 17 00:00:00 2001 From: Alex Date: Mon, 20 Oct 2025 11:51:35 +0100 Subject: [PATCH 4/4] using interlocked --- SPTarkov.Server/Services/NoGCRegionMiddleware.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/SPTarkov.Server/Services/NoGCRegionMiddleware.cs b/SPTarkov.Server/Services/NoGCRegionMiddleware.cs index 99c271137..326047da7 100644 --- a/SPTarkov.Server/Services/NoGCRegionMiddleware.cs +++ b/SPTarkov.Server/Services/NoGCRegionMiddleware.cs @@ -7,13 +7,13 @@ namespace SPTarkov.Server.Services; // ReSharper disable once InconsistentNaming public class NoGCRegionMiddleware(RequestDelegate next) { - private static int _activeRequests; + private static long _activeRequests; private static bool OtherRequestsActive { get { - return _activeRequests > 1; + return Interlocked.Read(ref _activeRequests) > 1; } }