Skip to content

Commit e93920a

Browse files
committed
add test
1 parent 2701197 commit e93920a

File tree

1 file changed

+34
-9
lines changed

1 file changed

+34
-9
lines changed

src/libraries/System.Net.Http/tests/FunctionalTests/SocketsHttpHandlerTest.Http2FlowControl.cs

Lines changed: 34 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -128,7 +128,7 @@ static async Task RunTest()
128128
TimeSpan.FromMilliseconds(30),
129129
TimeSpan.Zero,
130130
2 * 1024 * 1024,
131-
null);
131+
maxWindowForPingStopValidation: MaxWindow);
132132

133133
Assert.True(maxCredit <= MaxWindow);
134134
}
@@ -181,19 +181,34 @@ static async Task RunTest()
181181
RemoteExecutor.Invoke(RunTest, options).Dispose();
182182
}
183183

184+
[OuterLoop("Runs long")]
185+
[Fact]
186+
public async Task LongRunningSlowServerStream_NoInvalidPingsAreSent()
187+
{
188+
// A scenario similar to https://github.com/grpc/grpc-dotnet/issues/2361.
189+
// We need to send a small amount of data so the connection window is not consumed and no "standard" WINDOW_UPDATEs are sent and
190+
// we also need to do it very slowly to cover some RTT PINGs after the initial burst.
191+
// This scenario should trigger the "forced WINDOW_UPDATE" logic in the implementation, ensuring that no more than 4 PINGs are sent without a WINDOW_UPDATE.
192+
await TestClientWindowScalingAsync(
193+
TimeSpan.FromMilliseconds(500),
194+
TimeSpan.FromMilliseconds(500),
195+
1024,
196+
_output,
197+
dataPerFrame: 32);
198+
}
199+
184200
private static async Task<int> TestClientWindowScalingAsync(
185201
TimeSpan networkDelay,
186202
TimeSpan slowBandwidthSimDelay,
187203
int bytesToDownload,
188204
ITestOutputHelper output = null,
189-
int maxWindowForPingStopValidation = int.MaxValue, // set to actual maximum to test if we stop sending PING when window reached maximum
190-
Action<SocketsHttpHandler> configureHandler = null)
205+
int dataPerFrame = 16384,
206+
int maxWindowForPingStopValidation = 16 * 1024 * 1024) // set to actual maximum to test if we stop sending PING when window reached maximum
191207
{
192208
TimeSpan timeout = TimeSpan.FromSeconds(30);
193209
CancellationTokenSource timeoutCts = new CancellationTokenSource(timeout);
194210

195211
HttpClientHandler handler = CreateHttpClientHandler(HttpVersion20.Value);
196-
configureHandler?.Invoke(GetUnderlyingSocketsHttpHandler(handler));
197212

198213
using Http2LoopbackServer server = Http2LoopbackServer.CreateServer(NoAutoPingResponseHttp2Options);
199214
using HttpClient client = new HttpClient(handler, true);
@@ -225,13 +240,13 @@ private static async Task<int> TestClientWindowScalingAsync(
225240
using SemaphoreSlim writeSemaphore = new SemaphoreSlim(1);
226241
int remainingBytes = bytesToDownload;
227242

228-
bool pingReceivedAfterReachingMaxWindow = false;
243+
string unexpectedPingReason = null;
229244
bool unexpectedFrameReceived = false;
230245
CancellationTokenSource stopFrameProcessingCts = new CancellationTokenSource();
231246

232247
CancellationTokenSource linkedCts = CancellationTokenSource.CreateLinkedTokenSource(stopFrameProcessingCts.Token, timeoutCts.Token);
233248
Task processFramesTask = ProcessIncomingFramesAsync(linkedCts.Token);
234-
byte[] buffer = new byte[16384];
249+
byte[] buffer = new byte[dataPerFrame];
235250

236251
while (remainingBytes > 0)
237252
{
@@ -259,7 +274,7 @@ private static async Task<int> TestClientWindowScalingAsync(
259274

260275
int dataReceived = (await response.Content.ReadAsByteArrayAsync()).Length;
261276
Assert.Equal(bytesToDownload, dataReceived);
262-
Assert.False(pingReceivedAfterReachingMaxWindow, "Server received a PING after reaching max window");
277+
Assert.Null(unexpectedPingReason);
263278
Assert.False(unexpectedFrameReceived, "Server received an unexpected frame, see test output for more details.");
264279

265280
return maxCredit;
@@ -270,6 +285,7 @@ async Task ProcessIncomingFramesAsync(CancellationToken cancellationToken)
270285
// We should not receive any more RTT PING's after this point
271286
int maxWindowCreditThreshold = (int) (0.9 * maxWindowForPingStopValidation);
272287
output?.WriteLine($"maxWindowCreditThreshold: {maxWindowCreditThreshold} maxWindowForPingStopValidation: {maxWindowForPingStopValidation}");
288+
int pingsWithoutWindowUpdate = 0;
273289

274290
try
275291
{
@@ -284,10 +300,18 @@ async Task ProcessIncomingFramesAsync(CancellationToken cancellationToken)
284300

285301
output?.WriteLine($"Received PING ({pingFrame.Data})");
286302

303+
pingsWithoutWindowUpdate++;
287304
if (maxCredit > maxWindowCreditThreshold)
288305
{
289-
output?.WriteLine("PING was unexpected");
290-
Volatile.Write(ref pingReceivedAfterReachingMaxWindow, true);
306+
Volatile.Write(ref unexpectedPingReason, "The server received a PING after reaching max window");
307+
output?.WriteLine($"PING was unexpected: {unexpectedPingReason}");
308+
}
309+
310+
// Exceeding this limit may trigger a GOAWAY on some servers. See implementation comments for more details.
311+
if (pingsWithoutWindowUpdate > 4)
312+
{
313+
Volatile.Write(ref unexpectedPingReason, $"The server received {pingsWithoutWindowUpdate} PINGs without receiving a WINDOW_UPDATE");
314+
output?.WriteLine($"PING was unexpected: {unexpectedPingReason}");
291315
}
292316

293317
await writeSemaphore.WaitAsync(cancellationToken);
@@ -296,6 +320,7 @@ async Task ProcessIncomingFramesAsync(CancellationToken cancellationToken)
296320
}
297321
else if (frame is WindowUpdateFrame windowUpdateFrame)
298322
{
323+
pingsWithoutWindowUpdate = 0;
299324
// Ignore connection window:
300325
if (windowUpdateFrame.StreamId != streamId) continue;
301326

0 commit comments

Comments
 (0)