dotnet / dotnet/runtime

QuicStream default error code API limitation

Open
#121,910 5 comments 0 reactions 0 assignees View on GitHub
api-suggestion area-System.Net.Quic
Dominant language
C#
Stars
18.3k
Forks
5.6k
PR merge metrics
PR metrics pending

Description

Currently, we can set a default stream error code for all `QuicStream` instances associated with a `QuicConnection`. This default value must be set using `QuicConnectionOptions.DefaultStreamErrorCode`.

In some scenarios, it may be useful to be able to set default stream error codes per stream and also to be able to modify the value during the lifetime of the object. The following paragraph will explain why.

The `QuicConnectionOptions.DefaultStreamErrorCode` is used by `System.Net.Http` when setting up a QUIC connection for HTTP/3. It uses the value `(long)Http3ErrorCode.RequestCancelled`. This is built on the assumption, that when the stream's read/write side needs to be closed internally, it always means an HTTP/3 request is cancelled. This assumption is true now, but we might not be able to rely on it the future. If support for WebTransport over HTTP/3 is added, we run into problems. The way the protocol works is that a QUIC connection is shared between the HTTP/3 protocol and the WebTransport over HTTP/3 protocol. If we have a QUIC stream that is used by WebTransport over HTTP/3 and we need to close the read/write side internally, the HTTP/3 error code is used, which is an invalid stream error code in the WebTransport over HTTP/3 protocol. Currently, there are two scenarios when this might happen. The first one is during disposal of a WebTransport stream, which works as a wrapper around a QUIC stream. In this scenario the solution is simple - we just call `QuicStream.Abort` with the desired error code before we call `QuicStream.Dispose`. The second scenario when the default error code is applied is during operation cancellations. Consider the following code:

```cs
WebTransportStream stream = ...;
byte[] data = ...;
CancellationTokenSource cts = new();
ValueTask writeTask = stream.WriteAsync(data, cts.Token);
cts.Cancel();
```

If `WebTransportStream.WriteAsync` just passed the cancellation token to `QuicStream.WriteAsync`, the result would be that the write side is closed using the HTTP/3 error code. This behavior is invalid in WebTransport over HTTP/3. Therefore, the WebTransport over HTTP/3 implementation must work around this as follows:

```cs
class WebTransportStream : Stream {
public override ValueTask WriteAsync(byte[] data, CancellationToken cancellationToken) {
CancellationTokenRegistration ctr? = null;
if (cancellationToken.CanBeCanceled)
{
ctr = cancellationToken.Register(() =>
{
_quicStream.Abort(abortDirection, _remappedDefaultStreamErrorCode);
});
}
try
{
await _quicStream.WriteAsync(buffer.AsMemory(offset, count)).ConfigureAwait(false);
}
catch (QuicException ex)
{
/ * exception handling * /
}
finally
{
if (ctr.HasValue)
{
await ctr.Value.DisposeAsync().ConfigureAwait(false);
}
}
}
}
```

The same handling of the cancellation token is required for all async operations that support a cancellation token. If we were able to assign a default stream error code for every instance of a `QuicStream`, the code would be much simpler.

Contributor guide

Open the contributing guide

Assessment

This issue has not been assessed yet.

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.