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

Commit 697554b

Browse files
committed
[Wip] Add timeouts
1 parent a696eb8 commit 697554b

File tree

3 files changed

+143
-19
lines changed

3 files changed

+143
-19
lines changed

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

Lines changed: 129 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,13 @@ public abstract partial class Frame : FrameContext, IFrameControl
3939
private static readonly byte[] _bytesDate = Encoding.ASCII.GetBytes("Date: ");
4040
private static readonly byte[] _bytesEndHeaders = Encoding.ASCII.GetBytes("\r\n\r\n");
4141

42+
private static readonly TimerCallback _timeoutRequest = (o) => ((Frame)o).TimeoutRequest();
43+
protected readonly Timer _timeout;
44+
/* TODO: Need to move to config */
45+
private static readonly int _keepAliveTimout = 160000;
46+
private static readonly int _headerTimeout = 160000;
47+
private static readonly int _bodyTimeout = 160000;
48+
4249
private readonly object _onStartingSync = new Object();
4350
private readonly object _onCompletedSync = new Object();
4451
protected readonly FrameRequestHeaders _requestHeaders = new FrameRequestHeaders();
@@ -50,8 +57,9 @@ public abstract partial class Frame : FrameContext, IFrameControl
5057

5158
private bool _requestProcessingStarted;
5259
private Task _requestProcessingTask;
53-
protected volatile bool _requestProcessingStopping; // volatile, see: https://msdn.microsoft.com/en-us/library/x13ttww7.aspx
54-
protected volatile bool _requestAborted;
60+
// volatile, see: https://msdn.microsoft.com/en-us/library/x13ttww7.aspx
61+
// enum doesn't work with Interlocked
62+
protected volatile int _frameState;
5563
protected CancellationTokenSource _abortedCts;
5664
protected CancellationToken? _manuallySetRequestAbortToken;
5765

@@ -87,6 +95,9 @@ public Frame(ConnectionContext context,
8795
_prepareRequest = prepareRequest;
8896
_pathBase = context.ServerAddress.PathBase;
8997

98+
_frameState = FrameState.Waiting;
99+
_timeout = new Timer(_timeoutRequest, this, _keepAliveTimout, Timeout.Infinite);
100+
90101
FrameControl = this;
91102
Reset();
92103
}
@@ -155,7 +166,7 @@ public CancellationToken RequestAborted
155166
var cts = _abortedCts;
156167
return
157168
cts != null ? cts.Token :
158-
_requestAborted ? new CancellationToken(true) :
169+
_frameState == FrameState.Aborted ? new CancellationToken(true) :
159170
RequestAbortedSource.Token;
160171
}
161172
set
@@ -173,7 +184,7 @@ private CancellationTokenSource RequestAbortedSource
173184
// Get the abort token, lazily-initializing it if necessary.
174185
// Make sure it's canceled if an abort request already came in.
175186
var cts = LazyInitializer.EnsureInitialized(ref _abortedCts, () => new CancellationTokenSource());
176-
if (_requestAborted)
187+
if (_frameState == FrameState.Aborted)
177188
{
178189
cts.Cancel();
179190
}
@@ -185,8 +196,91 @@ public bool HasResponseStarted
185196
get { return _responseStarted; }
186197
}
187198

