Merge branch 'tenor/speed-up-date' into dev

This commit is contained in:
Stephen Halter 2015-12-02 19:52:48 -08:00
commit 10490888d3
1 changed files with 79 additions and 52 deletions

View File

@ -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[] _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 readonly byte[] _dateBytes1 = Encoding.ASCII.GetBytes("\r\nDate: DDD, dd mmm yyyy hh:mm:ss GMT");
private object _timerLocker = new object(); private object _timerLocker = new object();
private bool _isDisposed = false; private volatile bool _isDisposed = false;
private bool _hadRequestsSinceLastTimerTick = false; private volatile bool _hadRequestsSinceLastTimerTick = false;
private Timer _dateValueTimer; private Timer _dateValueTimer;
private DateTimeOffset _lastRequestSeen = DateTimeOffset.MinValue; private long _lastRequestSeenTicks;
private volatile bool _timerIsRunning;
/// <summary> /// <summary>
/// Initializes a new instance of the <see cref="DateHeaderValueManager"/> class. /// Initializes a new instance of the <see cref="DateHeaderValueManager"/> class.
@ -36,7 +37,6 @@ namespace Microsoft.AspNet.Server.Kestrel.Http
timeWithoutRequestsUntilIdle: TimeSpan.FromSeconds(10), timeWithoutRequestsUntilIdle: TimeSpan.FromSeconds(10),
timerInterval: TimeSpan.FromSeconds(1)) timerInterval: TimeSpan.FromSeconds(1))
{ {
} }
// Internal for testing // Internal for testing
@ -48,6 +48,7 @@ namespace Microsoft.AspNet.Server.Kestrel.Http
_systemClock = systemClock; _systemClock = systemClock;
_timeWithoutRequestsUntilIdle = timeWithoutRequestsUntilIdle; _timeWithoutRequestsUntilIdle = timeWithoutRequestsUntilIdle;
_timerInterval = timerInterval; _timerInterval = timerInterval;
_dateValueTimer = new Timer(TimerLoop, state: null, dueTime: Timeout.Infinite, period: Timeout.Infinite);
} }
/// <summary> /// <summary>
@ -57,18 +58,15 @@ namespace Microsoft.AspNet.Server.Kestrel.Http
/// <returns>The value.</returns> /// <returns>The value.</returns>
public virtual string GetDateHeaderValue() public virtual string GetDateHeaderValue()
{ {
PumpTimer(); _hadRequestsSinceLastTimerTick = true;
PrepareDateValues();
// See https://msdn.microsoft.com/en-us/library/az4se3k1(v=vs.110).aspx#RFC1123 for info on the format return _dateValue;
// 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);
} }
public byte[] GetDateHeaderValueBytes() public byte[] GetDateHeaderValueBytes()
{ {
PumpTimer(); _hadRequestsSinceLastTimerTick = true;
PrepareDateValues();
return _activeDateBytes ? _dateBytes0 : _dateBytes1; return _activeDateBytes ? _dateBytes0 : _dateBytes1;
} }
@ -77,83 +75,112 @@ namespace Microsoft.AspNet.Server.Kestrel.Http
/// </summary> /// </summary>
public void Dispose() public void Dispose()
{ {
lock (_timerLocker) if (!_isDisposed)
{ {
DisposeTimer();
_isDisposed = true; _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 if (!_isDisposed)
// 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)
{ {
lock (_timerLocker) 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 _timerIsRunning = true;
// here as the timer won't fire until the timer interval has passed and we want a value assigned _dateValueTimer.Change(_timerInterval, _timerInterval);
// 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);
/// <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 // Called by the Timer (background) thread
private void UpdateDateValue(object state) private void TimerLoop(object state)
{ {
var now = _systemClock.UtcNow; var now = _systemClock.UtcNow;
// See http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.18 for required format of Date header SetDateValues(now);
_dateValue = now.ToString(Constants.RFC1123DateFormat);
Encoding.ASCII.GetBytes(_dateValue, 0, _dateValue.Length, !_activeDateBytes ? _dateBytes0 : _dateBytes1, "\r\nDate: ".Length);
_activeDateBytes = !_activeDateBytes;
if (_hadRequestsSinceLastTimerTick) if (_hadRequestsSinceLastTimerTick)
{ {
// We served requests since the last tick, reset the flag and return as we're still active // We served requests since the last tick, reset the flag and return as we're still active
_hadRequestsSinceLastTimerTick = false; _hadRequestsSinceLastTimerTick = false;
_lastRequestSeen = now; Interlocked.Exchange(ref _lastRequestSeenTicks, now.Ticks);
return; return;
} }
// No requests since the last timer tick, we need to check if we're beyond the idle threshold // No requests since the last timer tick, we need to check if we're beyond the idle threshold
var timeSinceLastRequestSeen = now - _lastRequestSeen; if ((now.Ticks - Interlocked.Read(ref _lastRequestSeenTicks)) >= _timeWithoutRequestsUntilIdle.Ticks)
if (timeSinceLastRequestSeen >= _timeWithoutRequestsUntilIdle)
{ {
// No requests since idle threshold so stop the timer if it's still running // 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) StartTimer();
{
if (_dateValueTimer != null)
{
DisposeTimer();
}
}
} }
} }
} }
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) // See http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.18 for required format of Date header
{ _dateValue = value.ToString(Constants.RFC1123DateFormat);
_dateValueTimer.Dispose(); Encoding.ASCII.GetBytes(_dateValue, 0, _dateValue.Length, !_activeDateBytes ? _dateBytes0 : _dateBytes1, "\r\nDate: ".Length);
_dateValueTimer = null; _activeDateBytes = !_activeDateBytes;
_dateValue = null;
}
} }
} }
} }