// Copyright (c) .NET Foundation. All rights reserved. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using System; using System.Diagnostics; using System.Runtime.CompilerServices; namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http2 { public class Http2StreamOutputFlowControl { private readonly Http2OutputFlowControl _connectionLevelFlowControl; private readonly Http2OutputFlowControl _streamLevelFlowControl; private Http2OutputFlowControlAwaitable _currentConnectionLevelAwaitable; public Http2StreamOutputFlowControl(Http2OutputFlowControl connectionLevelFlowControl, uint initialWindowSize) { _connectionLevelFlowControl = connectionLevelFlowControl; _streamLevelFlowControl = new Http2OutputFlowControl(initialWindowSize); } public int Available => Math.Min(_connectionLevelFlowControl.Available, _streamLevelFlowControl.Available); public bool IsAborted => _connectionLevelFlowControl.IsAborted || _streamLevelFlowControl.IsAborted; public void Advance(int bytes) { _connectionLevelFlowControl.Advance(bytes); _streamLevelFlowControl.Advance(bytes); } public int AdvanceUpToAndWait(long bytes, out Http2OutputFlowControlAwaitable awaitable) { var leastAvailableFlow = _connectionLevelFlowControl.Available < _streamLevelFlowControl.Available ? _connectionLevelFlowControl : _streamLevelFlowControl; // Clamp ~= Math.Clamp from netcoreapp >= 2.0 var actual = Clamp(leastAvailableFlow.Available, 0, bytes); // Make sure to advance prior to accessing AvailabilityAwaitable. _connectionLevelFlowControl.Advance(actual); _streamLevelFlowControl.Advance(actual); awaitable = null; _currentConnectionLevelAwaitable = null; if (actual < bytes) { awaitable = leastAvailableFlow.AvailabilityAwaitable; if (leastAvailableFlow == _connectionLevelFlowControl) { _currentConnectionLevelAwaitable = awaitable; } } return actual; } // The connection-level update window is updated independently. // https://httpwg.org/specs/rfc7540.html#rfc.section.6.9.1 public bool TryUpdateWindow(int bytes) { return _streamLevelFlowControl.TryUpdateWindow(bytes); } public void Abort() { _streamLevelFlowControl.Abort(); // If this stream is waiting on a connection-level window update, complete this stream's // connection-level awaitable so the stream abort is observed immediately. // This could complete an awaitable still sitting in the connection-level awaitable queue, // but this is safe because completing it again will just no-op. _currentConnectionLevelAwaitable?.Complete(); } [MethodImpl(MethodImplOptions.AggressiveInlining)] private static int Clamp(int value, int min, long max) { Debug.Assert(min <= max, $"{nameof(Clamp)} called with a min greater than the max."); if (value < min) { return min; } else if (value > max) { return (int)max; } return value; } } }