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

Commit 16a7bda

Browse files
committed
[Wip] Add timeouts
1 parent 849ff20 commit 16a7bda

File tree

13 files changed

+317
-35
lines changed

13 files changed

+317
-35
lines changed

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

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,6 @@
1111
using System.Threading.Tasks;
1212
using Microsoft.AspNet.Http;
1313
using Microsoft.AspNet.Http.Features;
14-
using Microsoft.Extensions.Logging;
1514
using Microsoft.Extensions.Primitives;
1615

1716
namespace Microsoft.AspNet.Server.Kestrel.Http
@@ -316,7 +315,11 @@ async Task<Stream> IHttpUpgradeFeature.UpgradeAsync()
316315

317316
await ProduceStartAndFireOnStarting(immediate: true);
318317

319-
return DuplexStream;
318+
if (TransitionToState(FrameState.UpgradedRequest) == FrameState.UpgradedRequest)
319+
{
320+
return DuplexStream;
321+
}
322+
throw new IOException("Failed to upgrade request");
320323
}
321324

322325
IEnumerator<KeyValuePair<Type, object>> IEnumerable<KeyValuePair<Type, object>>.GetEnumerator() => FastEnumerable().GetEnumerator();
Lines changed: 143 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,143 @@
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.Diagnostics;
6+
using System.Threading;
7+
8+
namespace Microsoft.AspNet.Server.Kestrel.Http
9+
{
10+
public partial class Frame
11+
{
12+
private static readonly TimerCallback _timeoutRequest = (o) => ((Frame)o).TimeoutRequest();
13+
protected readonly Timer _timeout;
14+
// enum doesn't work with Interlocked
15+
protected int _frameState;
16+
17+
private void TimeoutRequest()
18+
{
19+
// Don't abort if debugging
20+
if (!Debugger.IsAttached && TransitionToState(FrameState.Timeout) == FrameState.Timeout)
21+
{
22+
Abort();
23+
}
24+
}
25+
26+
protected int TransitionToState(int state)
27+
{
28+
int prevState = Volatile.Read(ref _frameState);
29+
30+
switch (state)
31+
{
32+
case FrameState.Waiting:
33+
return TransitionToWaiting(prevState);
34+
case FrameState.ReadingHeaders:
35+
if (prevState == FrameState.ReadingHeaders) return FrameState.ReadingHeaders;
36+
// can only transition to ReadingHeaders from Waiting
37+
prevState = Interlocked.CompareExchange(ref _frameState, FrameState.ReadingHeaders, FrameState.Waiting);
38+
if (prevState == FrameState.Waiting)
39+
{
40+
// only reset timer on transition into this state
41+
_timeout.Change((int)Settings.HeadersCompleteTimeout.TotalMilliseconds, Timeout.Infinite);
42+
return FrameState.ReadingHeaders;
43+
}
44+
break;
45+
case FrameState.ExecutingRequest:
46+
// can only transition to ExecutingRequest from ReadingHeaders
47+
prevState = Interlocked.CompareExchange(ref _frameState, FrameState.ExecutingRequest, FrameState.ReadingHeaders);
48+
if (prevState == FrameState.ReadingHeaders)
49+
{
50+
// only reset timer if state correct
51+
_timeout.Change((int)Settings.ExecutionTimeout.TotalMilliseconds, Timeout.Infinite);
52+
return FrameState.ExecutingRequest;
53+
}
54+
break;
55+
case FrameState.UpgradedRequest:
56+
// can only transition to UpgradedRequest from ExecutingRequest
57+
prevState = Interlocked.CompareExchange(ref _frameState, FrameState.UpgradedRequest, FrameState.ExecutingRequest);
58+
if (prevState == FrameState.ExecutingRequest)
59+
{
60+
// switch off timer for upgraded request; upgraded pipeline should handle its own timeouts
61+
_timeout.Change(Timeout.Infinite, Timeout.Infinite);
62+
return FrameState.UpgradedRequest;
63+
}
64+
break;
65+
case FrameState.Stopping:
66+
// marker state, can't transition into it.
67+
throw new InvalidOperationException();
68+
case FrameState.Timeout:
69+
if (prevState >= FrameState.Timeout) return prevState;
70+
// can transition to Timeout from states below it
71+
do
72+
{
73+
prevState = Interlocked.CompareExchange(ref _frameState, FrameState.Timeout, prevState);
74+
} while (prevState < FrameState.Timeout);
75+
return prevState < FrameState.Timeout ? FrameState.Timeout : prevState;
76+
case FrameState.Stopped:
77+
if (prevState >= FrameState.Stopped) return prevState;
78+
// can transition to Stopped from states below it
79+
do
80+
{
81+
prevState = Interlocked.CompareExchange(ref _frameState, FrameState.Stopped, prevState);
82+
} while (prevState < FrameState.Stopped);
83+
return prevState < FrameState.Stopped ? FrameState.Stopped : prevState;
84+
case FrameState.Aborted:
85+
// can transition to Aborted from any state
86+
return (_frameState = FrameState.Aborted);
87+
}
88+
return prevState;
89+
}
90+
91+
private int TransitionToWaiting(int prevState)
92+
{
93+
switch (prevState)
94+
{
95+
case FrameState.ExecutingRequest:
96+
prevState = Interlocked.CompareExchange(ref _frameState, FrameState.Waiting, FrameState.ExecutingRequest);
97+
if (prevState == FrameState.ExecutingRequest)
98+
{
99+
// only reset timer on transition into this state
100+
_timeout.Change((int)Settings.KeepAliveTimeout.TotalMilliseconds, Timeout.Infinite);
101+
return FrameState.Waiting;
102+
}
103+
break;
104+
case FrameState.UpgradedRequest:
105+
prevState = Interlocked.CompareExchange(ref _frameState, FrameState.Waiting, FrameState.UpgradedRequest);
106+
if (prevState == FrameState.UpgradedRequest)
107+
{
108+
// only reset timer on transition into this state
109+
_timeout.Change((int)Settings.KeepAliveTimeout.TotalMilliseconds, Timeout.Infinite);
110+
return FrameState.Waiting;
111+
}
112+
break;
113+
case FrameState.NotStarted:
114+
prevState = Interlocked.CompareExchange(ref _frameState, FrameState.Waiting, FrameState.NotStarted);
115+
if (prevState == FrameState.NotStarted)
116+
{
117+
// only reset timer on transition into this state
118+
_timeout.Change((int)Settings.KeepAliveTimeout.TotalMilliseconds, Timeout.Infinite);
119+
return FrameState.Waiting;
120+
}
121+
break;
122+
}
123+
return prevState;
124+
}
125+
126+
// enum doesn't work with Interlocked
127+
protected class FrameState
128+
{
129+
public const int NotStarted = -1;
130+
public const int Waiting = 0;
131+
public const int ReadingHeaders = 1;
132+
public const int ExecutingRequest = 2;
133+
public const int UpgradedRequest = 3;
134+
// Do not change order of these with out changing comparision tests
135+
public const int Stopping = 99;
136+
// States are status codes
137+
public const int Timeout = 408;
138+
// Other final states
139+
public const int Stopped = 1000;
140+
public const int Aborted = 1001;
141+
}
142+
}
143+
}

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

