Skip to content

Commit 55ad01c

Browse files
committed
Add expiration for stored request durations
This adds configurable expiration time for request durations. This is to prevent memory leak in long-running applications where otherwise the queue would only keep growing indefinitely.
1 parent 67b9261 commit 55ad01c

File tree

2 files changed

+37
-4
lines changed

2 files changed

+37
-4
lines changed

src/Stats.cs

Lines changed: 36 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7,11 +7,14 @@ namespace SpacetimeDB
77
{
88
public class NetworkRequestTracker
99
{
10-
private readonly ConcurrentQueue<(DateTime End, TimeSpan Duration, string Metadata)> _requestDurations = new();
10+
private readonly ConcurrentQueue<(DateTime End, (TimeSpan Duration, string Metadata) Request)> _requestDurations = new();
1111

1212
private uint _nextRequestId;
1313
private readonly Dictionary<uint, (DateTime Start, string Metadata)> _requests = new();
1414

15+
// Limit the number of request durations we store to prevent memory leaks.
16+
public int KeepLastSeconds = 5 * 60;
17+
1518
internal uint StartTrackingRequest(string metadata = "")
1619
{
1720
// Record the start time of the request
@@ -40,9 +43,25 @@ internal bool FinishTrackingRequest(uint requestId)
4043
return true;
4144
}
4245

46+
private IEnumerable<(TimeSpan Duration, string Metadata)> GetRequestDurations(int lastSeconds)
47+
{
48+
var cutoff = DateTime.UtcNow.AddSeconds(-lastSeconds);
49+
return _requestDurations.SkipWhile(x => x.End < cutoff).Select(x => x.Request);
50+
}
51+
4352
internal void InsertRequest(TimeSpan duration, string metadata)
4453
{
45-
_requestDurations.Enqueue((DateTime.UtcNow, duration, metadata));
54+
lock (_requestDurations)
55+
{
56+
// Remove expired entries, we need to do this atomically.
57+
var cutoff = DateTime.UtcNow.AddSeconds(-KeepLastSeconds);
58+
var removeCount = _requestDurations.TakeWhile(x => x.End < cutoff).Count();
59+
for (var i = 0; i < removeCount; i++)
60+
{
61+
_requestDurations.TryDequeue(out _);
62+
}
63+
_requestDurations.Enqueue((DateTime.UtcNow, (duration, metadata)));
64+
}
4665
}
4766

4867
internal void InsertRequest(DateTime start, string metadata)
@@ -52,8 +71,13 @@ internal void InsertRequest(DateTime start, string metadata)
5271

5372
public ((TimeSpan Duration, string Metadata) Min, (TimeSpan Duration, string Metadata) Max)? GetMinMaxTimes(int lastSeconds)
5473
{
74+
if (lastSeconds > KeepLastSeconds)
75+
{
76+
throw new ArgumentException($"lastSeconds must be less than or equal to KeepLastSeconds = {KeepLastSeconds}", nameof(lastSeconds));
77+
}
78+
5579
var cutoff = DateTime.UtcNow.AddSeconds(-lastSeconds);
56-
var requestDurations = _requestDurations.Where(x => x.End >= cutoff).Select(x => (x.Duration, x.Metadata));
80+
var requestDurations = _requestDurations.SkipWhile(x => x.End < cutoff).Select(x => x.Request);
5781

5882
if (!requestDurations.Any())
5983
{
@@ -74,5 +98,14 @@ public class Stats
7498
public readonly NetworkRequestTracker SubscriptionRequestTracker = new();
7599
public readonly NetworkRequestTracker AllReducersTracker = new();
76100
public readonly NetworkRequestTracker ParseMessageTracker = new();
101+
102+
public void KeepLastSeconds(int seconds)
103+
{
104+
ReducerRequestTracker.KeepLastSeconds = seconds;
105+
OneOffRequestTracker.KeepLastSeconds = seconds;
106+
SubscriptionRequestTracker.KeepLastSeconds = seconds;
107+
AllReducersTracker.KeepLastSeconds = seconds;
108+
ParseMessageTracker.KeepLastSeconds = seconds;
109+
}
77110
}
78111
}

tests~/VerifyInit.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -56,7 +56,7 @@ public override void Write(VerifyJsonWriter writer, NetworkRequestTracker value)
5656
}
5757

5858
if (
59-
value.GetMinMaxTimes(int.MaxValue) is
59+
value.GetMinMaxTimes(60) is
6060
{ Min.Metadata: var Min, Max.Metadata: var Max }
6161
)
6262
{

0 commit comments

Comments
 (0)