@@ -30,6 +30,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.FunctionalTests
30
30
public class RequestTests
31
31
{
32
32
private const int _connectionStartedEventId = 1 ;
33
+ private const int _connectionKeepAliveEventId = 9 ;
33
34
private const int _connectionResetEventId = 19 ;
34
35
private const int _semaphoreWaitTimeout = 2500 ;
35
36
@@ -241,7 +242,8 @@ public void CanUpgradeRequestWithConnectionKeepAliveUpgradeHeader()
241
242
[ Fact ]
242
243
public async Task ConnectionResetPriorToRequestIsLoggedAsDebug ( )
243
244
{
244
- var connectionStartedTcs = new TaskCompletionSource < object > ( ) ;
245
+ var connectionStarted = new SemaphoreSlim ( 0 ) ;
246
+ var connectionReset = new SemaphoreSlim ( 0 ) ;
245
247
246
248
var mockLogger = new Mock < ILogger > ( ) ;
247
249
mockLogger
@@ -251,7 +253,13 @@ public async Task ConnectionResetPriorToRequestIsLoggedAsDebug()
251
253
. Setup ( logger => logger . Log ( LogLevel . Debug , _connectionStartedEventId , It . IsAny < object > ( ) , null , It . IsAny < Func < object , Exception , string > > ( ) ) )
252
254
. Callback ( ( ) =>
253
255
{
254
- connectionStartedTcs . SetResult ( null ) ;
256
+ connectionStarted . Release ( ) ;
257
+ } ) ;
258
+ mockLogger
259
+ . Setup ( logger => logger . Log ( LogLevel . Debug , _connectionResetEventId , It . IsAny < object > ( ) , null , It . IsAny < Func < object , Exception , string > > ( ) ) )
260
+ . Callback ( ( ) =>
261
+ {
262
+ connectionReset . Release ( ) ;
255
263
} ) ;
256
264
257
265
var mockLoggerFactory = new Mock < ILoggerFactory > ( ) ;
@@ -266,10 +274,7 @@ public async Task ConnectionResetPriorToRequestIsLoggedAsDebug()
266
274
. UseLoggerFactory ( mockLoggerFactory . Object )
267
275
. UseKestrel ( )
268
276
. UseUrls ( $ "http://127.0.0.1:0")
269
- . Configure ( app => app . Run ( context =>
270
- {
271
- return TaskCache . CompletedTask ;
272
- } ) ) ;
277
+ . Configure ( app => app . Run ( context => TaskCache . CompletedTask ) ) ;
273
278
274
279
using ( var host = builder . Build ( ) )
275
280
{
@@ -280,23 +285,42 @@ public async Task ConnectionResetPriorToRequestIsLoggedAsDebug()
280
285
socket . Connect ( new IPEndPoint ( IPAddress . Loopback , host . GetPort ( ) ) ) ;
281
286
282
287
// Wait until connection is established
283
- await connectionStartedTcs . Task . TimeoutAfter ( TimeSpan . FromSeconds ( 10 ) ) ;
288
+ await connectionStarted . WaitAsync ( TimeSpan . FromSeconds ( 10 ) ) ;
284
289
285
290
// Force a reset
286
291
socket . LingerState = new LingerOption ( true , 0 ) ;
287
292
}
288
- }
289
293
290
- mockLogger . Verify ( logger => logger . Log ( LogLevel . Debug , _connectionResetEventId , It . IsAny < object > ( ) , null , It . IsAny < Func < object , Exception , string > > ( ) ) ) ;
294
+ // If the reset is correctly logged as Debug, the wait below should complete shortly.
295
+ // This check MUST come before disposing the server, otherwise there's a race where the RST
296
+ // is still in flight when the connection is aborted, leading to the reset never being received
297
+ // and therefore not logged.
298
+ await connectionReset . WaitAsync ( TimeSpan . FromSeconds ( 10 ) ) ;
299
+ }
291
300
}
292
301
293
302
[ Fact ]
294
303
public async Task ConnectionResetBetweenRequestsIsLoggedAsDebug ( )
295
304
{
305
+ var requestDone = new SemaphoreSlim ( 0 ) ;
306
+ var connectionReset = new SemaphoreSlim ( 0 ) ;
307
+
296
308
var mockLogger = new Mock < ILogger > ( ) ;
297
309
mockLogger
298
310
. Setup ( logger => logger . IsEnabled ( It . IsAny < LogLevel > ( ) ) )
299
311
. Returns ( true ) ;
312
+ mockLogger
313
+ . Setup ( logger => logger . Log ( LogLevel . Debug , _connectionKeepAliveEventId , It . IsAny < object > ( ) , null , It . IsAny < Func < object , Exception , string > > ( ) ) )
314
+ . Callback ( ( ) =>
315
+ {
316
+ requestDone . Release ( ) ;
317
+ } ) ;
318
+ mockLogger
319
+ . Setup ( logger => logger . Log ( LogLevel . Debug , _connectionResetEventId , It . IsAny < object > ( ) , null , It . IsAny < Func < object , Exception , string > > ( ) ) )
320
+ . Callback ( ( ) =>
321
+ {
322
+ connectionReset . Release ( ) ;
323
+ } ) ;
300
324
301
325
var mockLoggerFactory = new Mock < ILoggerFactory > ( ) ;
302
326
mockLoggerFactory
@@ -306,17 +330,12 @@ public async Task ConnectionResetBetweenRequestsIsLoggedAsDebug()
306
330
. Setup ( factory => factory . CreateLogger ( It . IsNotIn ( new [ ] { "Microsoft.AspNetCore.Server.Kestrel" } ) ) )
307
331
. Returns ( Mock . Of < ILogger > ( ) ) ;
308
332
309
- var connectionStartedTcs = new TaskCompletionSource < object > ( ) ;
310
333
311
334
var builder = new WebHostBuilder ( )
312
335
. UseLoggerFactory ( mockLoggerFactory . Object )
313
336
. UseKestrel ( )
314
337
. UseUrls ( $ "http://127.0.0.1:0")
315
- . Configure ( app => app . Run ( context =>
316
- {
317
- connectionStartedTcs . SetResult ( null ) ;
318
- return TaskCache . CompletedTask ;
319
- } ) ) ;
338
+ . Configure ( app => app . Run ( context => TaskCache . CompletedTask ) ) ;
320
339
321
340
using ( var host = builder . Build ( ) )
322
341
{
@@ -327,24 +346,36 @@ public async Task ConnectionResetBetweenRequestsIsLoggedAsDebug()
327
346
socket . Connect ( new IPEndPoint ( IPAddress . Loopback , host . GetPort ( ) ) ) ;
328
347
socket . Send ( Encoding . ASCII . GetBytes ( "GET / HTTP/1.1\r \n \r \n " ) ) ;
329
348
330
- // Wait until connection is established
331
- await connectionStartedTcs . Task . TimeoutAfter ( TimeSpan . FromSeconds ( 10 ) ) ;
349
+ // Wait until request is done being processed
350
+ await requestDone . WaitAsync ( TimeSpan . FromSeconds ( 10 ) ) ;
332
351
333
352
// Force a reset
334
353
socket . LingerState = new LingerOption ( true , 0 ) ;
335
354
}
336
- }
337
355
338
- mockLogger . Verify ( logger => logger . Log ( LogLevel . Debug , _connectionResetEventId , It . IsAny < object > ( ) , null , It . IsAny < Func < object , Exception , string > > ( ) ) ) ;
356
+ // If the reset is correctly logged as Debug, the wait below should complete shortly.
357
+ // This check MUST come before disposing the server, otherwise there's a race where the RST
358
+ // is still in flight when the connection is aborted, leading to the reset never being received
359
+ // and therefore not logged.
360
+ await connectionReset . WaitAsync ( TimeSpan . FromSeconds ( 10 ) ) ;
361
+ }
339
362
}
340
363
341
364
[ Fact ]
342
365
public async Task ConnectionResetMidRequestIsLoggedAsDebug ( )
343
366
{
367
+ var connectionReset = new SemaphoreSlim ( 0 ) ;
368
+
344
369
var mockLogger = new Mock < ILogger > ( ) ;
345
370
mockLogger
346
371
. Setup ( logger => logger . IsEnabled ( It . IsAny < LogLevel > ( ) ) )
347
372
. Returns ( true ) ;
373
+ mockLogger
374
+ . Setup ( logger => logger . Log ( LogLevel . Debug , _connectionResetEventId , It . IsAny < object > ( ) , null , It . IsAny < Func < object , Exception , string > > ( ) ) )
375
+ . Callback ( ( ) =>
376
+ {
377
+ connectionReset . Release ( ) ;
378
+ } ) ;
348
379
349
380
var mockLoggerFactory = new Mock < ILoggerFactory > ( ) ;
350
381
mockLoggerFactory
@@ -354,15 +385,15 @@ public async Task ConnectionResetMidRequestIsLoggedAsDebug()
354
385
. Setup ( factory => factory . CreateLogger ( It . IsNotIn ( new [ ] { "Microsoft.AspNetCore.Server.Kestrel" } ) ) )
355
386
. Returns ( Mock . Of < ILogger > ( ) ) ;
356
387
357
- var connectionStartedTcs = new TaskCompletionSource < object > ( ) ;
388
+ var requestStarted = new SemaphoreSlim ( 0 ) ;
358
389
359
390
var builder = new WebHostBuilder ( )
360
391
. UseLoggerFactory ( mockLoggerFactory . Object )
361
392
. UseKestrel ( )
362
393
. UseUrls ( $ "http://127.0.0.1:0")
363
394
. Configure ( app => app . Run ( async context =>
364
395
{
365
- connectionStartedTcs . SetResult ( null ) ;
396
+ requestStarted . Release ( ) ;
366
397
await context . Request . Body . ReadAsync ( new byte [ 1 ] , 0 , 1 ) ;
367
398
} ) ) ;
368
399
@@ -376,14 +407,18 @@ public async Task ConnectionResetMidRequestIsLoggedAsDebug()
376
407
socket . Send ( Encoding . ASCII . GetBytes ( "GET / HTTP/1.1\r \n \r \n " ) ) ;
377
408
378
409
// Wait until connection is established
379
- await connectionStartedTcs . Task . TimeoutAfter ( TimeSpan . FromSeconds ( 10 ) ) ;
410
+ await requestStarted . WaitAsync ( TimeSpan . FromSeconds ( 10 ) ) ;
380
411
381
412
// Force a reset
382
413
socket . LingerState = new LingerOption ( true , 0 ) ;
383
414
}
384
- }
385
415
386
- mockLogger . Verify ( logger => logger . Log ( LogLevel . Debug , _connectionResetEventId , It . IsAny < object > ( ) , null , It . IsAny < Func < object , Exception , string > > ( ) ) ) ;
416
+ // If the reset is correctly logged as Debug, the wait below should complete shortly.
417
+ // This check MUST come before disposing the server, otherwise there's a race where the RST
418
+ // is still in flight when the connection is aborted, leading to the reset never being received
419
+ // and therefore not logged.
420
+ await connectionReset . WaitAsync ( TimeSpan . FromSeconds ( 10 ) ) ;
421
+ }
387
422
}
388
423
389
424
[ Fact ]
0 commit comments