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

Commit bd8910d

Browse files
committed
[Wip] Add timeouts
1 parent d9f6ac7 commit bd8910d

22 files changed

+421
-55
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: 55 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -52,11 +52,8 @@ public abstract partial class Frame : FrameContext, IFrameControl
5252
protected List<KeyValuePair<Func<object, Task>, object>> _onStarting;
5353

5454
protected List<KeyValuePair<Func<object, Task>, object>> _onCompleted;
55-
56-
private bool _requestProcessingStarted;
55+
5756
private Task _requestProcessingTask;
58-
protected volatile bool _requestProcessingStopping; // volatile, see: https://msdn.microsoft.com/en-us/library/x13ttww7.aspx
59-
protected volatile bool _requestAborted;
6057
protected CancellationTokenSource _abortedCts;
6158
protected CancellationToken? _manuallySetRequestAbortToken;
6259

@@ -99,6 +96,9 @@ public Frame(ConnectionContext context,
9996
_duplexStream = new FrameDuplexStream(_requestBody, _responseBody);
10097
}
10198

99+
_frameState = FrameState.NotStarted;
100+
_timeout = new Timer(_timeoutRequest, this, Timeout.Infinite, Timeout.Infinite);
101+
102102
FrameControl = this;
103103
Reset();
104104
}
@@ -167,7 +167,7 @@ public CancellationToken RequestAborted
167167
var cts = _abortedCts;
168168
return
169169
cts != null ? cts.Token :
170-
_requestAborted ? new CancellationToken(true) :
170+
Volatile.Read(ref _frameState) == FrameState.Aborted ? new CancellationToken(true) :
171171
RequestAbortedSource.Token;
172172
}
173173
set
@@ -185,20 +185,26 @@ private CancellationTokenSource RequestAbortedSource
185185
// Get the abort token, lazily-initializing it if necessary.
186186
// Make sure it's canceled if an abort request already came in.
187187
var cts = LazyInitializer.EnsureInitialized(ref _abortedCts, () => new CancellationTokenSource());
188-
if (_requestAborted)
188+
if (Volatile.Read(ref _frameState) == FrameState.Aborted)
189189
{
190190
cts.Cancel();
191191
}
192192
return cts;
193193
}
194194
}
195+
195196
public bool HasResponseStarted
196197
{
197198
get { return _responseStarted; }
198199
}
199200

200-
public void Reset()
201+
public bool Reset()
201202
{
203+
if (TransitionToState(FrameState.Waiting) != FrameState.Waiting)
204+
{
205+
return false;
206+
}
207+
202208
_onStarting = null;
203209
_onCompleted = null;
204210

@@ -246,6 +252,8 @@ public void Reset()
246252

247253
_manuallySetRequestAbortToken = null;
248254
_abortedCts = null;
255+
256+
return true;
249257
}
250258

251259
public void ResetResponseHeaders()
@@ -264,9 +272,8 @@ public void ResetResponseHeaders()
264272
/// </summary>
265273
public void Start()
266274
{
267-
if (!_requestProcessingStarted)
275+
if (TransitionToState(FrameState.Waiting) == FrameState.Waiting)
268276
{
269-
_requestProcessingStarted = true;
270277
_requestProcessingTask =
271278
Task.Factory.StartNew(
272279
(o) => ((Frame)o).RequestProcessingAsync(),
@@ -285,10 +292,8 @@ public void Start()
285292
/// </summary>
286293
public Task Stop()
287294
{
288-
if (!_requestProcessingStopping)
289-
{
290-
_requestProcessingStopping = true;
291-
}
295+
TransitionToState(FrameState.Stopped);
296+
292297
return _requestProcessingTask ?? TaskUtilities.CompletedTask;
293298
}
294299

@@ -297,24 +302,28 @@ public Task Stop()
297302
/// </summary>
298303
public void Abort()
299304
{
300-
_requestProcessingStopping = true;
301-
_requestAborted = true;
305+
TransitionToState(FrameState.Aborted);
302306

303307
_requestBody?.Abort();
304308
_responseBody?.Abort();
305309

306310
try
307311
{
308-
ConnectionControl.End(ProduceEndType.SocketDisconnect);
309-
SocketInput.AbortAwaiting();
310-
RequestAbortedSource.Cancel();
312+
ConnectionControl?.End(ProduceEndType.SocketDisconnect);
313+
SocketInput?.AbortAwaiting();
311314
}
312315
catch (Exception ex)
313316
{
314-
Log.LogError("Abort", ex);
317+
Log?.LogError("Abort", ex);
315318
}
316319
finally
317320
{
321+
try
322+
{
323+
RequestAbortedSource.Cancel();
324+
}
325+
catch
326+
{ }
318327
_abortedCts = null;
319328
}
320329
}
@@ -552,7 +561,7 @@ protected Task ProduceEnd()
552561
if (_responseStarted)
553562
{
554563
// We can no longer respond with a 500, so we simply close the connection.
555-
_requestProcessingStopping = true;
564+
TransitionToState(FrameState.Stopped);
556565
return TaskUtilities.CompletedTask;
557566
}
558567
else
@@ -565,9 +574,22 @@ protected Task ProduceEnd()
565574
}
566575
}
567576

568-
569577
if (!_responseStarted)
570578
{
579+
var frameState = Volatile.Read(ref _frameState);
580+
if (frameState > FrameState.Stopping)
581+
{
582+
if (frameState < FrameState.Stopped)
583+
{
584+
// State is status code
585+
StatusCode = _frameState;
586+
}
587+
else
588+
{
589+
StatusCode = 500;
590+
}
591+
ReasonPhrase = null;
592+
}
571593
return ProduceEndAwaited();
572594
}
573595

@@ -677,12 +699,23 @@ protected bool TakeStartLine(SocketInput input)
677699
{
678700
string method;
679701
var begin = scan;
680-
if (!begin.GetKnownMethod(ref scan,out method))
702+
if (begin.GetKnownMethod(ref scan, out method))
703+
{
704+
if (TransitionToState(FrameState.ReadingHeaders) != FrameState.ReadingHeaders)
705+
{
706+
return false;
707+
}
708+
}
709+
else
681710
{
682711
if (scan.Seek(ref _vectorSpaces) == -1)
683712
{
684713
return false;
685714
}
715+
if (TransitionToState(FrameState.ReadingHeaders) != FrameState.ReadingHeaders)
716+
{
717+
return false;
718+
}
686719
method = begin.GetAsciiString(scan);
687720
scan.Take();
688721
}

0 commit comments

Comments
 (0)