Merge branch 'tenor/speed-up-date' into dev
This commit is contained in:
commit
10490888d3
|
|
@ -22,10 +22,11 @@ namespace Microsoft.AspNet.Server.Kestrel.Http
|
|||
private readonly byte[] _dateBytes0 = Encoding.ASCII.GetBytes("\r\nDate: DDD, dd mmm yyyy hh:mm:ss GMT");
|
||||
private readonly byte[] _dateBytes1 = Encoding.ASCII.GetBytes("\r\nDate: DDD, dd mmm yyyy hh:mm:ss GMT");
|
||||
private object _timerLocker = new object();
|
||||
private bool _isDisposed = false;
|
||||
private bool _hadRequestsSinceLastTimerTick = false;
|
||||
private volatile bool _isDisposed = false;
|
||||
private volatile bool _hadRequestsSinceLastTimerTick = false;
|
||||
private Timer _dateValueTimer;
|
||||
private DateTimeOffset _lastRequestSeen = DateTimeOffset.MinValue;
|
||||
private long _lastRequestSeenTicks;
|
||||
private volatile bool _timerIsRunning;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="DateHeaderValueManager"/> class.
|
||||
|
|
@ -36,7 +37,6 @@ namespace Microsoft.AspNet.Server.Kestrel.Http
|
|||
timeWithoutRequestsUntilIdle: TimeSpan.FromSeconds(10),
|
||||
timerInterval: TimeSpan.FromSeconds(1))
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
// Internal for testing
|
||||
|
|
@ -48,6 +48,7 @@ namespace Microsoft.AspNet.Server.Kestrel.Http
|
|||
_systemClock = systemClock;
|
||||
_timeWithoutRequestsUntilIdle = timeWithoutRequestsUntilIdle;
|
||||
_timerInterval = timerInterval;
|
||||
_dateValueTimer = new Timer(TimerLoop, state: null, dueTime: Timeout.Infinite, period: Timeout.Infinite);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
|
@ -57,18 +58,15 @@ namespace Microsoft.AspNet.Server.Kestrel.Http
|
|||
/// <returns>The value.</returns>
|
||||
public virtual string GetDateHeaderValue()
|
||||
{
|
||||
PumpTimer();
|
||||
|
||||
// See https://msdn.microsoft.com/en-us/library/az4se3k1(v=vs.110).aspx#RFC1123 for info on the format
|
||||
// string used here.
|
||||
// The null-coalesce here is to protect against returning null after Dispose() is called, at which
|
||||
// point _dateValue will be null forever after.
|
||||
return _dateValue ?? _systemClock.UtcNow.ToString(Constants.RFC1123DateFormat);
|
||||
_hadRequestsSinceLastTimerTick = true;
|
||||
PrepareDateValues();
|
||||
return _dateValue;
|
||||
}
|
||||
|
||||
public byte[] GetDateHeaderValueBytes()
|
||||
{
|
||||
PumpTimer();
|
||||
_hadRequestsSinceLastTimerTick = true;
|
||||
PrepareDateValues();
|
||||
return _activeDateBytes ? _dateBytes0 : _dateBytes1;
|
||||
}
|
||||
|
||||
|
|
@ -77,83 +75,112 @@ namespace Microsoft.AspNet.Server.Kestrel.Http
|
|||
/// </summary>
|
||||
public void Dispose()
|
||||
{
|
||||
lock (_timerLocker)
|
||||
if (!_isDisposed)
|
||||
{
|
||||
DisposeTimer();
|
||||
|
||||
_isDisposed = true;
|
||||
|
||||
lock (_timerLocker)
|
||||
{
|
||||
if (_dateValueTimer != null)
|
||||
{
|
||||
_timerIsRunning = false;
|
||||
_dateValueTimer.Dispose();
|
||||
_dateValueTimer = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void PumpTimer()
|
||||
/// <summary>
|
||||
/// Starts the timer
|
||||
/// </summary>
|
||||
private void StartTimer()
|
||||
{
|
||||
_hadRequestsSinceLastTimerTick = true;
|
||||
var now = _systemClock.UtcNow;
|
||||
SetDateValues(now);
|
||||
|
||||
// If we're already disposed we don't care about starting the timer again. This avoids us having to worry
|
||||
// about requests in flight during dispose (not that that should actually happen) as those will just get
|
||||
// SystemClock.UtcNow (aka "the slow way").
|
||||
if (!_isDisposed && _dateValueTimer == null)
|
||||
if (!_isDisposed)
|
||||
{
|
||||
lock (_timerLocker)
|
||||
{
|
||||
if (!_isDisposed && _dateValueTimer == null)
|
||||
if (!_timerIsRunning && _dateValueTimer != null)
|
||||
{
|
||||
// Immediately assign the date value and start the timer again. We assign the value immediately
|
||||
// here as the timer won't fire until the timer interval has passed and we want a value assigned
|
||||
// inline now to serve requests that occur in the meantime.
|
||||
_dateValue = _systemClock.UtcNow.ToString(Constants.RFC1123DateFormat);
|
||||
Encoding.ASCII.GetBytes(_dateValue, 0, _dateValue.Length, !_activeDateBytes ? _dateBytes0 : _dateBytes1, "\r\nDate: ".Length);
|
||||
_activeDateBytes = !_activeDateBytes;
|
||||
_dateValueTimer = new Timer(UpdateDateValue, state: null, dueTime: _timerInterval, period: _timerInterval);
|
||||
_timerIsRunning = true;
|
||||
_dateValueTimer.Change(_timerInterval, _timerInterval);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Stops the timer
|
||||
/// </summary>
|
||||
private void StopTimer()
|
||||
{
|
||||
if (!_isDisposed)
|
||||
{
|
||||
lock (_timerLocker)
|
||||
{
|
||||
if (_dateValueTimer != null)
|
||||
{
|
||||
_timerIsRunning = false;
|
||||
_dateValueTimer.Change(Timeout.Infinite, Timeout.Infinite);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Called by the Timer (background) thread
|
||||
private void UpdateDateValue(object state)
|
||||
private void TimerLoop(object state)
|
||||
{
|
||||
var now = _systemClock.UtcNow;
|
||||
|
||||
// See http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.18 for required format of Date header
|
||||
_dateValue = now.ToString(Constants.RFC1123DateFormat);
|
||||
Encoding.ASCII.GetBytes(_dateValue, 0, _dateValue.Length, !_activeDateBytes ? _dateBytes0 : _dateBytes1, "\r\nDate: ".Length);
|
||||
_activeDateBytes = !_activeDateBytes;
|
||||
SetDateValues(now);
|
||||
|
||||
if (_hadRequestsSinceLastTimerTick)
|
||||
{
|
||||
// We served requests since the last tick, reset the flag and return as we're still active
|
||||
_hadRequestsSinceLastTimerTick = false;
|
||||
_lastRequestSeen = now;
|
||||
Interlocked.Exchange(ref _lastRequestSeenTicks, now.Ticks);
|
||||
return;
|
||||
}
|
||||
|
||||
// No requests since the last timer tick, we need to check if we're beyond the idle threshold
|
||||
var timeSinceLastRequestSeen = now - _lastRequestSeen;
|
||||
if (timeSinceLastRequestSeen >= _timeWithoutRequestsUntilIdle)
|
||||
if ((now.Ticks - Interlocked.Read(ref _lastRequestSeenTicks)) >= _timeWithoutRequestsUntilIdle.Ticks)
|
||||
{
|
||||
// No requests since idle threshold so stop the timer if it's still running
|
||||
if (_dateValueTimer != null)
|
||||
StopTimer();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Starts the timer if it's turned off, or sets the datevalues to the current time if disposed.
|
||||
/// </summary>
|
||||
private void PrepareDateValues()
|
||||
{
|
||||
if (_isDisposed)
|
||||
{
|
||||
SetDateValues(_systemClock.UtcNow);
|
||||
}
|
||||
else
|
||||
{
|
||||
if (!_timerIsRunning)
|
||||
{
|
||||
lock (_timerLocker)
|
||||
{
|
||||
if (_dateValueTimer != null)
|
||||
{
|
||||
DisposeTimer();
|
||||
}
|
||||
}
|
||||
StartTimer();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void DisposeTimer()
|
||||
/// <summary>
|
||||
/// Sets date values from a provided ticks value
|
||||
/// </summary>
|
||||
/// <param name="value">A DateTimeOffset value</param>
|
||||
private void SetDateValues(DateTimeOffset value)
|
||||
{
|
||||
if (_dateValueTimer != null)
|
||||
{
|
||||
_dateValueTimer.Dispose();
|
||||
_dateValueTimer = null;
|
||||
_dateValue = null;
|
||||
}
|
||||
// See http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.18 for required format of Date header
|
||||
_dateValue = value.ToString(Constants.RFC1123DateFormat);
|
||||
Encoding.ASCII.GetBytes(_dateValue, 0, _dateValue.Length, !_activeDateBytes ? _dateBytes0 : _dateBytes1, "\r\nDate: ".Length);
|
||||
_activeDateBytes = !_activeDateBytes;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue