public Http2Stream(HttpRequestMessage request, Http2Connection connection, int streamId, int initialWindowSize) { _connection = connection; _streamId = streamId; _state = StreamState.ExpectingStatus; _request = request; _shouldSendRequestBody = true; _disposed = false; _responseBuffer = new ArrayBuffer(InitialStreamBufferSize, usePool: true); _pendingWindowUpdate = 0; _streamWindow = new CreditManager(this, nameof(_streamWindow), initialWindowSize); _headerBudgetRemaining = connection._pool.Settings._maxResponseHeadersLength * 1024; if (NetEventSource.IsEnabled) { Trace($"{request}, {nameof(initialWindowSize)}={initialWindowSize}"); } }
internal void OnPingAckReceived(long payload, Http2Connection connection) { if (_state != State.PingSent && _state != State.TerminatingMayReceivePingAck) { if (NetEventSource.Log.IsEnabled()) { connection.Trace($"[FlowControl] Unexpected PING ACK in state {_state}"); } ThrowProtocolError(); } if (_state == State.TerminatingMayReceivePingAck) { _state = State.Disabled; return; } // RTT PINGs always carry negative payload, positive values indicate a response to KeepAlive PING. Debug.Assert(payload < 0); if (_pingCounter != payload) { if (NetEventSource.Log.IsEnabled()) { connection.Trace($"[FlowControl] Unexpected RTT PING ACK payload {payload}, should be {_pingCounter}."); } ThrowProtocolError(); } RefreshRtt(connection); _state = State.Waiting; }
internal void OnDataOrHeadersReceived(Http2Connection connection) { if (_state != State.Waiting) { return; } long now = Stopwatch.GetTimestamp(); bool initial = _initialBurst > 0; if (initial || now - _pingSentTimestamp > PingIntervalInTicks) { if (initial) { _initialBurst--; } // Send a PING _pingCounter--; if (NetEventSource.Log.IsEnabled()) { connection.Trace($"[FlowControl] Sending RTT PING with payload {_pingCounter}"); } connection.LogExceptions(connection.SendPingAsync(_pingCounter, isAck: false)); _pingSentTimestamp = now; _state = State.PingSent; } }
private void AdjustWindowDynamic(int bytesConsumed, Http2Stream stream) { _deliveredBytes += bytesConsumed; if (_deliveredBytes < StreamWindowThreshold) { return; } int windowUpdateIncrement = _deliveredBytes; long currentTime = Stopwatch.GetTimestamp(); Http2Connection connection = stream.Connection; TimeSpan rtt = connection._rttEstimator.MinRtt; if (rtt > TimeSpan.Zero && _streamWindowSize < MaxStreamWindowSize) { TimeSpan dt = Stopwatch.GetElapsedTime(_lastWindowUpdate, currentTime); // We are detecting bursts in the amount of data consumed within a single 'dt' window update period. // The value "_deliveredBytes / dt" correlates with the bandwidth of the connection. // We need to extend the window, if the bandwidth-delay product grows over the current window size. // To enable empirical fine tuning, we apply a configurable multiplier (_windowScaleThresholdMultiplier) to the window size, which defaults to 1.0 // // The condition to extend the window is: // (_deliveredBytes / dt) * rtt > _streamWindowSize * _windowScaleThresholdMultiplier // // Which is reordered into the form below, to avoid the division: if (_deliveredBytes * (double)rtt.Ticks > _streamWindowSize * dt.Ticks * WindowScaleThresholdMultiplier) { int extendedWindowSize = Math.Min(MaxStreamWindowSize, _streamWindowSize * 2); windowUpdateIncrement += extendedWindowSize - _streamWindowSize; _streamWindowSize = extendedWindowSize; if (NetEventSource.Log.IsEnabled()) { stream.Trace($"[FlowControl] Updated Stream Window. StreamWindowSize: {StreamWindowSize}, StreamWindowThreshold: {StreamWindowThreshold}"); } Debug.Assert(_streamWindowSize <= MaxStreamWindowSize); if (_streamWindowSize == MaxStreamWindowSize) { if (NetEventSource.Log.IsEnabled()) { stream.Trace($"[FlowControl] StreamWindowSize reached the configured maximum of {MaxStreamWindowSize}."); } } } } _deliveredBytes = 0; Task sendWindowUpdateTask = connection.SendWindowUpdateAsync(stream.StreamId, windowUpdateIncrement); connection.LogExceptions(sendWindowUpdateTask); _lastWindowUpdate = currentTime; }
internal void OnInitialSettingsAckReceived(Http2Connection connection) { if (_state == State.Disabled) { return; } RefreshRtt(connection); _state = State.Waiting; }
public Http2Stream(Http2Connection connection) { _connection = connection; _streamId = connection.AddStream(this); _syncObject = new object(); _disposed = false; _responseBuffer = new ArrayBuffer(InitialBufferSize); }
private void RefreshRtt(Http2Connection connection) { long prevRtt = _minRtt == 0 ? long.MaxValue : _minRtt; TimeSpan currentRtt = Stopwatch.GetElapsedTime(_pingSentTimestamp); long minRtt = Math.Min(prevRtt, currentRtt.Ticks); Interlocked.Exchange(ref _minRtt, minRtt); // MinRtt is being queried from another thread if (NetEventSource.Log.IsEnabled()) { connection.Trace($"[FlowControl] Updated MinRtt: {MinRtt.TotalMilliseconds} ms"); } }
public Http2StreamWindowManager(Http2Connection connection, Http2Stream stream) { HttpConnectionSettings settings = connection._pool.Settings; _streamWindowSize = settings._initialHttp2StreamWindowSize; _deliveredBytes = 0; _lastWindowUpdate = default; if (NetEventSource.Log.IsEnabled()) { stream.Trace($"[FlowControl] InitialClientStreamWindowSize: {StreamWindowSize}, StreamWindowThreshold: {StreamWindowThreshold}, WindowScaleThresholdMultiplier: {WindowScaleThresholdMultiplier}"); } }
private void AjdustWindowStatic(int bytesConsumed, Http2Stream stream) { _deliveredBytes += bytesConsumed; if (_deliveredBytes < StreamWindowThreshold) { return; } int windowUpdateIncrement = _deliveredBytes; _deliveredBytes = 0; Http2Connection connection = stream.Connection; Task sendWindowUpdateTask = connection.SendWindowUpdateAsync(stream.StreamId, windowUpdateIncrement); connection.LogExceptions(sendWindowUpdateTask); }
public Http2Stream(HttpRequestMessage request, Http2Connection connection, int streamId, int initialWindowSize) { _connection = connection; _streamId = streamId; _state = StreamState.ExpectingHeaders; _request = request; _disposed = false; _responseBuffer = new ArrayBuffer(InitialStreamBufferSize, usePool: true); _pendingWindowUpdate = 0; _streamWindow = new CreditManager(initialWindowSize); }
public Http2Stream(HttpRequestMessage request, Http2Connection connection, int streamId, int initialWindowSize) { _connection = connection; _streamId = streamId; _state = StreamState.ExpectingStatus; _request = request; _shouldSendRequestBody = true; _disposed = false; _responseBuffer = new ArrayBuffer(InitialStreamBufferSize, usePool: true); _pendingWindowUpdate = 0; _streamWindow = new CreditManager(initialWindowSize); _headerBudgetRemaining = connection._pool.Settings._maxResponseHeadersLength * 1024; }
internal void OnPingAckReceived(long payload, Http2Connection connection) { if (_state != State.PingSent && _state != State.TerminatingMayReceivePingAck) { ThrowProtocolError(); } if (_state == State.TerminatingMayReceivePingAck) { _state = State.Disabled; return; } // RTT PINGs always carry negative payload, positive values indicate a response to KeepAlive PING. Debug.Assert(payload < 0); if (_pingCounter != payload) { ThrowProtocolError(); } RefreshRtt(connection); _state = State.Waiting; }
internal void OnDataOrHeadersReceived(Http2Connection connection) { if (_state != State.Waiting) { return; } long now = Stopwatch.GetTimestamp(); bool initial = _initialBurst > 0; if (initial || now - _pingSentTimestamp > PingIntervalInTicks) { if (initial) { _initialBurst--; } // Send a PING _pingCounter--; connection.LogExceptions(connection.SendPingAsync(_pingCounter, isAck: false)); _pingSentTimestamp = now; _state = State.PingSent; } }
public void InvalidateHttp2Connection(Http2Connection connection) { Debug.Assert(_http2Connection == connection); _http2Connection = null; }
GetHttp2ConnectionAsync(HttpRequestMessage request, CancellationToken cancellationToken) { Debug.Assert(_kind == HttpConnectionKind.Https || _kind == HttpConnectionKind.SslProxyTunnel); // See if we have an HTTP2 connection Http2Connection http2Connection = _http2Connection; if (http2Connection != null) { if (NetEventSource.IsEnabled) { Trace("Using existing HTTP2 connection."); } return(http2Connection, false, null); } // Ensure that the connection creation semaphore is created if (_http2ConnectionCreateLock == null) { lock (SyncObj) { if (_http2ConnectionCreateLock == null) { _http2ConnectionCreateLock = new SemaphoreSlim(1); } } } // Try to establish an HTTP2 connection Socket socket = null; SslStream sslStream = null; TransportContext transportContext = null; // Serialize creation attempt await _http2ConnectionCreateLock.WaitAsync().ConfigureAwait(false); try { if (_http2Connection != null) { // Someone beat us to it if (NetEventSource.IsEnabled) { Trace("Using existing HTTP2 connection."); } return(_http2Connection, false, null); } // Recheck if HTTP2 has been disabled by a previous attempt. if (_http2Enabled) { if (NetEventSource.IsEnabled) { Trace("Attempting new HTTP2 connection."); } Stream stream; HttpResponseMessage failureResponse; (socket, stream, transportContext, failureResponse) = await ConnectAsync(request, true, cancellationToken).ConfigureAwait(false); if (failureResponse != null) { return(null, true, failureResponse); } sslStream = (SslStream)stream; if (sslStream.NegotiatedApplicationProtocol == SslApplicationProtocol.Http2) { // The server accepted our request for HTTP2. if (sslStream.SslProtocol < SslProtocols.Tls12) { throw new HttpRequestException(SR.net_http_invalid_response); } http2Connection = new Http2Connection(this, sslStream); await http2Connection.SetupAsync().ConfigureAwait(false); Debug.Assert(_http2Connection == null); _http2Connection = http2Connection; if (NetEventSource.IsEnabled) { Trace("New HTTP2 connection established."); } return(_http2Connection, true, null); } } } finally { _http2ConnectionCreateLock.Release(); } if (sslStream != null) { // We established an SSL connection, but the server denied our request for HTTP2. // Continue as an HTTP/1.1 connection. if (NetEventSource.IsEnabled) { Trace("Server does not support HTTP2; disabling HTTP2 use and proceeding with HTTP/1.1 connection"); } bool canUse = true; lock (SyncObj) { _http2Enabled = false; if (_associatedConnectionCount < _maxConnections) { IncrementConnectionCountNoLock(); } else { // We are in the weird situation of having established a new HTTP 1.1 connection // when we were already at the maximum for HTTP 1.1 connections. // Just discard this connection and get another one from the pool. // This should be a really rare situation to get into, since it would require // the user to make multiple HTTP 1.1-only requests first before attempting an // HTTP2 request, and the server failing to accept HTTP2. canUse = false; } } if (canUse) { return(ConstructHttp11Connection(socket, sslStream, transportContext), true, null); } else { if (NetEventSource.IsEnabled) { Trace("Discarding downgraded HTTP/1.1 connection because connection limit is exceeded"); } sslStream.Close(); } } // If we reach this point, it means we need to fall back to a (new or existing) HTTP/1.1 connection. return(await GetHttpConnectionAsync(request, cancellationToken)); }