Skip to content

Commit 7c93c51

Browse files
committed
Convert TimeoutControl to timestamps
1 parent c1c724e commit 7c93c51

19 files changed

+136
-117
lines changed

src/Servers/Kestrel/Core/src/Internal/Http2/Http2Connection.cs

+1-1
Original file line numberDiff line numberDiff line change
@@ -1270,7 +1270,7 @@ private void UpdateCompletedStreams()
12701270
if (stream.DrainExpirationTicks == default)
12711271
{
12721272
_serverActiveStreamCount--;
1273-
stream.DrainExpirationTicks = now + (long)(Constants.RequestBodyDrainTimeout.TotalSeconds * TimeProvider.TimestampFrequency);
1273+
stream.DrainExpirationTicks = now + Constants.RequestBodyDrainTimeout.ToTicks(TimeProvider.TimestampFrequency);
12741274
}
12751275

12761276
if (stream.EndStreamReceived || stream.RstStreamReceived || stream.DrainExpirationTicks < now)

src/Servers/Kestrel/Core/src/Internal/Http2/Http2KeepAlive.cs

+2-2
Original file line numberDiff line numberDiff line change
@@ -29,9 +29,9 @@ internal sealed class Http2KeepAlive
2929

3030
public Http2KeepAlive(TimeSpan keepAliveInterval, TimeSpan keepAliveTimeout, TimeProvider timeProvider)
3131
{
32-
_keepAliveInterval = (long)(keepAliveInterval.TotalSeconds * timeProvider.TimestampFrequency);
32+
_keepAliveInterval = keepAliveInterval.ToTicks(timeProvider.TimestampFrequency);
3333
_keepAliveTimeout = keepAliveTimeout == TimeSpan.MaxValue ? long.MaxValue
34-
: (long)(keepAliveTimeout.TotalSeconds * timeProvider.TimestampFrequency);
34+
: keepAliveTimeout.ToTicks(timeProvider.TimestampFrequency);
3535
_timeProvider = timeProvider;
3636
}
3737

src/Servers/Kestrel/Core/src/Internal/HttpConnection.cs

+4-4
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,7 @@ public HttpConnection(BaseHttpConnectionContext context)
3636
_context = context;
3737
_timeProvider = _context.ServiceContext.TimeProvider;
3838

39-
_timeoutControl = new TimeoutControl(this);
39+
_timeoutControl = new TimeoutControl(this, _timeProvider.TimestampFrequency);
4040

4141
// Tests override the timeout control sometimes
4242
_context.TimeoutControl ??= _timeoutControl;
@@ -49,7 +49,7 @@ public async Task ProcessRequestsAsync<TContext>(IHttpApplication<TContext> http
4949
try
5050
{
5151
// Ensure TimeoutControl._lastTimestamp is initialized before anything that could set timeouts runs.
52-
_timeoutControl.Initialize(_timeProvider.GetUtcNow().Ticks);
52+
_timeoutControl.Initialize(_timeProvider.GetTimestamp());
5353

5454
IRequestProcessor? requestProcessor = null;
5555

@@ -236,9 +236,9 @@ private void Tick()
236236
return;
237237
}
238238

239-
// It's safe to use UtcNowUnsynchronized since Tick is called by the Heartbeat.
240239
var now = _timeProvider.GetUtcNow();
241-
_timeoutControl.Tick(now);
240+
var ticks = _timeProvider.GetTimestamp();
241+
_timeoutControl.Tick(ticks);
242242
_requestProcessor!.Tick(now);
243243
}
244244

src/Servers/Kestrel/Core/src/Internal/Infrastructure/ITimeoutControl.cs

+1-1
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ internal interface ITimeoutControl
1414
void CancelTimeout();
1515

1616
void InitializeHttp2(InputFlowControl connectionInputFlowControl);
17-
void Tick(DateTimeOffset now);
17+
void Tick(long now);
1818

1919
void StartRequestBody(MinDataRate minRate);
2020
void StopRequestBody();
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
// Licensed to the .NET Foundation under one or more agreements.
2+
// The .NET Foundation licenses this file to you under the MIT license.
3+
4+
namespace System;
5+
6+
internal static class TimeSpanExtensions
7+
{
8+
public static long ToTicks(this TimeSpan timeSpan, long tickFrequency)
9+
{
10+
return (long)(timeSpan.TotalSeconds * tickFrequency);
11+
}
12+
}

