|
7 | 7 | using Microsoft.AspNetCore.Http;
|
8 | 8 | using Microsoft.AspNetCore.Server.Kestrel.Core;
|
9 | 9 | using Microsoft.AspNetCore.Server.Kestrel.Core.Features;
|
10 |
| -using Microsoft.AspNetCore.Server.Kestrel.Core.Internal; |
11 | 10 | using Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http;
|
12 | 11 | using Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Infrastructure;
|
13 | 12 | using Microsoft.AspNetCore.Testing;
|
@@ -37,8 +36,32 @@ public async Task RequestTimesOutWhenRequestBodyNotReceivedAtSpecifiedMinimumRat
|
37 | 36 | context.Features.Get<IHttpMinRequestBodyDataRateFeature>().MinDataRate =
|
38 | 37 | new MinDataRate(bytesPerSecond: 1, gracePeriod: gracePeriod);
|
39 | 38 |
|
| 39 | + // The server must call Request.Body.ReadAsync() *before* the test sets systemClock.UtcNow (which is triggered by the |
| 40 | + // server calling appRunningEvent.Set()). If systemClock.UtcNow is set first, it's possible for the test to fail |
| 41 | + // due to the following race condition: |
| 42 | + // |
| 43 | + // 1. [test] systemClock.UtcNow += gracePeriod + TimeSpan.FromSeconds(1); |
| 44 | + // 2. [server] Heartbeat._timer is triggered, which calls HttpConnection.Tick() |
| 45 | + // 3. [server] HttpConnection.Tick() calls HttpConnection.CheckForReadDataRateTimeout() |
| 46 | + // 4. [server] HttpConnection.CheckForReadDataRateTimeout() is a no-op, since _readTimingEnabled is false, |
| 47 | + // since Request.Body.ReadAsync() has not been called yet |
| 48 | + // 5. [server] HttpConnection.Tick() sets _lastTimestamp = timestamp |
| 49 | + // 6. [server] Request.Body.ReadAsync() is called |
| 50 | + // 6. [test] systemClock.UtcNow is never updated again, so server timestamp is never updated, |
| 51 | + // so HttpConnection.CheckForReadDataRateTimeout() is always a no-op until test fails |
| 52 | + // |
| 53 | + // This is a pretty tight race, since the once-per-second Heartbeat._timer needs to fire between the test updating |
| 54 | + // systemClock.UtcNow and the server calling Request.Body.ReadAsync(). But it happened often enough to cause |
| 55 | + // test flakiness in our CI (https://github.com/aspnet/KestrelHttpServer/issues/2539). |
| 56 | + // |
| 57 | + // For verification, I was able to induce the race by adding a sleep in the RequestDelegate: |
| 58 | + // appRunningEvent.Set(); |
| 59 | + // Thread.Sleep(5000); |
| 60 | + // return context.Request.Body.ReadAsync(new byte[1], 0, 1); |
| 61 | + |
| 62 | + var readTask = context.Request.Body.ReadAsync(new byte[1], 0, 1); |
40 | 63 | appRunningEvent.Set();
|
41 |
| - return context.Request.Body.ReadAsync(new byte[1], 0, 1); |
| 64 | + return readTask; |
42 | 65 | }, serviceContext))
|
43 | 66 | {
|
44 | 67 | using (var connection = server.CreateConnection())
|
|
0 commit comments