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..cb4a6171a 100644 --- a/Libraries/SPTarkov.Server.Core/Models/Spt/Config/CoreConfig.cs +++ b/Libraries/SPTarkov.Server.Core/Models/Spt/Config/CoreConfig.cs @@ -42,6 +42,50 @@ 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 ArgumentOutOfRangeException( + $"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 ArgumentOutOfRangeException( + $"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..3c7c4e3f0 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; @@ -147,12 +148,9 @@ private static void ConfigureWebApp(WebApplication app) app.UseMiddleware(); - app.Use( - async (HttpContext context, RequestDelegate next) => - { - await context.RequestServices.GetRequiredService().HandleRequest(context, next); - } - ); + app.UseNoGCRegions(); + + app.Use(async (context, next) => await context.RequestServices.GetRequiredService().HandleRequest(context, next)); app.UseSptBlazor(); } diff --git a/SPTarkov.Server/Services/NoGCRegionMiddleware.cs b/SPTarkov.Server/Services/NoGCRegionMiddleware.cs new file mode 100644 index 000000000..326047da7 --- /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 long _activeRequests; + + private static bool OtherRequestsActive + { + get + { + return Interlocked.Read(ref _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(); + } +}