Skip to content
This repository was archived by the owner on Dec 18, 2018. It is now read-only.

Commit 4da048e

Browse files
committed
Use a timer to generate the value for the Date header in responses:
- Doing it on each request is expensive - The Timer is started when the first request comes in and fires every second - Every request flips a bool so the Timer knows requests are coming in - The Timer stops itself after a period of no requests coming in (10 ticks) - #163
1 parent 355bc01 commit 4da048e

File tree

14 files changed

+131
-12
lines changed

14 files changed

+131
-12
lines changed

KestrelHttpServer.sln

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution
1212
build.cmd = build.cmd
1313
global.json = global.json
1414
makefile.shade = makefile.shade
15+
NuGet.Config = NuGet.Config
1516
EndProjectSection
1617
EndProject
1718
Project("{8BB2217D-0F2D-49D1-97BC-3654ED321F3B}") = "SampleApp", "samples\SampleApp\SampleApp.xproj", "{2C3CB3DC-EEBF-4F52-9E1C-4F2F972E76C3}"

src/Microsoft.AspNet.Server.Kestrel/Http/ConnectionContext.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ public ConnectionContext(ConnectionContext context) : base(context)
1818
SocketInput = context.SocketInput;
1919
SocketOutput = context.SocketOutput;
2020
ConnectionControl = context.ConnectionControl;
21+
DateHeaderValueManager = context.DateHeaderValueManager;
2122
}
2223

2324
public SocketInput SocketInput { get; set; }
Lines changed: 101 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,101 @@
1+
// Copyright (c) .NET Foundation. All rights reserved.
2+
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
3+
4+
using System;
5+
using System.Threading;
6+
7+
namespace Microsoft.AspNet.Server.Kestrel.Http
8+
{
9+
public class DateHeaderValueManager : IDisposable
10+
{
11+
private readonly TimeSpan _timeWithoutRequestsUntilIdle = TimeSpan.FromSeconds(10);
12+
private readonly TimeSpan _timerInterval = TimeSpan.FromSeconds(1);
13+
private readonly uint _timerTicksWithoutRequestsUntilIdle;
14+
15+
private volatile string _dateValue;
16+
private bool _isDisposed = false;
17+
private bool _hadRequestsSinceLastTimerTick = false;
18+
private Timer _dateValueTimer;
19+
private object _timerLocker = new object();
20+
private int _timerTicksSinceLastRequest;
21+
22+
public DateHeaderValueManager()
23+
{
24+
_timerTicksWithoutRequestsUntilIdle = (uint)(_timeWithoutRequestsUntilIdle.TotalMilliseconds / _timerInterval.TotalMilliseconds);
25+
}
26+
27+
public string GetDateHeaderValue()
28+
{
29+
PumpTimer();
30+
31+
return _dateValue ?? DateTime.UtcNow.ToString("r");
32+
}
33+
34+
public void Dispose()
35+
{
36+
lock (_timerLocker)
37+
{
38+
if (_dateValueTimer != null)
39+
{
40+
DisposeTimer();
41+
_isDisposed = true;
42+
}
43+
}
44+
}
45+
46+
private void PumpTimer()
47+
{
48+
_hadRequestsSinceLastTimerTick = true;
49+
50+
if (!_isDisposed && _dateValueTimer == null)
51+
{
52+
lock (_timerLocker)
53+
{
54+
if (!_isDisposed && _dateValueTimer == null)
55+
{
56+
// Immediately assign the date value and start the timer again
57+
_dateValue = DateTime.UtcNow.ToString("r");
58+
_dateValueTimer = new Timer(UpdateDateValue, state: null, dueTime: _timerInterval, period: _timerInterval);
59+
}
60+
}
61+
}
62+
}
63+
64+
private void UpdateDateValue(object state)
65+
{
66+
_dateValue = DateTime.UtcNow.ToString("r");
67+
68+
if (_hadRequestsSinceLastTimerTick)
69+
{
70+
// We served requests since the last tick, just return
71+
_hadRequestsSinceLastTimerTick = false;
72+
_timerTicksSinceLastRequest = 0;
73+
return;
74+
}
75+
76+
// No requests since the last timer tick
77+
_timerTicksSinceLastRequest++;
78+
if (_timerTicksSinceLastRequest == _timerTicksWithoutRequestsUntilIdle)
79+
{
80+
// No requests since idle threshold so stop the timer if it's still running
81+
if (_dateValueTimer != null)
82+
{
83+
lock (_timerLocker)
84+
{
85+
if (_dateValueTimer != null)
86+
{
87+
DisposeTimer();
88+
}
89+
}
90+
}
91+
}
92+
}
93+
94+
private void DisposeTimer()
95+
{
96+
_dateValueTimer.Dispose();
97+
_dateValueTimer = null;
98+
_dateValue = null;
99+
}
100+
}
101+
}