199+
private void TimeoutRequest()
200+
{
201+
if (TransitionToState(FrameState.Timeout) == FrameState.Timeout)
202+
{
203+
SocketInput?.AbortAwaiting();
204+
}
205+
}
206+
protected int TransitionToState(int state)
207+
{
208+
#pragma warning disable CS0420 // A reference to a volatile field will not be treated as volatile
209+
int prevState = Volatile.Read(ref _frameState);
210+
#pragma warning restore CS0420
211+
212+
switch (state)
213+
{
214+
case FrameState.Waiting:
215+
if (prevState == FrameState.Waiting) return FrameState.Waiting;
216+
// can only transition to Waiting from ReadingBody
217+
prevState = Interlocked.CompareExchange(ref _frameState, FrameState.Waiting, FrameState.ReadingBody);
218+
if (prevState == FrameState.ReadingBody)
219+
{
220+
// only reset timer on transition into this state
221+
_timeout.Change(_keepAliveTimout, Timeout.Infinite);
222+
return FrameState.Waiting;
223+
}
224+
return prevState;
225+
case FrameState.ReadingHeaders:
226+
if (prevState == FrameState.ReadingHeaders) return FrameState.ReadingHeaders;
227+
// can only transition to ReadingHeaders from Waiting
228+
prevState = Interlocked.CompareExchange(ref _frameState, FrameState.ReadingHeaders, FrameState.Waiting);
229+
if (prevState == FrameState.Waiting)
230+
{
231+
// only reset timer on transition into this state
232+
_timeout.Change(_headerTimeout, Timeout.Infinite);
233+
return FrameState.ReadingHeaders;
234+
}
235+
return prevState;
236+
case FrameState.ReadingBody:
237+
/* TODO Need to plumb in SocketInput reads */
238+
if (prevState == FrameState.ReadingBody)
239+
{
240+
// reset timer on each read
241+
_timeout.Change(_bodyTimeout, Timeout.Infinite);
242+
return FrameState.ReadingBody;
243+
}
244+
// can only transition to ReadingBody from ReadingHeaders
245+
prevState = Interlocked.CompareExchange(ref _frameState, FrameState.ReadingBody, FrameState.ReadingHeaders);
246+
if (prevState == FrameState.ReadingHeaders)
247+
{
248+
// only reset timer if state correct
249+
_timeout.Change(_headerTimeout, Timeout.Infinite);
250+
return FrameState.ReadingBody;
251+
}
252+
return prevState;
253+
case FrameState.Stopping:
254+
// marker state, can't transition into it.
255+
throw new InvalidOperationException();
256+
case FrameState.Timeout:
257+
if (prevState >= FrameState.Timeout) return prevState;
258+
// can transition to Timeout from states below it
259+
do
260+
{
261+
prevState = Interlocked.CompareExchange(ref _frameState, FrameState.Timeout, prevState);
262+
} while (prevState < FrameState.Timeout);
263+
return prevState < FrameState.Timeout ? FrameState.Timeout : prevState;
264+
case FrameState.Stopped:
265+
if (prevState >= FrameState.Stopped) return prevState;
266+
// can transition to Stopped from states below it
267+
do
268+
{
269+
prevState = Interlocked.CompareExchange(ref _frameState, FrameState.Stopped, prevState);
270+
} while (prevState < FrameState.Stopped);
271+
return prevState < FrameState.Stopped ? FrameState.Stopped : prevState;
272+
case FrameState.Aborted:
273+
// can transition to Aborted from any state
274+
return (_frameState = FrameState.Aborted);
275+
default:
276+
throw new InvalidOperationException();
277+
}
278+
}
279+
188280
public void Reset()
189281
{
282+
TransitionToState(FrameState.Waiting);
283+
190284
_onStarting = null;
191285
_onCompleted = null;
192286

@@ -273,10 +367,8 @@ public void Start()
273367
/// </summary>
274368
public Task Stop()
275369
{
276-
if (!_requestProcessingStopping)
277-
{
278-
_requestProcessingStopping = true;
279-
}
370+
TransitionToState(FrameState.Stopped);
371+
280372
return _requestProcessingTask ?? TaskUtilities.CompletedTask;
281373
}
282374

@@ -285,8 +377,7 @@ public Task Stop()
285377
/// </summary>
286378
public void Abort()
287379
{
288-
_requestProcessingStopping = true;
289-
_requestAborted = true;
380+
TransitionToState(FrameState.Aborted);
290381

291382
_requestBody?.Abort();
292383
_responseBody?.Abort();
@@ -573,7 +664,7 @@ protected Task ProduceEnd()
573664
if (_responseStarted)
574665
{
575666
// We can no longer respond with a 500, so we simply close the connection.
576-
_requestProcessingStopping = true;
667+
TransitionToState(FrameState.Stopped);
577668
return TaskUtilities.CompletedTask;
578669
}
579670
else
@@ -589,6 +680,12 @@ protected Task ProduceEnd()
589680

590681
if (!_responseStarted)
591682
{
683+
if (_frameState > FrameState.Stopping && _frameState < FrameState.Stopped)
684+
{
685+
// State is status code
686+
StatusCode = _frameState;
687+
ReasonPhrase = null;
688+
}
592689
return ProduceEndAwaited();
593690
}
594691

@@ -701,6 +798,12 @@ protected bool TakeStartLine(SocketInput input)
701798
{
702799
return false;
703800
}
801+
802+
if (TransitionToState(FrameState.ReadingHeaders) != FrameState.ReadingHeaders)
803+
{
804+
return false;
805+
}
806+
704807
var method = begin.GetAsciiString(scan);
705808

706809
scan.Take();
@@ -946,5 +1049,20 @@ private enum HttpVersionType
9461049
Http1_0 = 0,
9471050
Http1_1 = 1
9481051
}
1052+
1053+
// enum doesn't work with Interlocked
1054+
protected class FrameState
1055+
{
1056+
public const int Waiting = 0;
1057+
public const int ReadingHeaders = 1;
1058+
public const int ReadingBody = 2;
1059+
// Do not change order of these with out changing comparision tests
1060+
public const int Stopping = 99;
1061+
// States are status codes
1062+
public const int Timeout = 408;
1063+
// Other final states
1064+
public const int Stopped = 1000;
1065+
public const int Aborted = 1001;
1066+
}
9491067
}
9501068
}

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

