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();
+ }
+}