Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

QuicStream shutdown #52929

Closed
wants to merge 9 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 16 additions & 0 deletions src/libraries/Common/src/System/Net/StreamBuffer.cs
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ internal sealed class StreamBuffer : IDisposable
private bool _readAborted;
private readonly ResettableValueTaskSource _readTaskSource;
private readonly ResettableValueTaskSource _writeTaskSource;
private readonly TaskCompletionSource _shutdownTaskSource;

public const int DefaultInitialBufferSize = 4 * 1024;
public const int DefaultMaxBufferSize = 32 * 1024;
Expand All @@ -28,10 +29,13 @@ public StreamBuffer(int initialBufferSize = DefaultInitialBufferSize, int maxBuf
_maxBufferSize = maxBufferSize;
_readTaskSource = new ResettableValueTaskSource();
_writeTaskSource = new ResettableValueTaskSource();
_shutdownTaskSource = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously);
}

private object SyncObject => _readTaskSource;

public Task Completed => _shutdownTaskSource.Task;

public bool IsComplete
{
get
Expand Down Expand Up @@ -187,6 +191,11 @@ public void EndWrite()
_writeEnded = true;

_readTaskSource.SignalWaiter();

if (_buffer.IsEmpty)
{
_shutdownTaskSource.TrySetResult();
}
}
}

Expand All @@ -210,10 +219,16 @@ public void EndWrite()

_writeTaskSource.SignalWaiter();

if (_buffer.IsEmpty && _writeEnded)
{
_shutdownTaskSource.TrySetResult();
}

return (false, bytesRead);
}
else if (_writeEnded)
{
_shutdownTaskSource.TrySetResult();
return (false, 0);
}

Expand Down Expand Up @@ -280,6 +295,7 @@ public void AbortRead()

_readTaskSource.SignalWaiter();
_writeTaskSource.SignalWaiter();
_shutdownTaskSource.TrySetResult();
}
}