Lines changed: 9 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -40,9 +40,9 @@ public override async Task RequestProcessingAsync()
4040
{
4141
try
4242
{
43-
while (!_requestProcessingStopping)
43+
while (_frameState == FrameState.Waiting)
4444
{
45-
while (!_requestProcessingStopping && !TakeStartLine(SocketInput))
45+
while (_frameState < FrameState.Stopping && !TakeStartLine(SocketInput))
4646
{
4747
if (SocketInput.RemoteIntakeFin)
4848
{
@@ -51,7 +51,7 @@ public override async Task RequestProcessingAsync()
5151
await SocketInput;
5252
}
5353

54-
while (!_requestProcessingStopping && !TakeMessageHeaders(SocketInput, _requestHeaders))
54+
while (_frameState < FrameState.Stopping && !TakeMessageHeaders(SocketInput, _requestHeaders))
5555
{
5656
if (SocketInput.RemoteIntakeFin)
5757
{
@@ -60,7 +60,7 @@ public override async Task RequestProcessingAsync()
6060
await SocketInput;
6161
}
6262

63-
if (!_requestProcessingStopping)
63+
if (TransitionToState(FrameState.ReadingBody) == FrameState.ReadingBody)
6464
{
6565
var messageBody = MessageBody.For(HttpVersion, _requestHeaders, this);
6666
_keepAlive = messageBody.RequestKeepAlive;
@@ -100,8 +100,8 @@ public override async Task RequestProcessingAsync()
100100

101101
_application.DisposeContext(context, _applicationException);
102102

103-
// If _requestAbort is set, the connection has already been closed.
104-
if (!_requestAborted)
103+
// If Aborted, the connection has already been closed.
104+
if (_frameState != FrameState.Aborted)
105105
{
106106
await ProduceEnd();
107107

@@ -133,10 +133,11 @@ public override async Task RequestProcessingAsync()
133133
{
134134
try
135135
{
136+
_timeout.Dispose();
136137
_abortedCts = null;
137138

138-
// If _requestAborted is set, the connection has already been closed.
139-
if (!_requestAborted)
139+
// If Aborted, the connection has already been closed.
140+
if (_frameState != FrameState.Aborted)
140141
{
141142
// Inform client no more data will ever arrive
142143
ConnectionControl.End(ProduceEndType.SocketShutdownSend);

src/Microsoft.AspNet.Server.Kestrel/Infrastructure/KestrelThread.cs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -245,6 +245,11 @@ private void ThreadStart(object parameter)
245245
}
246246
catch (Exception ex)
247247
{
248+
#if DEBUG
249+
// Output error in debug for Tests, which do not support application lifetime
250+
Console.WriteLine(nameof(KestrelThread) + " Exception");
251+
Console.WriteLine(ex);
252+
#endif
248253
_closeError = ExceptionDispatchInfo.Capture(ex);
249254
// Request shutdown so we can rethrow this exception
250255
// in Stop which should be observable.

0 commit comments

Comments
 (0)