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