File: System\Net\Http\SocketsHttpHandler\HttpContentReadStream.cs
Web Access
Project: src\src\libraries\System.Net.Http\src\System.Net.Http.csproj (System.Net.Http)
// 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;
using System.Threading;
using System.Threading.Tasks;
 
namespace System.Net.Http
{
    internal sealed partial class HttpConnection
    {
        internal abstract class HttpContentReadStream : HttpContentStream
        {
            private bool _disposed;
 
            public HttpContentReadStream(HttpConnection connection) : base(connection)
            {
            }
 
            public sealed override bool CanRead => !_disposed;
            public sealed override bool CanWrite => false;
 
            public sealed override void Write(ReadOnlySpan<byte> buffer) => throw new NotSupportedException(SR.net_http_content_readonly_stream);
 
            public sealed override ValueTask WriteAsync(ReadOnlyMemory<byte> destination, CancellationToken cancellationToken) => throw new NotSupportedException();
 
            public virtual bool NeedsDrain => false;
 
            protected bool IsDisposed => _disposed;
 
            protected bool CanReadFromConnection
            {
                get
                {
                    // _connection == null typically means that we have finished reading the response.
                    // Cancellation may lead to a state where a disposed _connection is not null.
                    HttpConnection? connection = _connection;
                    return connection != null && !connection._disposed;
                }
            }
 
            public virtual ValueTask<bool> DrainAsync(int maxDrainBytes)
            {
                Debug.Fail($"DrainAsync should not be called for this response stream: {GetType()}");
                return new ValueTask<bool>(false);
            }
 
            protected override void Dispose(bool disposing)
            {
                // Only attempt draining if we haven't started draining due to disposal; otherwise
                // multiple calls to Dispose (which happens frequently when someone disposes of the
                // response stream and response content) will kick off multiple concurrent draining
                // operations. Also don't delegate to the base if Dispose has already been called,
                // as doing so will end up disposing of the connection before we're done draining.
                if (Interlocked.Exchange(ref _disposed, true))
                {
                    return;
                }
 
                if (disposing && NeedsDrain)
                {
                    // Start the asynchronous drain.
                    // It may complete synchronously, in which case the connection will be put back in the pool synchronously.
                    // Skip the call to base.Dispose -- it will be deferred until DrainOnDisposeAsync finishes.
                    _ = DrainOnDisposeAsync();
                    return;
                }
 
                base.Dispose(disposing);
            }
 
            private async Task DrainOnDisposeAsync()
            {
                HttpConnection? connection = _connection;        // Will be null after drain succeeds
                Debug.Assert(connection != null);
                try
                {
                    bool drained = await DrainAsync(connection._pool.Settings._maxResponseDrainSize).ConfigureAwait(false);
 
                    if (NetEventSource.Log.IsEnabled())
                    {
                        connection.Trace(drained ?
                            "Connection drain succeeded" :
                            $"Connection drain failed when MaxResponseDrainSize={connection._pool.Settings._maxResponseDrainSize} bytes or MaxResponseDrainTime=={connection._pool.Settings._maxResponseDrainTime} exceeded");
                    }
                }
                catch (Exception e)
                {
                    if (NetEventSource.Log.IsEnabled())
                    {
                        connection.Trace($"Connection drain failed due to exception: {e}");
                    }
 
                    // Eat any exceptions and just Dispose.
                }
 
                base.Dispose(true);
            }
        }
    }
}