src/Servers/Kestrel/Core/src/Internal/Infrastructure/TimeoutControl.cs

+20-14
Original file line numberDiff line numberDiff line change
@@ -11,11 +11,14 @@ internal sealed class TimeoutControl : ITimeoutControl, IConnectionTimeoutFeatur
1111
{
1212
private readonly ITimeoutHandler _timeoutHandler;
1313

14+
private readonly long _heartbeatIntervalTicks;
15+
private readonly long _tickFrequency;
1416
private long _lastTimestamp;
1517
private long _timeoutTimestamp = long.MaxValue;
1618

1719
private readonly object _readTimingLock = new object();
1820
private MinDataRate? _minReadRate;
21+
private long _minReadRateGracePeriodTicks;
1922
private bool _readTimingEnabled;
2023
private bool _readTimingPauseRequested;
2124
private long _readTimingElapsedTicks;
@@ -29,9 +32,11 @@ internal sealed class TimeoutControl : ITimeoutControl, IConnectionTimeoutFeatur
2932
private int _concurrentAwaitingWrites;
3033
private long _writeTimingTimeoutTimestamp;
3134

32-
public TimeoutControl(ITimeoutHandler timeoutHandler)
35+
public TimeoutControl(ITimeoutHandler timeoutHandler, long tickFrequency)
3336
{
3437
_timeoutHandler = timeoutHandler;
38+
_tickFrequency = tickFrequency;
39+
_heartbeatIntervalTicks = Heartbeat.Interval.ToTicks(_tickFrequency);
3540
}
3641

3742
public TimeoutReason TimerReason { get; private set; }
@@ -43,9 +48,9 @@ internal void Initialize(long nowTicks)
4348
_lastTimestamp = nowTicks;
4449
}
4550