src/Microsoft.AspNet.Server.Kestrel/Http/Frame.cs

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ public class Frame : FrameContext, IFrameControl
2626
private bool _keepAlive;
2727
private bool _autoChunk;
2828
private readonly FrameRequestHeaders _requestHeaders = new FrameRequestHeaders();
29-
private readonly FrameResponseHeaders _responseHeaders = new FrameResponseHeaders();
29+
private readonly FrameResponseHeaders _responseHeaders;
3030

3131
private List<KeyValuePair<Func<object, Task>, object>> _onStarting;
3232
private List<KeyValuePair<Func<object, Task>, object>> _onCompleted;
@@ -38,6 +38,7 @@ public Frame(ConnectionContext context) : base(context)
3838
FrameControl = this;
3939
StatusCode = 200;
4040
RequestHeaders = _requestHeaders;
41+
_responseHeaders = new FrameResponseHeaders(DateHeaderValueManager.GetDateHeaderValue());
4142
ResponseHeaders = _responseHeaders;
4243
}
4344

@@ -388,8 +389,8 @@ public void ProduceEnd(Exception ex)
388389
// the app func has failed. https://github.com/aspnet/KestrelHttpServer/issues/43
389390
_onStarting = null;
390391

391-
ResponseHeaders = new FrameResponseHeaders();
392-
ResponseHeaders["Content-Length"] = new[] { "0" };
392+
ResponseHeaders = new FrameResponseHeaders(DateHeaderValueManager.GetDateHeaderValue());
393+
ResponseHeaders["Content-Length"] = "0";
393394
}
394395
}
395396

src/Microsoft.AspNet.Server.Kestrel/Http/FrameHeaders.Generated.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,10 +7,10 @@ namespace Microsoft.AspNet.Server.Kestrel.Http
77
{
88
public partial class FrameResponseHeaders
99
{
10-
public FrameResponseHeaders()
10+
public FrameResponseHeaders(string dateHeaderValue)
1111
{
1212
_Server = "Kestrel";
13-
_Date = DateTime.UtcNow.ToString("r");
13+
_Date = dateHeaderValue;
1414
_bits = 67108868L;
1515
}
1616
}

src/Microsoft.AspNet.Server.Kestrel/Http/FrameHeaders.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ StringValues IDictionary<string, StringValues>.this[string key]
2020
get
2121
{
2222
return GetValueFast(key);
23-
}
23+
}
2424

2525
set
2626
{

src/Microsoft.AspNet.Server.Kestrel/Http/Listener.cs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ public abstract class Listener : ListenerContext, IDisposable
1515
{
1616
protected Listener(ServiceContext serviceContext) : base(serviceContext)
1717
{
18+
1819
}
1920

2021
protected UvStreamHandle ListenSocket { get; private set; }
@@ -56,7 +57,7 @@ protected static void ConnectionCallback(UvStreamHandle stream, int status, Exce
5657
if (error != null)
5758
{
5859
listener.Log.LogError("Listener.ConnectionCallback ", error);
59-
}
60+
}
6061
else
6162
{
6263
listener.OnConnection(stream, status);

src/Microsoft.AspNet.Server.Kestrel/Http/ListenerContext.cs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ public ListenerContext() { }
1414
public ListenerContext(ServiceContext serviceContext)
1515
{
1616
Memory = serviceContext.Memory;
17+
DateHeaderValueManager = serviceContext.DateHeaderValueManager;
1718
Log = serviceContext.Log;
1819
}
1920

@@ -22,6 +23,7 @@ public ListenerContext(ListenerContext listenerContext)
2223
Thread = listenerContext.Thread;
2324
Application = listenerContext.Application;
2425
Memory = listenerContext.Memory;
26+
DateHeaderValueManager = listenerContext.DateHeaderValueManager;
2527
Log = listenerContext.Log;
2628
}
2729

@@ -31,6 +33,8 @@ public ListenerContext(ListenerContext listenerContext)
3133

3234
public IMemoryPool Memory { get; set; }
3335

36+
public DateHeaderValueManager DateHeaderValueManager { get; set; }
37+
3438
public IKestrelTrace Log { get; }
3539
}
3640
}

