Skip to content

Commit 397ef0a

Browse files
bhaskarqlikamcasey
andauthored
if actual bytes to be written is negative, set to zero (#58575)
* wait for window updates, if stream window becomes negative * update comment * fix typo * update comment per suggestion Co-authored-by: Andrew Casey <amcasey@users.noreply.github.com> --------- Co-authored-by: Andrew Casey <amcasey@users.noreply.github.com>
1 parent f6dedca commit 397ef0a

File tree

2 files changed

+57
-0
lines changed

2 files changed

+57
-0
lines changed

src/Servers/Kestrel/Core/src/Internal/Http2/Http2FrameWriter.cs

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -198,6 +198,17 @@ private async Task WriteToOutputPipe()
198198
// Now check the connection window
199199
actual = CheckConnectionWindow(actual);
200200

201+
// actual is negative means window size has become negative
202+
// this can usually happen if the receiver decreases window size before receiving the previous data frame
203+
// in this case, reset to 0 and continue, no data will be sent but will wait for window update
204+
// RFC 9113 section 6.9.2 specifically calls out that the window size can go negative. As required,
205+
// we continue to track the negative value but use 0 for the remainder of this write to avoid
206+
// out-of-range errors.
207+
if (actual < 0)
208+
{
209+
actual = 0;
210+
}
211+
201212
// Write what we can
202213
if (actual < buffer.Length)
203214
{

src/Servers/Kestrel/test/InMemory.FunctionalTests/Http2/Http2ConnectionTests.cs

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4751,6 +4751,52 @@ await ExpectAsync(Http2FrameType.DATA,
47514751
Assert.True(_helloWorldBytes.AsSpan(9, 3).SequenceEqual(dataFrame3.PayloadSequence.ToArray()));
47524752
}
47534753

4754+
[Fact]
4755+
public async Task WINDOW_UPDATE_Received_OnStream_Resumed_WhenInitialWindowSizeNegativeMidStream()
4756+
{
4757+
const int windowSize = 3;
4758+
_clientSettings.InitialWindowSize = windowSize;
4759+
var tcs = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously);
4760+
await InitializeConnectionAsync(async context =>
4761+
{
4762+
var bodyControlFeature = context.Features.Get<IHttpBodyControlFeature>();
4763+
bodyControlFeature.AllowSynchronousIO = true;
4764+
await context.Response.Body.WriteAsync(new byte[windowSize - 1], 0, windowSize - 1);
4765+
await tcs.Task;
4766+
await context.Response.Body.WriteAsync(new byte[windowSize], 0, windowSize);
4767+
});
4768+
await StartStreamAsync(1, _browserRequestHeaders, endStream: true);
4769+
await ExpectAsync(Http2FrameType.HEADERS,
4770+
withLength: 32,
4771+
withFlags: (byte)Http2HeadersFrameFlags.END_HEADERS,
4772+
withStreamId: 1);
4773+
4774+
// Decrease window size after server has already sent the current window - 1 size of data
4775+
_clientSettings.InitialWindowSize = windowSize - 2;
4776+
await SendSettingsAsync();
4777+
await ExpectAsync(Http2FrameType.DATA,
4778+
withLength: windowSize - 1,
4779+
withFlags: (byte)Http2DataFrameFlags.NONE,
4780+
withStreamId: 1);
4781+
await ExpectAsync(Http2FrameType.SETTINGS,
4782+
withLength: 0,
4783+
withFlags: (byte)Http2DataFrameFlags.END_STREAM,
4784+
withStreamId: 0);
4785+
tcs.SetResult();
4786+
4787+
// send window update to receive the next frame data
4788+
await SendWindowUpdateAsync(1, windowSize + 1);
4789+
await ExpectAsync(Http2FrameType.DATA,
4790+
withLength: windowSize,
4791+
withFlags: (byte)Http2DataFrameFlags.NONE,
4792+
withStreamId: 1);
4793+
await ExpectAsync(Http2FrameType.DATA,
4794+
withLength: 0,
4795+
withFlags: (byte)Http2DataFrameFlags.END_STREAM,
4796+
withStreamId: 1);
4797+
await StopConnectionAsync(expectedLastStreamId: 1, ignoreNonGoAwayFrames: false);
4798+
}
4799+
47544800
[Fact]
47554801
public async Task CONTINUATION_Received_Decoded()
47564802
{

0 commit comments

Comments
 (0)