| File: Internal\Http2\FlowControl\StreamInputFlowControl.cs | Web Access |
| Project: src\aspnetcore\src\Servers\Kestrel\Core\src\Microsoft.AspNetCore.Server.Kestrel.Core.csproj (Microsoft.AspNetCore.Server.Kestrel.Core) |
// Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. using System.Diagnostics; namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http2.FlowControl; /// <summary> /// Represents the in-bound flow control state of a stream. /// </summary> /// <remarks> /// Owns an <see cref="InputFlowControl"/> for the stream-level flow control and /// references another (owned by a <see cref="Http2Connection"/>) for the /// connection-level flow control. /// <para/> /// <see cref="Http2Stream"/> owns an instance for the stream-level flow control. /// <para/> /// Reusable after calling <see cref="Reset"/>. /// </remarks> /// <seealso href="https://datatracker.ietf.org/doc/html/rfc9113#name-flow-control"/> internal sealed class StreamInputFlowControl { private readonly InputFlowControl _connectionLevelFlowControl; private readonly InputFlowControl _streamLevelFlowControl; private int StreamId => _stream.StreamId; private readonly Http2Stream _stream; private readonly Http2FrameWriter _frameWriter; public StreamInputFlowControl( Http2Stream stream, Http2FrameWriter frameWriter, InputFlowControl connectionLevelFlowControl, uint initialWindowSize, uint minWindowSizeIncrement) { _connectionLevelFlowControl = connectionLevelFlowControl; _streamLevelFlowControl = new InputFlowControl(initialWindowSize, minWindowSizeIncrement); _stream = stream; _frameWriter = frameWriter; } public void Reset() { _streamLevelFlowControl.Reset(); } public void Advance(int bytes) { var connectionSuccess = _connectionLevelFlowControl.TryAdvance(bytes); Debug.Assert(connectionSuccess, "Connection-level input flow control should never be aborted."); if (!_streamLevelFlowControl.TryAdvance(bytes)) { // The stream has already been aborted, so immediately count the bytes as read at the connection level. UpdateConnectionWindow(bytes); } } public void UpdateWindows(int bytes) { if (!_streamLevelFlowControl.TryUpdateWindow(bytes, out var streamWindowUpdateSize)) { // Stream-level flow control was aborted. Any unread bytes have already been returned to the connection // flow-control window by Abort(). return; } if (streamWindowUpdateSize > 0) { // Writing with the FrameWriter should only fail if given a canceled token, so just fire and forget. _ = _frameWriter.WriteWindowUpdateAsync(StreamId, streamWindowUpdateSize).Preserve(); } UpdateConnectionWindow(bytes); } public void StopWindowUpdates() { _streamLevelFlowControl.StopWindowUpdates(); } public void Abort() { var unreadBytes = _streamLevelFlowControl.Abort(); if (unreadBytes > 0) { // We assume that the app won't read the remaining data from the request body pipe. // Even if the app does continue reading, _streamLevelFlowControl.TryUpdateWindow() will return false // from now on which prevents double counting. UpdateConnectionWindow(unreadBytes); } } private void UpdateConnectionWindow(int bytes) { var connectionSuccess = _connectionLevelFlowControl.TryUpdateWindow(bytes, out var connectionWindowUpdateSize); Debug.Assert(connectionSuccess, "Connection-level input flow control should never be aborted."); if (connectionWindowUpdateSize > 0) { // Writing with the FrameWriter should only fail if given a canceled token, so just fire and forget. _ = _frameWriter.WriteWindowUpdateAsync(0, connectionWindowUpdateSize).Preserve(); } } }