src/Microsoft.AspNet.Server.Kestrel/Http/ListenerSecondary.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ public abstract class ListenerSecondary : ListenerContext, IDisposable
1818
{
1919
protected ListenerSecondary(ServiceContext serviceContext) : base(serviceContext)
2020
{
21+
2122
}
2223

2324
UvPipeHandle DispatchPipe { get; set; }

src/Microsoft.AspNet.Server.Kestrel/KestrelEngine.cs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -75,13 +75,15 @@ private KestrelEngine(IApplicationShutdown appShutdownService, ILogger logger)
7575
{
7676
AppShutdown = appShutdownService,
7777
Memory = new MemoryPool(),
78+
DateHeaderValueManager = new DateHeaderValueManager(),
7879
Log = new KestrelTrace(logger)
7980
};
8081

8182
Threads = new List<KestrelThread>();
8283
}
8384

8485
public Libuv Libuv { get; private set; }
86+
8587
public List<KestrelThread> Threads { get; private set; }
8688

8789
public void Start(int count)
@@ -104,6 +106,8 @@ public void Dispose()
104106
thread.Stop(TimeSpan.FromSeconds(2.5));
105107
}
106108
Threads.Clear();
109+
110+
_serviceContext.DateHeaderValueManager.Dispose();
107111
}
108112

109113
public IDisposable CreateServer(string scheme, string host, int port, Func<Frame, Task> application)

src/Microsoft.AspNet.Server.Kestrel/ServiceContext.cs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,8 @@ public class ServiceContext
1313

1414
public IMemoryPool Memory { get; set; }
1515

16+
public DateHeaderValueManager DateHeaderValueManager { get; set; }
17+
1618
public IKestrelTrace Log { get; set; }
1719
}
1820
}

src/Microsoft.AspNet.Server.Kestrel/project.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,8 @@
3232
"System.Threading": "4.0.11-beta-*",
3333
"System.Threading.Tasks": "4.0.11-beta-*",
3434
"System.Threading.Thread": "4.0.0-beta-*",
35-
"System.Threading.ThreadPool": "4.0.10-beta-*"
35+
"System.Threading.ThreadPool": "4.0.10-beta-*",
36+
"System.Threading.Timer": "4.0.0"
3637
}
3738
}
3839
},

test/Microsoft.AspNet.Server.KestrelTests/FrameResponseHeadersTests.cs

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,8 @@ public class FrameResponseHeadersTests
1414
[Fact]
1515
public void InitialDictionaryContainsServerAndDate()
1616
{
17-
IDictionary<string, StringValues> headers = new FrameResponseHeaders();
17+
var dateHeaderValue = DateTime.UtcNow.ToString("r");
18+
IDictionary<string, StringValues> headers = new FrameResponseHeaders(dateHeaderValue);
1819

1920
Assert.Equal(2, headers.Count);
2021

@@ -36,7 +37,8 @@ public void InitialDictionaryContainsServerAndDate()
3637
[Fact]
3738
public void InitialEntriesCanBeCleared()
3839
{
39-
IDictionary<string, StringValues> headers = new FrameResponseHeaders();
40+
var dateHeaderValue = DateTime.UtcNow.ToString("r");
41+
IDictionary<string, StringValues> headers = new FrameResponseHeaders(dateHeaderValue);
4042

4143
headers.Clear();
4244

tools/Microsoft.AspNet.Server.Kestrel.GeneratedCode/KnownHeaders.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -173,10 +173,10 @@ namespace Microsoft.AspNet.Server.Kestrel.Http
173173
{{
174174
public partial class FrameResponseHeaders
175175
{{
176-
public FrameResponseHeaders()
176+
public FrameResponseHeaders(string dateHeaderValue)
177177
{{
178178
_Server = ""Kestrel"";
179-
_Date = DateTime.UtcNow.ToString(""r"");
179+
_Date = dateHeaderValue;
180180
_bits = {
181181
1L << responseHeaders.First(header => header.Name == "Server").Index |
182182
1L << responseHeaders.First(header => header.Name == "Date").Index

0 commit comments

Comments
 (0)