@@ -39,6 +39,13 @@ public abstract partial class Frame : FrameContext, IFrameControl
39
39
private static readonly byte [ ] _bytesDate = Encoding . ASCII . GetBytes ( "Date: " ) ;
40
40
private static readonly byte [ ] _bytesEndHeaders = Encoding . ASCII . GetBytes ( "\r \n \r \n " ) ;
41
41
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
+
42
49
private readonly object _onStartingSync = new Object ( ) ;
43
50
private readonly object _onCompletedSync = new Object ( ) ;
44
51
protected readonly FrameRequestHeaders _requestHeaders = new FrameRequestHeaders ( ) ;
@@ -50,8 +57,9 @@ public abstract partial class Frame : FrameContext, IFrameControl
50
57
51
58
private bool _requestProcessingStarted ;
52
59
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 ;
55
63
protected CancellationTokenSource _abortedCts ;
56
64
protected CancellationToken ? _manuallySetRequestAbortToken ;
57
65
@@ -87,6 +95,9 @@ public Frame(ConnectionContext context,
87
95
_prepareRequest = prepareRequest ;
88
96
_pathBase = context . ServerAddress . PathBase ;
89
97
98
+ _frameState = FrameState . Waiting ;
99
+ _timeout = new Timer ( _timeoutRequest , this , _keepAliveTimout , Timeout . Infinite ) ;
100
+
90
101
FrameControl = this ;
91
102
Reset ( ) ;
92
103
}
@@ -155,7 +166,7 @@ public CancellationToken RequestAborted
155
166
var cts = _abortedCts ;
156
167
return
157
168
cts != null ? cts . Token :
158
- _requestAborted ? new CancellationToken ( true ) :
169
+ _frameState == FrameState . Aborted ? new CancellationToken ( true ) :
159
170
RequestAbortedSource . Token ;
160
171
}
161
172
set
@@ -173,7 +184,7 @@ private CancellationTokenSource RequestAbortedSource
173
184
// Get the abort token, lazily-initializing it if necessary.
174
185
// Make sure it's canceled if an abort request already came in.
175
186
var cts = LazyInitializer . EnsureInitialized ( ref _abortedCts , ( ) => new CancellationTokenSource ( ) ) ;
176
- if ( _requestAborted )
187
+ if ( _frameState == FrameState . Aborted )
177
188
{
178
189
cts . Cancel ( ) ;
179
190
}
@@ -185,8 +196,91 @@ public bool HasResponseStarted
185
196
get { return _responseStarted ; }
186
197
}
187
198
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
+
188
280
public void Reset ( )
189
281
{
282
+ TransitionToState ( FrameState . Waiting ) ;
283
+
190
284
_onStarting = null ;
191
285
_onCompleted = null ;
192
286
@@ -273,10 +367,8 @@ public void Start()
273
367
/// </summary>
274
368
public Task Stop ( )
275
369
{
276
- if ( ! _requestProcessingStopping )
277
- {
278
- _requestProcessingStopping = true ;
279
- }
370
+ TransitionToState ( FrameState . Stopped ) ;
371
+
280
372
return _requestProcessingTask ?? TaskUtilities . CompletedTask ;
281
373
}
282
374
@@ -285,8 +377,7 @@ public Task Stop()
285
377
/// </summary>
286
378
public void Abort ( )
287
379
{
288
- _requestProcessingStopping = true ;
289
- _requestAborted = true ;
380
+ TransitionToState ( FrameState . Aborted ) ;
290
381
291
382
_requestBody ? . Abort ( ) ;
292
383
_responseBody ? . Abort ( ) ;
@@ -573,7 +664,7 @@ protected Task ProduceEnd()
573
664
if ( _responseStarted )
574
665
{
575
666
// We can no longer respond with a 500, so we simply close the connection.
576
- _requestProcessingStopping = true ;
667
+ TransitionToState ( FrameState . Stopped ) ;
577
668
return TaskUtilities . CompletedTask ;
578
669
}
579
670
else
@@ -589,6 +680,12 @@ protected Task ProduceEnd()
589
680
590
681
if ( ! _responseStarted )
591
682
{
683
+ if ( _frameState > FrameState . Stopping && _frameState < FrameState . Stopped )
684
+ {
685
+ // State is status code
686
+ StatusCode = _frameState ;
687
+ ReasonPhrase = null ;
688
+ }
592
689
return ProduceEndAwaited ( ) ;
593
690
}
594
691
@@ -701,6 +798,12 @@ protected bool TakeStartLine(SocketInput input)
701
798
{
702
799
return false ;
703
800
}
801
+
802
+ if ( TransitionToState ( FrameState . ReadingHeaders ) != FrameState . ReadingHeaders )
803
+ {
804
+ return false ;
805
+ }
806
+
704
807
var method = begin . GetAsciiString ( scan ) ;
705
808
706
809
scan . Take ( ) ;
@@ -946,5 +1049,20 @@ private enum HttpVersionType
946
1049
Http1_0 = 0 ,
947
1050
Http1_1 = 1
948
1051
}
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
+ }
949
1067
}
950
1068
}
0 commit comments