4
4
using System . Diagnostics ;
5
5
using System . Threading . Tasks . Sources ;
6
6
using Microsoft . AspNetCore . HttpSys . Internal ;
7
+ using Microsoft . Extensions . Logging ;
7
8
8
9
namespace Microsoft . AspNetCore . Server . HttpSys ;
9
10
10
- internal sealed unsafe class AsyncAcceptContext : IValueTaskSource < RequestContext > , IDisposable
11
+ internal sealed unsafe partial class AsyncAcceptContext : IValueTaskSource < RequestContext > , IDisposable
11
12
{
12
13
private static readonly IOCompletionCallback IOCallback = IOWaitCallback ;
13
14
private readonly PreAllocatedOverlapped _preallocatedOverlapped ;
14
15
private readonly IRequestContextFactory _requestContextFactory ;
16
+ private readonly ILogger _logger ;
17
+ private int _expectedCompletionCount ;
15
18
16
19
private NativeOverlapped * _overlapped ;
17
20
21
+ private readonly bool _logExpectationFailures = AppContext . TryGetSwitch (
22
+ "Microsoft.AspNetCore.Server.HttpSys.LogAcceptExpectationFailure" , out var enabled ) && enabled ;
23
+
18
24
// mutable struct; do not make this readonly
19
25
private ManualResetValueTaskSourceCore < RequestContext > _mrvts = new ( )
20
26
{
@@ -24,11 +30,12 @@ internal sealed unsafe class AsyncAcceptContext : IValueTaskSource<RequestContex
24
30
25
31
private RequestContext ? _requestContext ;
26
32
27
- internal AsyncAcceptContext ( HttpSysListener server , IRequestContextFactory requestContextFactory )
33
+ internal AsyncAcceptContext ( HttpSysListener server , IRequestContextFactory requestContextFactory , ILogger logger )
28
34
{
29
35
Server = server ;
30
36
_requestContextFactory = requestContextFactory ;
31
37
_preallocatedOverlapped = new ( IOCallback , state : this , pinData : null ) ;
38
+ _logger = logger ;
32
39
}
33
40
34
41
internal HttpSysListener Server { get ; }
@@ -51,15 +58,16 @@ internal ValueTask<RequestContext> AcceptAsync()
51
58
return new ValueTask < RequestContext > ( this , _mrvts . Version ) ;
52
59
}
53
60
54
- private void IOCompleted ( uint errorCode , uint numBytes )
61
+ private void IOCompleted ( uint errorCode , uint numBytes , bool managed )
55
62
{
56
63
try
57
64
{
65
+ ObserveCompletion ( managed ) ; // expectation tracking
58
66
if ( errorCode != UnsafeNclNativeMethods . ErrorCodes . ERROR_SUCCESS &&
59
67
errorCode != UnsafeNclNativeMethods . ErrorCodes . ERROR_MORE_DATA )
60
68
{
61
- _mrvts . SetException ( new HttpSysException ( ( int ) errorCode ) ) ;
62
- return ;
69
+ // (keep all the error handling in one place)
70
+ throw new HttpSysException ( ( int ) errorCode ) ;
63
71
}
64
72
65
73
Debug . Assert ( _requestContext != null ) ;
@@ -71,7 +79,14 @@ private void IOCompleted(uint errorCode, uint numBytes)
71
79
// we want to reuse the acceptContext object for future accepts.
72
80
_requestContext = null ;
73
81
74
- _mrvts . SetResult ( requestContext ) ;
82
+ try
83
+ {
84
+ _mrvts . SetResult ( requestContext ) ;
85
+ }
86
+ catch ( Exception ex )
87
+ {
88
+ Log . AcceptSetResultFailed ( _logger , ex ) ;
89
+ }
75
90
}
76
91
else
77
92
{
@@ -84,22 +99,69 @@ private void IOCompleted(uint errorCode, uint numBytes)
84
99
if ( statusCode != UnsafeNclNativeMethods . ErrorCodes . ERROR_SUCCESS &&
85
100
statusCode != UnsafeNclNativeMethods . ErrorCodes . ERROR_IO_PENDING )
86
101
{
87
- // someother bad error, possible(?) return values are:
102
+ // some other bad error, possible(?) return values are:
88
103
// ERROR_INVALID_HANDLE, ERROR_INSUFFICIENT_BUFFER, ERROR_OPERATION_ABORTED
89
- _mrvts . SetException ( new HttpSysException ( ( int ) statusCode ) ) ;
104
+ // (keep all the error handling in one place)
105
+ throw new HttpSysException ( ( int ) statusCode ) ;
90
106
}
91
107
}
92
108
}
93
109
catch ( Exception exception )
94
110
{
95
- _mrvts . SetException ( exception ) ;
111
+ try
112
+ {
113
+ _mrvts . SetException ( exception ) ;
114
+ }
115
+ catch ( Exception ex )
116
+ {
117
+ Log . AcceptSetResultFailed ( _logger , ex ) ;
118
+ }
96
119
}
97
120
}
98
121
99
122
private static unsafe void IOWaitCallback ( uint errorCode , uint numBytes , NativeOverlapped * nativeOverlapped )
100
123
{
101
124
var acceptContext = ( AsyncAcceptContext ) ThreadPoolBoundHandle . GetNativeOverlappedState ( nativeOverlapped ) ! ;
102
- acceptContext . IOCompleted ( errorCode , numBytes ) ;
125
+ acceptContext . IOCompleted ( errorCode , numBytes , false ) ;
126
+ }
127
+
128
+ private void SetExpectCompletion ( ) // we anticipate a completion *might* occur
129
+ {
130
+ // note this is intentionally a "reset and check" rather than Increment, so that we don't spam
131
+ // the logs forever if a glitch occurs
132
+ var value = Interlocked . Exchange ( ref _expectedCompletionCount , 1 ) ; // should have been 0
133
+ if ( value != 0 )
134
+ {
135
+ if ( _logExpectationFailures )
136
+ {
137
+ Log . AcceptSetExpectationMismatch ( _logger , value ) ;
138
+ }
139
+ Debug . Assert ( false , nameof ( SetExpectCompletion ) ) ; // fail hard in debug
140
+ }
141
+ }
142
+ private void CancelExpectCompletion ( ) // due to error-code etc, we no longer anticipate a completion
143
+ {
144
+ var value = Interlocked . Decrement ( ref _expectedCompletionCount ) ; // should have been 1, so now 0
145
+ if ( value != 0 )
146
+ {
147
+ if ( _logExpectationFailures )
148
+ {
149
+ Log . AcceptCancelExpectationMismatch ( _logger , value ) ;
150
+ }
151
+ Debug . Assert ( false , nameof ( CancelExpectCompletion ) ) ; // fail hard in debug
152
+ }
153
+ }
154
+ private void ObserveCompletion ( bool managed ) // a completion was invoked
155
+ {
156
+ var value = Interlocked . Decrement ( ref _expectedCompletionCount ) ; // should have been 1, so now 0
157
+ if ( value != 0 )
158
+ {
159
+ if ( _logExpectationFailures )
160
+ {
161
+ Log . AcceptObserveExpectationMismatch ( _logger , managed ? "managed" : "unmanaged" , value ) ;
162
+ }
163
+ Debug . Assert ( false , nameof ( ObserveCompletion ) ) ; // fail hard in debug
164
+ }
103
165
}
104
166
105
167
private uint QueueBeginGetContext ( )
@@ -112,6 +174,7 @@ private uint QueueBeginGetContext()
112
174
113
175
retry = false ;
114
176
uint bytesTransferred = 0 ;
177
+ SetExpectCompletion ( ) ; // track this *before*, because of timing vs IOCP (could even be effectively synchronous)
115
178
statusCode = HttpApi . HttpReceiveHttpRequest (
116
179
Server . RequestQueue . Handle ,
117
180
_requestContext . RequestId ,
@@ -123,35 +186,44 @@ private uint QueueBeginGetContext()
123
186
& bytesTransferred ,
124
187
_overlapped ) ;
125
188
126
- if ( ( statusCode == UnsafeNclNativeMethods . ErrorCodes . ERROR_CONNECTION_INVALID
127
- || statusCode == UnsafeNclNativeMethods . ErrorCodes . ERROR_INVALID_PARAMETER )
128
- && _requestContext . RequestId != 0 )
129
- {
130
- // ERROR_CONNECTION_INVALID:
131
- // The client reset the connection between the time we got the MORE_DATA error and when we called HttpReceiveHttpRequest
132
- // with the new buffer. We can clear the request id and move on to the next request.
133
- //
134
- // ERROR_INVALID_PARAMETER: Historical check from HttpListener.
135
- // https://referencesource.microsoft.com/#System/net/System/Net/_ListenerAsyncResult.cs,137
136
- // we might get this if somebody stole our RequestId,
137
- // set RequestId to 0 and start all over again with the buffer we just allocated
138
- // BUGBUG: how can someone steal our request ID? seems really bad and in need of fix.
139
- _requestContext . RequestId = 0 ;
140
- retry = true ;
141
- }
142
- else if ( statusCode == UnsafeNclNativeMethods . ErrorCodes . ERROR_MORE_DATA )
143
- {
144
- // the buffer was not big enough to fit the headers, we need
145
- // to read the RequestId returned, allocate a new buffer of the required size
146
- // (uint)backingBuffer.Length - AlignmentPadding
147
- AllocateNativeRequest ( bytesTransferred ) ;
148
- retry = true ;
149
- }
150
- else if ( statusCode == UnsafeNclNativeMethods . ErrorCodes . ERROR_SUCCESS
151
- && HttpSysListener . SkipIOCPCallbackOnSuccess )
189
+ switch ( statusCode )
152
190
{
153
- // IO operation completed synchronously - callback won't be called to signal completion.
154
- IOCompleted ( statusCode , bytesTransferred ) ;
191
+ case ( UnsafeNclNativeMethods . ErrorCodes . ERROR_CONNECTION_INVALID or UnsafeNclNativeMethods . ErrorCodes . ERROR_INVALID_PARAMETER ) when _requestContext . RequestId != 0 :
192
+ // ERROR_CONNECTION_INVALID:
193
+ // The client reset the connection between the time we got the MORE_DATA error and when we called HttpReceiveHttpRequest
194
+ // with the new buffer. We can clear the request id and move on to the next request.
195
+ //
196
+ // ERROR_INVALID_PARAMETER: Historical check from HttpListener.
197
+ // https://referencesource.microsoft.com/#System/net/System/Net/_ListenerAsyncResult.cs,137
198
+ // we might get this if somebody stole our RequestId,
199
+ // set RequestId to 0 and start all over again with the buffer we just allocated
200
+ // BUGBUG: how can someone steal our request ID? seems really bad and in need of fix.
201
+ CancelExpectCompletion ( ) ;
202
+ _requestContext . RequestId = 0 ;
203
+ retry = true ;
204
+ break ;
205
+ case UnsafeNclNativeMethods . ErrorCodes . ERROR_MORE_DATA :
206
+ // the buffer was not big enough to fit the headers, we need
207
+ // to read the RequestId returned, allocate a new buffer of the required size
208
+ // (uint)backingBuffer.Length - AlignmentPadding
209
+ CancelExpectCompletion ( ) ; // we'll "expect" again when we retry
210
+ AllocateNativeRequest ( bytesTransferred ) ;
211
+ retry = true ;
212
+ break ;
213
+ case UnsafeNclNativeMethods . ErrorCodes . ERROR_SUCCESS :
214
+ if ( HttpSysListener . SkipIOCPCallbackOnSuccess )
215
+ {
216
+ // IO operation completed synchronously - callback won't be called to signal completion.
217
+ IOCompleted ( statusCode , bytesTransferred , true ) ; // marks completion
218
+ }
219
+ // else: callback fired by IOCP (at some point), which marks completion
220
+ break ;
221
+ case UnsafeNclNativeMethods . ErrorCodes . ERROR_IO_PENDING :
222
+ break ; // no change to state - callback will occur at some point
223
+ default :
224
+ // fault code, not expecting an IOCP callback
225
+ CancelExpectCompletion ( ) ;
226
+ break ;
155
227
}
156
228
}
157
229
while ( retry ) ;
0 commit comments