Lines changed: 46 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -54,11 +54,8 @@ public abstract partial class Frame : FrameContext, IFrameControl
5454
protected List<KeyValuePair<Func<object, Task>, object>> _onStarting;
5555

5656
protected List<KeyValuePair<Func<object, Task>, object>> _onCompleted;
57-
58-
private bool _requestProcessingStarted;
57+
5958
private Task _requestProcessingTask;
60-
protected volatile bool _requestProcessingStopping; // volatile, see: https://msdn.microsoft.com/en-us/library/x13ttww7.aspx
61-
protected volatile bool _requestAborted;
6259
protected CancellationTokenSource _abortedCts;
6360
protected CancellationToken? _manuallySetRequestAbortToken;
6461

@@ -101,6 +98,9 @@ public Frame(ConnectionContext context,
10198
_duplexStream = new FrameDuplexStream(_requestBody, _responseBody);
10299
}
103100

101+
_frameState = FrameState.NotStarted;
102+
_timeout = new Timer(_timeoutRequest, this, Timeout.Infinite, Timeout.Infinite);
103+
104104
FrameControl = this;
105105
Reset();
106106
}
@@ -169,7 +169,7 @@ public CancellationToken RequestAborted
169169
var cts = _abortedCts;
170170
return
171171
cts != null ? cts.Token :
172-
_requestAborted ? new CancellationToken(true) :
172+
Volatile.Read(ref _frameState) == FrameState.Aborted ? new CancellationToken(true) :
173173
RequestAbortedSource.Token;
174174
}
175175
set
@@ -187,20 +187,26 @@ private CancellationTokenSource RequestAbortedSource
187187
// Get the abort token, lazily-initializing it if necessary.
188188
// Make sure it's canceled if an abort request already came in.
189189
var cts = LazyInitializer.EnsureInitialized(ref _abortedCts, () => new CancellationTokenSource());
190-
if (_requestAborted)
190+
if (Volatile.Read(ref _frameState) == FrameState.Aborted)
191191
{
192192
cts.Cancel();
193193
}
194194
return cts;
195195
}
196196
}
197+
197198
public bool HasResponseStarted
198199
{
199200
get { return _responseStarted; }
200201
}
201202

