From 610b2c92bdf39b50a93a5869e4159393d9214e6f Mon Sep 17 00:00:00 2001 From: Miha Zupan Date: Thu, 11 Apr 2024 15:29:07 +0200 Subject: [PATCH] Reduce the overhead of updating the activity timeout timer --- .../ActivityCancellationTokenSource.cs | 23 +++++++++++++++++-- 1 file changed, 21 insertions(+), 2 deletions(-) diff --git a/src/ReverseProxy/Utilities/ActivityCancellationTokenSource.cs b/src/ReverseProxy/Utilities/ActivityCancellationTokenSource.cs index 7cd6ef79a..2471e0eb3 100644 --- a/src/ReverseProxy/Utilities/ActivityCancellationTokenSource.cs +++ b/src/ReverseProxy/Utilities/ActivityCancellationTokenSource.cs @@ -10,6 +10,11 @@ namespace Yarp.ReverseProxy.Utilities; internal sealed class ActivityCancellationTokenSource : CancellationTokenSource { + // Avoid paying the cost of updating the timeout timer if doing so won't meaningfully affect + // the overall timeout duration (default is 100s). This is a trade-off between precision and performance. + // The exact value is somewhat arbitrary, but should be large enough to avoid most timer updates. + private const int TimeoutResolutionMs = 20; + private const int MaxQueueSize = 1024; private static readonly ConcurrentQueue _sharedSources = new(); private static int _count; @@ -22,6 +27,7 @@ internal sealed class ActivityCancellationTokenSource : CancellationTokenSource }; private int _activityTimeoutMs; + private uint _lastTimeoutTicks; private CancellationTokenRegistration _linkedRegistration1; private CancellationTokenRegistration _linkedRegistration2; @@ -29,11 +35,24 @@ private ActivityCancellationTokenSource() { } public bool CancelledByLinkedToken { get; private set; } - public void ResetTimeout() + private void StartTimeout() { + _lastTimeoutTicks = (uint)Environment.TickCount; CancelAfter(_activityTimeoutMs); } + public void ResetTimeout() + { + var currentMs = (uint)Environment.TickCount; + var elapsedMs = currentMs - _lastTimeoutTicks; + + if (elapsedMs > TimeoutResolutionMs) + { + _lastTimeoutTicks = currentMs; + CancelAfter(_activityTimeoutMs); + } + } + public static ActivityCancellationTokenSource Rent(TimeSpan activityTimeout, CancellationToken linkedToken1 = default, CancellationToken linkedToken2 = default) { if (_sharedSources.TryDequeue(out var cts)) @@ -48,7 +67,7 @@ public static ActivityCancellationTokenSource Rent(TimeSpan activityTimeout, Can cts._activityTimeoutMs = (int)activityTimeout.TotalMilliseconds; cts._linkedRegistration1 = linkedToken1.UnsafeRegister(_linkedTokenCancelDelegate, cts); cts._linkedRegistration2 = linkedToken2.UnsafeRegister(_linkedTokenCancelDelegate, cts); - cts.ResetTimeout(); + cts.StartTimeout(); return cts; }