46-
public void Tick(DateTimeOffset now)
51+
public void Tick(long now)
4752
{
48-
var timestamp = now.Ticks;
53+
var timestamp = now;
4954

5055
CheckForTimeout(timestamp);
5156
CheckForReadDataRateTimeout(timestamp);
@@ -108,13 +113,13 @@ private void CheckForReadDataRateTimeout(long timestamp)
108113

109114
// Assume overly long tick intervals are the result of server resource starvation.
110115
// Don't count extra time between ticks against the rate limit.
111-
_readTimingElapsedTicks += Math.Min(timestamp - _lastTimestamp, Heartbeat.Interval.Ticks);
116+
_readTimingElapsedTicks += Math.Min(timestamp - _lastTimestamp, _heartbeatIntervalTicks);
112117

113118
Debug.Assert(_minReadRate != null);
114119

115-
if (_minReadRate.BytesPerSecond > 0 && _readTimingElapsedTicks > _minReadRate.GracePeriod.Ticks)
120+
if (_minReadRate.BytesPerSecond > 0 && _readTimingElapsedTicks > _minReadRateGracePeriodTicks)
116121
{
117-
var elapsedSeconds = (double)_readTimingElapsedTicks / TimeSpan.TicksPerSecond;
122+
var elapsedSeconds = (double)_readTimingElapsedTicks / _tickFrequency;
118123
var rate = _readTimingBytesRead / elapsedSeconds;
119124

120125
timeout = rate < _minReadRate.BytesPerSecond && !Debugger.IsAttached;
@@ -145,7 +150,7 @@ private void CheckForWriteDataRateTimeout(long timestamp)
145150
{
146151
// Assume overly long tick intervals are the result of server resource starvation.
147152
// Don't count extra time between ticks against the rate limit.
148-
var extraTimeForTick = timestamp - _lastTimestamp - Heartbeat.Interval.Ticks;
153+
var extraTimeForTick = timestamp - _lastTimestamp - _heartbeatIntervalTicks;
149154

150155
if (extraTimeForTick > 0)
151156
{
@@ -186,7 +191,7 @@ private void AssignTimeout(long ticks, TimeoutReason timeoutReason)
186191
TimerReason = timeoutReason;
187192

188193
// Add Heartbeat.Interval since this can be called right before the next heartbeat.
189-
Interlocked.Exchange(ref _timeoutTimestamp, Interlocked.Read(ref _lastTimestamp) + ticks + Heartbeat.Interval.Ticks);
194+
Interlocked.Exchange(ref _timeoutTimestamp, Interlocked.Read(ref _lastTimestamp) + ticks + _heartbeatIntervalTicks);
190195
}
191196

192197
public void InitializeHttp2(InputFlowControl connectionInputFlowControl)
@@ -202,6 +207,7 @@ public void StartRequestBody(MinDataRate minRate)
202207
Debug.Assert(_concurrentIncompleteRequestBodies == 0 || minRate == _minReadRate, "Multiple simultaneous read data rates are not supported.");
203208

204209
_minReadRate = minRate;
210+
_minReadRateGracePeriodTicks = minRate.GracePeriod.ToTicks(_tickFrequency);
205211
_concurrentIncompleteRequestBodies++;
206212

207213
if (_concurrentIncompleteRequestBodies == 1)
@@ -282,14 +288,14 @@ public void BytesWrittenToBuffer(MinDataRate minRate, long count)
282288
lock (_writeTimingLock)
283289
{
284290
// Add Heartbeat.Interval since this can be called right before the next heartbeat.
285-
var currentTimeUpperBound = Interlocked.Read(ref _lastTimestamp) + Heartbeat.Interval.Ticks;
286-
var ticksToCompleteWriteAtMinRate = TimeSpan.FromSeconds(count / minRate.BytesPerSecond).Ticks;
291+
var currentTimeUpperBound = Interlocked.Read(ref _lastTimestamp) + _heartbeatIntervalTicks;
292+
var ticksToCompleteWriteAtMinRate = TimeSpan.FromSeconds(count / minRate.BytesPerSecond).ToTicks(_tickFrequency);
287293

288294
// If ticksToCompleteWriteAtMinRate is less than the configured grace period,
289295
// allow that write to take up to the grace period to complete. Only add the grace period
290296
// to the current time and not to any accumulated timeout.
291297
var singleWriteTimeoutTimestamp = currentTimeUpperBound + Math.Max(
292-
minRate.GracePeriod.Ticks,
298+
minRate.GracePeriod.ToTicks(_tickFrequency),
293299
ticksToCompleteWriteAtMinRate);
294300

295301
// Don't penalize a connection for completing previous writes more quickly than required.
@@ -316,7 +322,7 @@ void IConnectionTimeoutFeature.SetTimeout(TimeSpan timeSpan)
316322
throw new InvalidOperationException(CoreStrings.ConcurrentTimeoutsNotSupported);
317323
}
318324

319-
SetTimeout(timeSpan.Ticks, TimeoutReason.TimeoutFeature);
325+
SetTimeout(timeSpan.ToTicks(_tickFrequency), TimeoutReason.TimeoutFeature);
320326
}
321327

322328
void IConnectionTimeoutFeature.ResetTimeout(TimeSpan timeSpan)
@@ -326,13 +332,13 @@ void IConnectionTimeoutFeature.ResetTimeout(TimeSpan timeSpan)
326332
throw new ArgumentException(CoreStrings.PositiveFiniteTimeSpanRequired, nameof(timeSpan));
327333
}
328334

329-
ResetTimeout(timeSpan.Ticks, TimeoutReason.TimeoutFeature);
335+
ResetTimeout(timeSpan.ToTicks(_tickFrequency), TimeoutReason.TimeoutFeature);
330336
}
331337

332338
public long GetResponseDrainDeadline(long ticks, MinDataRate minRate)
333339
{
334340
// On grace period overflow, use max value.
335-
var gracePeriod = ticks + minRate.GracePeriod.Ticks;
341+
var gracePeriod = ticks + minRate.GracePeriod.ToTicks(_tickFrequency);
336342
gracePeriod = gracePeriod >= 0 ? gracePeriod : long.MaxValue;
337343

338344
return Math.Max(_writeTimingTimeoutTimestamp, gracePeriod);

src/Servers/Kestrel/Core/test/StartLineTests.cs

+1-1
Original file line numberDiff line numberDiff line change
@@ -532,7 +532,7 @@ public StartLineTests()
532532
serviceContext: serviceContext,
533533
connectionContext: Mock.Of<ConnectionContext>(),
534534
transport: Transport,
535-
timeoutControl: new TimeoutControl(timeoutHandler: null),
535+
timeoutControl: new TimeoutControl(timeoutHandler: null, new MockTimeProvider().TimestampFrequency),
536536
memoryPool: MemoryPool,
537537
connectionFeatures: new FeatureCollection());
538538

0 commit comments

Comments
 (0)