202-
public void Reset()
203+
public bool Reset()
203204
{
205+
if (TransitionToState(FrameState.Waiting) != FrameState.Waiting)
206+
{
207+
return false;
208+
}
209+
204210
_onStarting = null;
205211
_onCompleted = null;
206212

@@ -248,6 +254,8 @@ public void Reset()
248254

249255
_manuallySetRequestAbortToken = null;
250256
_abortedCts = null;
257+
258+
return true;
251259
}
252260

253261
public void ResetResponseHeaders()
@@ -266,9 +274,8 @@ public void ResetResponseHeaders()
266274
/// </summary>
267275
public void Start()
268276
{
269-
if (!_requestProcessingStarted)
277+
if (TransitionToState(FrameState.Waiting) == FrameState.Waiting)
270278
{
271-
_requestProcessingStarted = true;
272279
_requestProcessingTask =
273280
Task.Factory.StartNew(
274281
(o) => ((Frame)o).RequestProcessingAsync(),
@@ -287,10 +294,8 @@ public void Start()
287294
/// </summary>
288295
public Task Stop()
289296
{
290-
if (!_requestProcessingStopping)
291-
{
292-
_requestProcessingStopping = true;
293-
}
297+
TransitionToState(FrameState.Stopped);
298+
294299
return _requestProcessingTask ?? TaskUtilities.CompletedTask;
295300
}
296301

@@ -299,8 +304,7 @@ public Task Stop()
299304
/// </summary>
300305
public void Abort()
301306
{
302-
_requestProcessingStopping = true;
303-
_requestAborted = true;
307+
TransitionToState(FrameState.Aborted);
304308

305309
_requestBody?.Abort();
306310
_responseBody?.Abort();
@@ -587,7 +591,7 @@ protected Task ProduceEnd()
587591
if (_responseStarted)
588592
{
589593
// We can no longer respond with a 500, so we simply close the connection.
590-
_requestProcessingStopping = true;
594+
TransitionToState(FrameState.Stopped);
591595
return TaskUtilities.CompletedTask;
592596
}
593597
else
@@ -600,9 +604,22 @@ protected Task ProduceEnd()
600604
}
601605
}
602606

603-
604607
if (!_responseStarted)
605608
{
609+
var frameState = Volatile.Read(ref _frameState);
610+
if (frameState > FrameState.Stopping)
611+
{
612+
if (frameState < FrameState.Stopped)
613+
{
614+
// State is status code
615+
StatusCode = _frameState;
616+
}
617+
else
618+
{
619+
StatusCode = 500;
620+
}
621+
ReasonPhrase = null;
622+
}
606623
return ProduceEndAwaited();
607624
}
608625

@@ -712,12 +729,23 @@ protected bool TakeStartLine(SocketInput input)
712729
{
713730
string method;
714731
var begin = scan;
715-
if (!begin.GetKnownMethod(ref scan,out method))
732+
if (begin.GetKnownMethod(ref scan, out method))
733+
{
734+
if (TransitionToState(FrameState.ReadingHeaders) != FrameState.ReadingHeaders)
735+
{
736+
return false;
737+
}
738+
}
739+
else
716740
{
717741
if (scan.Seek(ref _vectorSpaces) == -1)
718742
{
719743
return false;
720744
}
745+
if (TransitionToState(FrameState.ReadingHeaders) != FrameState.ReadingHeaders)
746+
{
747+
return false;
748+
}
721749
method = begin.GetAsciiString(scan);
722750
scan.Take();
723751
}

0 commit comments

Comments
 (0)