Expand Down
17 changes: 7 additions & 10 deletions src/libraries/Common/tests/System/Net/Http/Http3LoopbackStream.cs
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@
namespace System.Net.Test.Common
{

internal sealed class Http3LoopbackStream : IDisposable
internal sealed class Http3LoopbackStream : IDisposable, IAsyncDisposable
{
private const int MaximumVarIntBytes = 8;
private const long VarIntMax = (1L << 62) - 1;
Expand Down Expand Up @@ -43,6 +43,10 @@ public void Dispose()
{
_stream.Dispose();
}

public ValueTask DisposeAsync() =>
_stream.DisposeAsync();

public async Task<HttpRequestData> HandleRequestAsync(HttpStatusCode statusCode = HttpStatusCode.OK, IList<HttpHeaderData> headers = null, string content = "")
{
HttpRequestData request = await ReadRequestDataAsync().ConfigureAwait(false);
Expand Down Expand Up @@ -116,12 +120,6 @@ public async Task SendFrameAsync(long frameType, ReadOnlyMemory<byte> framePaylo
await _stream.WriteAsync(framePayload).ConfigureAwait(false);
}

public async Task ShutdownSendAsync()
{
_stream.Shutdown();
await _stream.ShutdownWriteCompleted().ConfigureAwait(false);
}

static int EncodeHttpInteger(long longToEncode, Span<byte> buffer)
{
Debug.Assert(longToEncode >= 0);
Expand Down Expand Up @@ -226,9 +224,8 @@ public async Task SendResponseBodyAsync(byte[] content, bool isFinal = true)

if (isFinal)
{
await ShutdownSendAsync().ConfigureAwait(false);
await _stream.ShutdownCompleted().ConfigureAwait(false);
Dispose();
_stream.CompleteWrites();
await DisposeAsync();
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -519,7 +519,7 @@ private async Task ProcessServerStreamAsync(QuicStream stream)
NetEventSource.Info(this, $"Ignoring server-initiated stream of unknown type {unknownStreamType}.");
}

stream.AbortWrite((long)Http3ErrorCode.StreamCreationError);
stream.Abort((long)Http3ErrorCode.StreamCreationError, QuicAbortDirection.Read);
return;
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -91,7 +91,10 @@ public async ValueTask DisposeAsync()
if (!_disposed)
{
_disposed = true;

// TODO: use CloseAsync() with a cancellation token to prevent a DoS
await _stream.DisposeAsync().ConfigureAwait(false);

DisposeSyncHelper();
}
}
Expand Down Expand Up @@ -151,7 +154,7 @@ public async Task<HttpResponseMessage> SendAsync(CancellationToken cancellationT
}
else
{
_stream.Shutdown();
_stream.CompleteWrites();
}
}

Expand Down Expand Up @@ -262,7 +265,7 @@ public async Task<HttpResponseMessage> SendAsync(CancellationToken cancellationT

if (cancellationToken.IsCancellationRequested)
{
_stream.AbortWrite((long)Http3ErrorCode.RequestCancelled);
_stream.Abort((long)Http3ErrorCode.RequestCancelled);
throw new OperationCanceledException(ex.Message, ex, cancellationToken);
}
else
Expand All @@ -279,7 +282,7 @@ public async Task<HttpResponseMessage> SendAsync(CancellationToken cancellationT
}
catch (Exception ex)
{
_stream.AbortWrite((long)Http3ErrorCode.InternalError);
_stream.Abort((long)Http3ErrorCode.InternalError);
if (ex is HttpRequestException)
{
throw;
Expand Down Expand Up @@ -371,7 +374,7 @@ private async Task SendContentAsync(HttpContent content, CancellationToken cance
_sendBuffer.Discard(_sendBuffer.ActiveLength);
}

_stream.Shutdown();
_stream.CompleteWrites();
}

private async ValueTask WriteRequestContentAsync(ReadOnlyMemory<byte> buffer, CancellationToken cancellationToken)
Expand Down Expand Up @@ -776,7 +779,7 @@ private async ValueTask ReadHeadersAsync(long headersLength, CancellationToken c
// https://tools.ietf.org/html/draft-ietf-quic-http-24#section-4.1.1
if (headersLength > _headerBudgetRemaining)
{
_stream.AbortWrite((long)Http3ErrorCode.ExcessiveLoad);
_stream.Abort((long)Http3ErrorCode.ExcessiveLoad);
throw new HttpRequestException(SR.Format(SR.net_http_response_headers_exceeded_length, _connection.Pool.Settings._maxResponseHeadersLength * 1024L));
}

Expand Down Expand Up @@ -1113,11 +1116,11 @@ private void HandleReadResponseContentException(Exception ex, CancellationToken
_connection.Abort(ex);
throw new IOException(SR.net_http_client_execution_error, new HttpRequestException(SR.net_http_client_execution_error, ex));
case OperationCanceledException oce when oce.CancellationToken == cancellationToken:
_stream.AbortWrite((long)Http3ErrorCode.RequestCancelled);
_stream.Abort((long)Http3ErrorCode.RequestCancelled);
ExceptionDispatchInfo.Throw(ex); // Rethrow.
return; // Never reached.
default:
_stream.AbortWrite((long)Http3ErrorCode.InternalError);
_stream.Abort((long)Http3ErrorCode.InternalError);
throw new IOException(SR.net_http_client_execution_error, new HttpRequestException(SR.net_http_client_execution_error, ex));
}
}
Expand Down
35 changes: 23 additions & 12 deletions src/libraries/System.Net.Quic/System.Net.Quic.sln
Original file line number Diff line number Diff line change
@@ -1,4 +1,8 @@
Microsoft Visual Studio Solution File, Format Version 12.00

Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio Version 16
VisualStudioVersion = 16.0.31220.234
MinimumVisualStudioVersion = 10.0.40219.1
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "TestUtilities", "..\Common\tests\TestUtilities\TestUtilities.csproj", "{55C933AA-2735-4B38-A1DD-01A27467AB18}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.Win32.Registry", "..\Microsoft.Win32.Registry\ref\Microsoft.Win32.Registry.csproj", "{69CDCFD5-AA35-40D8-A437-ED1C06E9CA95}"
Expand All @@ -23,18 +27,9 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "ref", "ref", "{4BABFE90-C81
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{DAC0D00A-6EB0-4A72-94BB-EB90B3EE72A9}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "StreamConformanceTests", "..\Common\tests\StreamConformanceTests\StreamConformanceTests.csproj", "{CCE2D0B0-BDBE-4750-B215-2517286510EB}"
EndProject
Global
GlobalSection(NestedProjects) = preSolution
{55C933AA-2735-4B38-A1DD-01A27467AB18} = {BDA10542-BE94-4A73-9B5B-6BE5CE57F883}
{E8E7DD3A-EC3F-4472-9F70-B515A3D11038} = {BDA10542-BE94-4A73-9B5B-6BE5CE57F883}
{69CDCFD5-AA35-40D8-A437-ED1C06E9CA95} = {4BABFE90-C818-4772-9D2E-B92F69E1FCDF}
{D7A52855-C6DE-4FD0-9CAF-E55F292C69E5} = {4BABFE90-C818-4772-9D2E-B92F69E1FCDF}
{7BB8C50D-4770-42CB-BE15-76AD623A5AE8} = {4BABFE90-C818-4772-9D2E-B92F69E1FCDF}
{833418C5-FEC9-482F-A0D6-69DFC332C1B6} = {4BABFE90-C818-4772-9D2E-B92F69E1FCDF}
{E1CABA2F-48AD-49FA-B872-BEED78C51980} = {4BABFE90-C818-4772-9D2E-B92F69E1FCDF}
{4F87758B-D1AF-4DE3-A9A2-68B1558C02B7} = {DAC0D00A-6EB0-4A72-94BB-EB90B3EE72A9}
{9D56BA9E-1B0D-4320-9FE9-A2D326A32BE0} = {DAC0D00A-6EB0-4A72-94BB-EB90B3EE72A9}
EndGlobalSection
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Release|Any CPU = Release|Any CPU
Expand Down Expand Up @@ -76,10 +71,26 @@ Global
{E1CABA2F-48AD-49FA-B872-BEED78C51980}.Debug|Any CPU.Build.0 = Debug|Any CPU
{E1CABA2F-48AD-49FA-B872-BEED78C51980}.Release|Any CPU.ActiveCfg = Release|Any CPU
{E1CABA2F-48AD-49FA-B872-BEED78C51980}.Release|Any CPU.Build.0 = Release|Any CPU
{CCE2D0B0-BDBE-4750-B215-2517286510EB}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{CCE2D0B0-BDBE-4750-B215-2517286510EB}.Debug|Any CPU.Build.0 = Debug|Any CPU
{CCE2D0B0-BDBE-4750-B215-2517286510EB}.Release|Any CPU.ActiveCfg = Release|Any CPU
{CCE2D0B0-BDBE-4750-B215-2517286510EB}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
EndGlobalSection
GlobalSection(NestedProjects) = preSolution
{55C933AA-2735-4B38-A1DD-01A27467AB18} = {BDA10542-BE94-4A73-9B5B-6BE5CE57F883}
{69CDCFD5-AA35-40D8-A437-ED1C06E9CA95} = {4BABFE90-C818-4772-9D2E-B92F69E1FCDF}
{D7A52855-C6DE-4FD0-9CAF-E55F292C69E5} = {4BABFE90-C818-4772-9D2E-B92F69E1FCDF}
{4F87758B-D1AF-4DE3-A9A2-68B1558C02B7} = {DAC0D00A-6EB0-4A72-94BB-EB90B3EE72A9}
{E8E7DD3A-EC3F-4472-9F70-B515A3D11038} = {BDA10542-BE94-4A73-9B5B-6BE5CE57F883}
{7BB8C50D-4770-42CB-BE15-76AD623A5AE8} = {4BABFE90-C818-4772-9D2E-B92F69E1FCDF}
{9D56BA9E-1B0D-4320-9FE9-A2D326A32BE0} = {DAC0D00A-6EB0-4A72-94BB-EB90B3EE72A9}
{833418C5-FEC9-482F-A0D6-69DFC332C1B6} = {4BABFE90-C818-4772-9D2E-B92F69E1FCDF}
{E1CABA2F-48AD-49FA-B872-BEED78C51980} = {4BABFE90-C818-4772-9D2E-B92F69E1FCDF}
{CCE2D0B0-BDBE-4750-B215-2517286510EB} = {BDA10542-BE94-4A73-9B5B-6BE5CE57F883}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {4B59ACCA-7F0C-4062-AA79-B3D75EFACCCD}
EndGlobalSection
Expand Down
17 changes: 12 additions & 5 deletions src/libraries/System.Net.Quic/ref/System.Net.Quic.cs
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,14 @@

namespace System.Net.Quic
{
[System.FlagsAttribute]
public enum QuicAbortDirection
{
Read = 1,
Write = 2,
Both = 3,
Immediate = 7
}
public partial class QuicClientConnectionOptions : System.Net.Quic.QuicOptions
{
public QuicClientConnectionOptions() { }
Expand Down Expand Up @@ -87,11 +95,13 @@ internal QuicStream() { }
public override long Length { get { throw null; } }
public override long Position { get { throw null; } set { } }
public long StreamId { get { throw null; } }
public void AbortRead(long errorCode) { }
public void AbortWrite(long errorCode) { }
public void Abort(long errorCode, System.Net.Quic.QuicAbortDirection abortDirection = System.Net.Quic.QuicAbortDirection.Immediate) { }
public override System.IAsyncResult BeginRead(byte[] buffer, int offset, int count, System.AsyncCallback? callback, object? state) { throw null; }
public override System.IAsyncResult BeginWrite(byte[] buffer, int offset, int count, System.AsyncCallback? callback, object? state) { throw null; }
public System.Threading.Tasks.ValueTask CloseAsync(System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) { throw null; }
public void CompleteWrites() { }
protected override void Dispose(bool disposing) { }
public override System.Threading.Tasks.ValueTask DisposeAsync() { throw null; }
public override int EndRead(System.IAsyncResult asyncResult) { throw null; }
public override void EndWrite(System.IAsyncResult asyncResult) { }
public override void Flush() { }
Expand All @@ -102,9 +112,6 @@ public override void Flush() { }
public override System.Threading.Tasks.ValueTask<int> ReadAsync(System.Memory<byte> buffer, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) { throw null; }
public override long Seek(long offset, System.IO.SeekOrigin origin) { throw null; }
public override void SetLength(long value) { }
public void Shutdown() { }
public System.Threading.Tasks.ValueTask ShutdownCompleted(System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) { throw null; }
public System.Threading.Tasks.ValueTask ShutdownWriteCompleted(System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) { throw null; }
public override void Write(byte[] buffer, int offset, int count) { }
public override void Write(System.ReadOnlySpan<byte> buffer) { }
public System.Threading.Tasks.ValueTask WriteAsync(System.Buffers.ReadOnlySequence<byte> buffers, bool endStream, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) { throw null; }
Expand Down
57 changes: 28 additions & 29 deletions src/libraries/System.Net.Quic/src/Resources/Strings.resx
Original file line number Diff line number Diff line change
@@ -1,17 +1,17 @@
<?xml version="1.0" encoding="utf-8"?>
<root>
<!--
Microsoft ResX Schema

<!--
Microsoft ResX Schema
Version 2.0

The primary goals of this format is to allow a simple XML format
that is mostly human readable. The generation and parsing of the
various data types are done through the TypeConverter classes
The primary goals of this format is to allow a simple XML format
that is mostly human readable. The generation and parsing of the
various data types are done through the TypeConverter classes
associated with the data types.

Example:

... ado.net/XML headers & schema ...
<resheader name="resmimetype">text/microsoft-resx</resheader>
<resheader name="version">2.0</resheader>
Expand All @@ -26,36 +26,36 @@
<value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
<comment>This is a comment</comment>
</data>

There are any number of "resheader" rows that contain simple
There are any number of "resheader" rows that contain simple
name/value pairs.

Each data row contains a name, and value. The row also contains a
type or mimetype. Type corresponds to a .NET class that support
text/value conversion through the TypeConverter architecture.
Classes that don't support this are serialized and stored with the
Each data row contains a name, and value. The row also contains a
type or mimetype. Type corresponds to a .NET class that support
text/value conversion through the TypeConverter architecture.
Classes that don't support this are serialized and stored with the
mimetype set.

The mimetype is used for serialized objects, and tells the
ResXResourceReader how to depersist the object. This is currently not
The mimetype is used for serialized objects, and tells the
ResXResourceReader how to depersist the object. This is currently not
extensible. For a given mimetype the value must be set accordingly:

Note - application/x-microsoft.net.object.binary.base64 is the format
that the ResXResourceWriter will generate, however the reader can
Note - application/x-microsoft.net.object.binary.base64 is the format
that the ResXResourceWriter will generate, however the reader can
read any of the formats listed below.

mimetype: application/x-microsoft.net.object.binary.base64
value : The object must be serialized with
value : The object must be serialized with
: System.Runtime.Serialization.Formatters.Binary.BinaryFormatter
: and then encoded with base64 encoding.

mimetype: application/x-microsoft.net.object.soap.base64
value : The object must be serialized with
value : The object must be serialized with
: System.Runtime.Serialization.Formatters.Soap.SoapFormatter
: and then encoded with base64 encoding.

mimetype: application/x-microsoft.net.object.bytearray.base64
value : The object must be serialized into a byte array
value : The object must be serialized into a byte array
: using a System.ComponentModel.TypeConverter
: and then encoded with base64 encoding.
-->
Expand Down Expand Up @@ -150,5 +150,4 @@
<data name="net_quic_writing_notallowed" xml:space="preserve">
<value>Writing is not allowed on stream.</value>
</data>
</root>

</root>
Loading