diff --git a/Libraries/SPTarkov.Server.Assets/SPT_Data/configs/core.json b/Libraries/SPTarkov.Server.Assets/SPT_Data/configs/core.json index 01bcb2d9..909be915 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 6c8ba5c7..e69f4709 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 c8c94860..d4b20351 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 class Program forwardedHeadersOptions.KnownProxies.Clear(); app.UseForwardedHeaders(forwardedHeadersOptions); + app.UseRequestTracking(); + SetConsoleOutputMode(); await app.Services.GetRequiredService().Startup(); @@ -147,16 +150,54 @@ public static class Program app.UseMiddleware(); - app.Use( - async (HttpContext context, RequestDelegate next) => - { - await context.RequestServices.GetRequiredService().HandleRequest(context, 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) + { + try + { + GC.TryStartNoGCRegion( + 1024L * 1024L * 1024L * config.NoGCRegionMaxMemoryGB, + 1024L * 1024L * 1024L * config.NoGCRegionMaxLOHMemoryGB, + true + ); + } + catch (Exception) + { + // ignored, we keep going + } + } + } + + 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) { builder.WebHost.ConfigureKestrel( diff --git a/SPTarkov.Server/Services/RequestTrackingMiddleware.cs b/SPTarkov.Server/Services/RequestTrackingMiddleware.cs new file mode 100644 index 00000000..d24d1b0a --- /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(); + } +}