--- /dev/null
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+
+using System.Net.Quic;
+
+namespace Microsoft.Quic;
+
+internal unsafe partial struct QUIC_NEW_CONNECTION_INFO
+{
+ public override string ToString()
+ => $"{{ {nameof(QuicVersion)} = {QuicVersion}, {nameof(LocalAddress)} = {LocalAddress->ToIPEndPoint()}, {nameof(RemoteAddress)} = {RemoteAddress->ToIPEndPoint()} }}";
+}
+
+internal unsafe partial struct QUIC_LISTENER_EVENT
+{
+ public override string ToString()
+ => Type switch
+ {
+ QUIC_LISTENER_EVENT_TYPE.NEW_CONNECTION
+ => $"{{ {nameof(NEW_CONNECTION.Info)} = {{ {nameof(QUIC_NEW_CONNECTION_INFO.QuicVersion)} = {NEW_CONNECTION.Info->QuicVersion}, {nameof(QUIC_NEW_CONNECTION_INFO.LocalAddress)} = {NEW_CONNECTION.Info->LocalAddress->ToIPEndPoint()}, {nameof(QUIC_NEW_CONNECTION_INFO.RemoteAddress)} = {NEW_CONNECTION.Info->RemoteAddress->ToIPEndPoint()} }} }}",
+ _ => string.Empty
+ };
+}
+
+internal unsafe partial struct QUIC_CONNECTION_EVENT
+{
+ public override string ToString()
+ => Type switch
+ {
+ QUIC_CONNECTION_EVENT_TYPE.CONNECTED
+ => $"{{ {nameof(CONNECTED.SessionResumed)} = {CONNECTED.SessionResumed} }}",
+ QUIC_CONNECTION_EVENT_TYPE.SHUTDOWN_INITIATED_BY_TRANSPORT
+ => $"{{ {nameof(SHUTDOWN_INITIATED_BY_TRANSPORT.Status)} = {SHUTDOWN_INITIATED_BY_TRANSPORT.Status}, {nameof(SHUTDOWN_INITIATED_BY_TRANSPORT.ErrorCode)} = {SHUTDOWN_INITIATED_BY_TRANSPORT.ErrorCode} }}",
+ QUIC_CONNECTION_EVENT_TYPE.SHUTDOWN_INITIATED_BY_PEER
+ => $"{{ {nameof(SHUTDOWN_INITIATED_BY_PEER.ErrorCode)} = {SHUTDOWN_INITIATED_BY_PEER.ErrorCode} }}",
+ QUIC_CONNECTION_EVENT_TYPE.SHUTDOWN_COMPLETE
+ => $"{{ {nameof(SHUTDOWN_COMPLETE.HandshakeCompleted)} = {SHUTDOWN_COMPLETE.HandshakeCompleted}, {nameof(SHUTDOWN_COMPLETE.PeerAcknowledgedShutdown)} = {SHUTDOWN_COMPLETE.PeerAcknowledgedShutdown}, {nameof(SHUTDOWN_COMPLETE.AppCloseInProgress)} = {SHUTDOWN_COMPLETE.AppCloseInProgress} }}",
+ QUIC_CONNECTION_EVENT_TYPE.LOCAL_ADDRESS_CHANGED
+ => $"{{ {nameof(LOCAL_ADDRESS_CHANGED.Address)} = {LOCAL_ADDRESS_CHANGED.Address->ToIPEndPoint()} }}",
+ QUIC_CONNECTION_EVENT_TYPE.PEER_ADDRESS_CHANGED
+ => $"{{ {nameof(PEER_ADDRESS_CHANGED.Address)} = {PEER_ADDRESS_CHANGED.Address->ToIPEndPoint()} }}",
+ QUIC_CONNECTION_EVENT_TYPE.PEER_STREAM_STARTED
+ => $"{{ {nameof(PEER_STREAM_STARTED.Flags)} = {PEER_STREAM_STARTED.Flags} }}",
+ QUIC_CONNECTION_EVENT_TYPE.PEER_CERTIFICATE_RECEIVED
+ => $"{{ {nameof(PEER_CERTIFICATE_RECEIVED.DeferredStatus)} = {PEER_CERTIFICATE_RECEIVED.DeferredStatus}, {nameof(PEER_CERTIFICATE_RECEIVED.DeferredErrorFlags)} = {PEER_CERTIFICATE_RECEIVED.DeferredErrorFlags} }}",
+ _ => string.Empty
+ };
+}
+
+internal unsafe partial struct QUIC_STREAM_EVENT
+{
+ public override string ToString()
+ => Type switch
+ {
+ QUIC_STREAM_EVENT_TYPE.START_COMPLETE
+ => $"{{ {nameof(START_COMPLETE.Status)} = {START_COMPLETE.Status}, {nameof(START_COMPLETE.ID)} = {START_COMPLETE.ID}, {nameof(START_COMPLETE.PeerAccepted)} = {START_COMPLETE.PeerAccepted} }}",
+ QUIC_STREAM_EVENT_TYPE.RECEIVE
+ => $"{{ {nameof(RECEIVE.AbsoluteOffset)} = {RECEIVE.AbsoluteOffset}, {nameof(RECEIVE.TotalBufferLength)} = {RECEIVE.TotalBufferLength}, {nameof(RECEIVE.Flags)} = {RECEIVE.Flags} }}",
+ QUIC_STREAM_EVENT_TYPE.SEND_COMPLETE
+ => $"{{ {nameof(SEND_COMPLETE.Canceled)} = {SEND_COMPLETE.Canceled} }}",
+ QUIC_STREAM_EVENT_TYPE.PEER_SEND_ABORTED
+ => $"{{ {nameof(PEER_SEND_ABORTED.ErrorCode)} = {PEER_SEND_ABORTED.ErrorCode} }}",
+ QUIC_STREAM_EVENT_TYPE.PEER_RECEIVE_ABORTED
+ => $"{{ {nameof(PEER_RECEIVE_ABORTED.ErrorCode)} = {PEER_RECEIVE_ABORTED.ErrorCode} }}",
+ QUIC_STREAM_EVENT_TYPE.SEND_SHUTDOWN_COMPLETE
+ => $"{{ {nameof(SEND_SHUTDOWN_COMPLETE.Graceful)} = {SEND_SHUTDOWN_COMPLETE.Graceful} }}",
+ QUIC_STREAM_EVENT_TYPE.SHUTDOWN_COMPLETE
+ => $"{{ {nameof(SHUTDOWN_COMPLETE.ConnectionShutdown)} = {SHUTDOWN_COMPLETE.ConnectionShutdown}, {nameof(SHUTDOWN_COMPLETE.ConnectionShutdownByApp)} = {SHUTDOWN_COMPLETE.ConnectionShutdownByApp}, {nameof(SHUTDOWN_COMPLETE.ConnectionClosedRemotely)} = {SHUTDOWN_COMPLETE.ConnectionClosedRemotely}, {nameof(SHUTDOWN_COMPLETE.ConnectionErrorCode)} = {SHUTDOWN_COMPLETE.ConnectionErrorCode}, {nameof(SHUTDOWN_COMPLETE.ConnectionCloseStatus)} = {SHUTDOWN_COMPLETE.ConnectionCloseStatus} }}",
+ _ => string.Empty
+ };
+}
return new Internals.SocketAddress(addressFamilyOverride ?? SocketAddressPal.GetAddressFamily(addressBytes), addressBytes).GetIPEndPoint();
}
- internal static unsafe QuicAddr ToQuicAddr(this IPEndPoint iPEndPoint)
+ internal static unsafe QuicAddr ToQuicAddr(this IPEndPoint ipEndPoint)
{
// TODO: is the layout same for SocketAddress.Buffer and QuicAddr on all platforms?
QuicAddr result = default;
Span<byte> rawAddress = MemoryMarshal.AsBytes(MemoryMarshal.CreateSpan(ref result, 1));
- Internals.SocketAddress address = IPEndPointExtensions.Serialize(iPEndPoint);
+ Internals.SocketAddress address = IPEndPointExtensions.Serialize(ipEndPoint);
Debug.Assert(address.Size <= rawAddress.Length);
address.Buffer.AsSpan(0, address.Size).CopyTo(rawAddress);
/// </summary>
public bool IsCompleted => (State)Volatile.Read(ref Unsafe.As<State, byte>(ref _state)) == State.Completed;
+ // TODO: Revisit this with https://github.com/dotnet/runtime/issues/79818 and https://github.com/dotnet/runtime/issues/79911
+ public bool KeepAliveReleased
+ {
+ get
+ {
+ lock (this)
+ {
+ return !_keepAlive.IsAllocated;
+ }
+ }
+ }
+
/// <summary>
/// Tries to get a value task representing this task source. If this task source is <see cref="State.None"/>, it'll also transition it into <see cref="State.Awaiting"/> state.
/// It prevents concurrent operations from being invoked since it'll return <c>false</c> if the task source was already in <see cref="State.Awaiting"/> state.
// Unblock the current task source and in case of a final also the final task source.
if (exception is not null)
{
- // Set up the exception stack strace for the caller.
+ // Set up the exception stack trace for the caller.
exception = exception.StackTrace is null ? ExceptionDispatchInfo.SetCurrentStackTrace(exception) : exception;
if (state == State.None ||
state == State.Awaiting)
// 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.Net.Security;
using System.Net.Sockets;
using System.Runtime.CompilerServices;
internal ValueTask FinishHandshakeAsync(QuicServerConnectionOptions options, string targetHost, CancellationToken cancellationToken = default)
{
- ObjectDisposedException.ThrowIf(_disposed == 1, this);
-
if (_connectedTcs.TryInitialize(out ValueTask valueTask, this, cancellationToken))
{
_canAccept = options.MaxInboundBidirectionalStreams > 0 || options.MaxInboundUnidirectionalStreams > 0;
if (NetEventSource.Log.IsEnabled())
{
- NetEventSource.Info(this, $"{this} Received event CONNECTED {LocalEndPoint} -> {RemoteEndPoint}");
+ NetEventSource.Info(this, $"{this} Connection connected {LocalEndPoint} -> {RemoteEndPoint} for {_negotiatedApplicationProtocol} protocol");
}
-
_connectedTcs.TrySetResult();
return QUIC_STATUS_SUCCESS;
}
private unsafe int HandleEventShutdownInitiatedByTransport(ref SHUTDOWN_INITIATED_BY_TRANSPORT_DATA data)
{
- if (NetEventSource.Log.IsEnabled())
- {
- NetEventSource.Info(this, $"{this} Received event SHUTDOWN_INITIATED_BY_TRANSPORT with {nameof(data.Status)}={data.Status}");
- }
-
// TODO: we should propagate transport error code.
// https://github.com/dotnet/runtime/issues/72666
Exception exception = ExceptionDispatchInfo.SetCurrentStackTrace(ThrowHelper.GetExceptionForMsQuicStatus(data.Status));
}
private unsafe int HandleEventShutdownInitiatedByPeer(ref SHUTDOWN_INITIATED_BY_PEER_DATA data)
{
- if (NetEventSource.Log.IsEnabled())
- {
- NetEventSource.Info(this, $"{this} Received event SHUTDOWN_INITIATED_BY_PEER_DATA with {nameof(data.ErrorCode)}={data.ErrorCode}");
- }
-
_acceptQueue.Writer.TryComplete(ExceptionDispatchInfo.SetCurrentStackTrace(ThrowHelper.GetConnectionAbortedException((long)data.ErrorCode)));
return QUIC_STATUS_SUCCESS;
}
private unsafe int HandleEventShutdownComplete()
{
- if (NetEventSource.Log.IsEnabled())
- {
- NetEventSource.Info(this, $"{this} Received event SHUTDOWN_INITIATED_BY_PEER_DATA");
- }
-
- _acceptQueue.Writer.TryComplete(ExceptionDispatchInfo.SetCurrentStackTrace(ThrowHelper.GetOperationAbortedException()));
+ Exception exception = ExceptionDispatchInfo.SetCurrentStackTrace(ThrowHelper.GetOperationAbortedException());
+ _acceptQueue.Writer.TryComplete(exception);
+ _connectedTcs.TrySetException(exception);
_shutdownTcs.TrySetResult();
return QUIC_STATUS_SUCCESS;
}
private unsafe int HandleEventLocalAddressChanged(ref LOCAL_ADDRESS_CHANGED_DATA data)
{
_localEndPoint = data.Address->ToIPEndPoint();
- if (NetEventSource.Log.IsEnabled())
- {
- NetEventSource.Info(this, $"{this} Received event LOCAL_ADDRESS_CHANGED with {nameof(data.Address)}={_localEndPoint}");
- }
-
return QUIC_STATUS_SUCCESS;
}
private unsafe int HandleEventPeerAddressChanged(ref PEER_ADDRESS_CHANGED_DATA data)
{
_remoteEndPoint = data.Address->ToIPEndPoint();
- if (NetEventSource.Log.IsEnabled())
- {
- NetEventSource.Info(this, $"{this} Received event LOCAL_ADDRESS_CHANGED with {nameof(data.Address)}={_remoteEndPoint}");
- }
-
return QUIC_STATUS_SUCCESS;
}
private unsafe int HandleEventPeerStreamStarted(ref PEER_STREAM_STARTED_DATA data)
{
- if (NetEventSource.Log.IsEnabled())
- {
- NetEventSource.Info(this, $"{this} Received event PEER_STREAM_STARTED");
- }
-
QuicStream stream = new QuicStream(_handle, data.Stream, data.Flags, _defaultStreamErrorCode);
if (!_acceptQueue.Writer.TryWrite(stream))
{
}
private unsafe int HandleEventPeerCertificateReceived(ref PEER_CERTIFICATE_RECEIVED_DATA data)
{
- if (NetEventSource.Log.IsEnabled())
- {
- NetEventSource.Info(this, $"{this} Received event PEER_CERTIFICATE_RECEIVED_DATA");
- }
-
try
{
return _sslConnectionOptions.ValidateCertificate((QUIC_BUFFER*)data.Certificate, (QUIC_BUFFER*)data.Chain, out _remoteCertificate);
}
}
- private int HandleConnectionEvent(QUIC_CONNECTION_EVENT_TYPE type)
- {
- if (NetEventSource.Log.IsEnabled())
- {
- NetEventSource.Info(this, $"{this} Received event {type}");
- }
-
- return QUIC_STATUS_SUCCESS;
- }
-
private unsafe int HandleConnectionEvent(ref QUIC_CONNECTION_EVENT connectionEvent)
=> connectionEvent.Type switch
{
QUIC_CONNECTION_EVENT_TYPE.PEER_ADDRESS_CHANGED => HandleEventPeerAddressChanged(ref connectionEvent.PEER_ADDRESS_CHANGED),
QUIC_CONNECTION_EVENT_TYPE.PEER_STREAM_STARTED => HandleEventPeerStreamStarted(ref connectionEvent.PEER_STREAM_STARTED),
QUIC_CONNECTION_EVENT_TYPE.PEER_CERTIFICATE_RECEIVED => HandleEventPeerCertificateReceived(ref connectionEvent.PEER_CERTIFICATE_RECEIVED),
- _ => HandleConnectionEvent(connectionEvent.Type),
+ _ => QUIC_STATUS_SUCCESS,
};
#pragma warning disable CS3016
try
{
+ // Process the event.
+ if (NetEventSource.Log.IsEnabled())
+ {
+ NetEventSource.Info(instance, $"{instance} Received event {connectionEvent->Type} {connectionEvent->ToString()}");
+ }
return instance.HandleConnectionEvent(ref *connectionEvent);
}
catch (Exception ex)
// Wait for SHUTDOWN_COMPLETE, the last event, so that all resources can be safely released.
await valueTask.ConfigureAwait(false);
+ Debug.Assert(_connectedTcs.IsCompleted);
_handle.Dispose();
_configuration?.Dispose();
// Process the event.
if (NetEventSource.Log.IsEnabled())
{
- NetEventSource.Info(instance, $"{instance} Received event {listenerEvent->Type}");
+ NetEventSource.Info(instance, $"{instance} Received event {listenerEvent->Type} {listenerEvent->ToString()}");
}
return instance.HandleListenerEvent(ref *listenerEvent);
}
// 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.IO;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using static System.Net.Quic.MsQuicHelpers;
using static Microsoft.Quic.MsQuic;
-using START_COMPLETE = Microsoft.Quic.QUIC_STREAM_EVENT._Anonymous_e__Union._START_COMPLETE_e__Struct;
-using RECEIVE = Microsoft.Quic.QUIC_STREAM_EVENT._Anonymous_e__Union._RECEIVE_e__Struct;
-using SEND_COMPLETE = Microsoft.Quic.QUIC_STREAM_EVENT._Anonymous_e__Union._SEND_COMPLETE_e__Struct;
-using PEER_SEND_ABORTED = Microsoft.Quic.QUIC_STREAM_EVENT._Anonymous_e__Union._PEER_SEND_ABORTED_e__Struct;
-using PEER_RECEIVE_ABORTED = Microsoft.Quic.QUIC_STREAM_EVENT._Anonymous_e__Union._PEER_RECEIVE_ABORTED_e__Struct;
-using SEND_SHUTDOWN_COMPLETE = Microsoft.Quic.QUIC_STREAM_EVENT._Anonymous_e__Union._SEND_SHUTDOWN_COMPLETE_e__Struct;
-using SHUTDOWN_COMPLETE = Microsoft.Quic.QUIC_STREAM_EVENT._Anonymous_e__Union._SHUTDOWN_COMPLETE_e__Struct;
+using START_COMPLETE_DATA = Microsoft.Quic.QUIC_STREAM_EVENT._Anonymous_e__Union._START_COMPLETE_e__Struct;
+using RECEIVE_DATA = Microsoft.Quic.QUIC_STREAM_EVENT._Anonymous_e__Union._RECEIVE_e__Struct;
+using SEND_COMPLETE_DATA = Microsoft.Quic.QUIC_STREAM_EVENT._Anonymous_e__Union._SEND_COMPLETE_e__Struct;
+using PEER_SEND_ABORTED_DATA = Microsoft.Quic.QUIC_STREAM_EVENT._Anonymous_e__Union._PEER_SEND_ABORTED_e__Struct;
+using PEER_RECEIVE_ABORTED_DATA = Microsoft.Quic.QUIC_STREAM_EVENT._Anonymous_e__Union._PEER_RECEIVE_ABORTED_e__Struct;
+using SEND_SHUTDOWN_COMPLETE_DATA = Microsoft.Quic.QUIC_STREAM_EVENT._Anonymous_e__Union._SEND_SHUTDOWN_COMPLETE_e__Struct;
+using SHUTDOWN_COMPLETE_DATA = Microsoft.Quic.QUIC_STREAM_EVENT._Anonymous_e__Union._SHUTDOWN_COMPLETE_e__Struct;
namespace System.Net.Quic;
/// <returns>An asynchronous task that completes with the opened <see cref="QuicStream" />.</returns>
internal ValueTask StartAsync(CancellationToken cancellationToken = default)
{
- ObjectDisposedException.ThrowIf(_disposed == 1, this);
-
_startedTcs.TryInitialize(out ValueTask valueTask, this, cancellationToken);
{
unsafe
}
}
- private unsafe int HandleEventStartComplete(ref START_COMPLETE data)
+ private unsafe int HandleEventStartComplete(ref START_COMPLETE_DATA data)
{
- if (NetEventSource.Log.IsEnabled())
- {
- NetEventSource.Info(this, $"{this} Received event START_COMPLETE with {nameof(data.ID)}={data.ID}, {nameof(data.Status)}={data.Status} and {nameof(data.PeerAccepted)}={data.PeerAccepted}");
- }
-
_id = unchecked((long)data.ID);
if (StatusSucceeded(data.Status))
{
return QUIC_STATUS_SUCCESS;
}
- private unsafe int HandleEventReceive(ref RECEIVE data)
+ private unsafe int HandleEventReceive(ref RECEIVE_DATA data)
{
ulong totalCopied = (ulong)_receiveBuffers.CopyFrom(
new ReadOnlySpan<QUIC_BUFFER>(data.Buffers, (int)data.BufferCount),
(int)data.TotalBufferLength,
data.Flags.HasFlag(QUIC_RECEIVE_FLAGS.FIN));
- if (NetEventSource.Log.IsEnabled())
- {
- NetEventSource.Info(this, $"{this} Received event RECEIVE with {nameof(data.BufferCount)}={data.BufferCount}, {nameof(data.TotalBufferLength)}={data.TotalBufferLength} and {nameof(totalCopied)}={totalCopied}");
- }
-
if (totalCopied < data.TotalBufferLength)
{
Volatile.Write(ref _receivedNeedsEnable, 1);
data.TotalBufferLength = totalCopied;
return (_receiveBuffers.HasCapacity() && Interlocked.CompareExchange(ref _receivedNeedsEnable, 0, 1) == 1) ? QUIC_STATUS_CONTINUE : QUIC_STATUS_SUCCESS;
}
- private unsafe int HandleEventSendComplete(ref SEND_COMPLETE data)
+ private unsafe int HandleEventSendComplete(ref SEND_COMPLETE_DATA data)
{
- if (NetEventSource.Log.IsEnabled())
- {
- NetEventSource.Info(this, $"{this} Received event SEND_COMPLETE with {nameof(data.Canceled)}={data.Canceled}");
- }
-
// In case of cancellation, the task from _sendTcs is finished before the aborting. It is technically possible for subsequent WriteAsync to grab the next task
// from _sendTcs and start executing before SendComplete event occurs for the previous (canceled) write
lock (_sendBuffersLock)
}
private unsafe int HandleEventPeerSendShutdown()
{
- if (NetEventSource.Log.IsEnabled())
- {
- NetEventSource.Info(this, $"{this} Received event PEER_SEND_SHUTDOWN.");
- }
-
// Same as RECEIVE with FIN flag. Remember that no more RECEIVE events will come.
// Don't set the task to its final state yet, but wait for all the buffered data to get consumed first.
_receiveBuffers.SetFinal();
_receiveTcs.TrySetResult();
return QUIC_STATUS_SUCCESS;
}
- private unsafe int HandleEventPeerSendAborted(ref PEER_SEND_ABORTED data)
+ private unsafe int HandleEventPeerSendAborted(ref PEER_SEND_ABORTED_DATA data)
{
- if (NetEventSource.Log.IsEnabled())
- {
- NetEventSource.Info(this, $"{this} Received event PEER_SEND_ABORTED with {nameof(data.ErrorCode)}={data.ErrorCode}");
- }
-
_receiveTcs.TrySetException(ThrowHelper.GetStreamAbortedException((long)data.ErrorCode), final: true);
return QUIC_STATUS_SUCCESS;
}
- private unsafe int HandleEventPeerReceiveAborted(ref PEER_RECEIVE_ABORTED data)
+ private unsafe int HandleEventPeerReceiveAborted(ref PEER_RECEIVE_ABORTED_DATA data)
{
- if (NetEventSource.Log.IsEnabled())
- {
- NetEventSource.Info(this, $"{this} Received event PEER_RECEIVE_ABORTED with {nameof(data.ErrorCode)}={data.ErrorCode}");
- }
-
_sendTcs.TrySetException(ThrowHelper.GetStreamAbortedException((long)data.ErrorCode), final: true);
return QUIC_STATUS_SUCCESS;
}
- private unsafe int HandleEventSendShutdownComplete(ref SEND_SHUTDOWN_COMPLETE data)
+ private unsafe int HandleEventSendShutdownComplete(ref SEND_SHUTDOWN_COMPLETE_DATA data)
{
- if (NetEventSource.Log.IsEnabled())
- {
- NetEventSource.Info(this, $"{this} Received event PEER_RECEIVE_ABORTED with {nameof(data.Graceful)}={data.Graceful}");
- }
-
if (data.Graceful != 0)
{
_sendTcs.TrySetResult(final: true);
// If Graceful == 0, we either aborted write, received PEER_RECEIVE_ABORTED or will receive SHUTDOWN_COMPLETE(ConnectionClose) later, all of which completes the _sendTcs.
return QUIC_STATUS_SUCCESS;
}
- private unsafe int HandleEventShutdownComplete(ref SHUTDOWN_COMPLETE data)
+ private unsafe int HandleEventShutdownComplete(ref SHUTDOWN_COMPLETE_DATA data)
{
- if (NetEventSource.Log.IsEnabled())
- {
- NetEventSource.Info(this, $"{this} Received event SHUTDOWN_COMPLETE with {nameof(data.ConnectionShutdown)}={data.ConnectionShutdown}");
- }
-
if (data.ConnectionShutdown != 0)
{
bool shutdownByApp = data.ConnectionShutdownByApp != 0;
}
private unsafe int HandleEventPeerAccepted()
{
- if (NetEventSource.Log.IsEnabled())
- {
- NetEventSource.Info(this, $"{this} Received event PEER_ACCEPTED");
- }
-
_startedTcs.TrySetResult();
return QUIC_STATUS_SUCCESS;
}
- private int HandleStreamEvent(QUIC_STREAM_EVENT_TYPE type)
- {
- if (NetEventSource.Log.IsEnabled())
- {
- NetEventSource.Info(this, $"{this} Received event {type}");
- }
-
- return QUIC_STATUS_SUCCESS;
- }
-
private unsafe int HandleStreamEvent(ref QUIC_STREAM_EVENT streamEvent)
=> streamEvent.Type switch
{
QUIC_STREAM_EVENT_TYPE.SEND_SHUTDOWN_COMPLETE => HandleEventSendShutdownComplete(ref streamEvent.SEND_SHUTDOWN_COMPLETE),
QUIC_STREAM_EVENT_TYPE.SHUTDOWN_COMPLETE => HandleEventShutdownComplete(ref streamEvent.SHUTDOWN_COMPLETE),
QUIC_STREAM_EVENT_TYPE.PEER_ACCEPTED => HandleEventPeerAccepted(),
- _ => HandleStreamEvent(streamEvent.Type)
+ _ => QUIC_STATUS_SUCCESS
};
#pragma warning disable CS3016
try
{
+ // Process the event.
+ if (NetEventSource.Log.IsEnabled())
+ {
+ NetEventSource.Info(instance, $"{instance} Received event {streamEvent->Type} {streamEvent->ToString()}");
+ }
return instance.HandleStreamEvent(ref *streamEvent);
}
catch (Exception ex)
// Wait for SHUTDOWN_COMPLETE, the last event, so that all resources can be safely released.
await valueTask.ConfigureAwait(false);
+ Debug.Assert(_startedTcs.IsCompleted);
+ // TODO: Revisit this with https://github.com/dotnet/runtime/issues/79818 and https://github.com/dotnet/runtime/issues/79911
+ Debug.Assert(_receiveTcs.KeepAliveReleased);
+ Debug.Assert(_sendTcs.KeepAliveReleased);
_handle.Dispose();
lock (_sendBuffersLock)
using Microsoft.DotNet.XUnitExtensions;
using Xunit;
using Xunit.Abstractions;
+using TestUtilities;
namespace System.Net.Quic.Tests
{
_certificates = setup;
}
+ [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsNotMonoRuntime))]
+ public async Task QuicRootedObjectGetReleased()
+ {
+ async Task<(WeakReference<QuicListener>, WeakReference<QuicConnection>, WeakReference<QuicConnection>, WeakReference<QuicStream>, WeakReference<QuicStream>)> GetWeakReferencesAsync()
+ {
+ // Set up all objects, keep their weak reference.
+ QuicListener listener = await CreateQuicListener();
+ WeakReference<QuicListener> wrListener = new WeakReference<QuicListener>(listener);
+
+ var (clientConnection, serverConnection) = await CreateConnectedQuicConnection(listener);
+ WeakReference<QuicConnection> wrClientConnection = new WeakReference<QuicConnection>(clientConnection);
+ WeakReference<QuicConnection> wrServerConnection = new WeakReference<QuicConnection>(serverConnection);
+
+ QuicStream clientStream = await clientConnection.OpenOutboundStreamAsync(QuicStreamType.Bidirectional);
+ await clientStream.WriteAsync(new byte[5], completeWrites: true);
+
+ QuicStream serverStream = await serverConnection.AcceptInboundStreamAsync();
+ await serverStream.WriteAsync(new byte[10], completeWrites: true);
+
+ WeakReference<QuicStream> wrClientStream = new WeakReference<QuicStream>(clientStream);
+ WeakReference<QuicStream> wrServerStream = new WeakReference<QuicStream>(serverStream);
+
+ while (!clientStream.ReadsClosed.IsCompleted)
+ {
+ int bytes = await clientStream.ReadAsync(new byte[10]);
+ if (bytes == 0)
+ {
+ break;
+ }
+ }
+ while (!serverStream.ReadsClosed.IsCompleted)
+ {
+ int bytes = await serverStream.ReadAsync(new byte[10]);
+ if (bytes == 0)
+ {
+ break;
+ }
+ }
+
+ // Dispose everything and check if all weak references are dead.
+ await clientStream.DisposeAsync();
+ await serverStream.DisposeAsync();
+ await clientConnection.DisposeAsync();
+ await serverConnection.DisposeAsync();
+ await listener.DisposeAsync();
+
+ return (wrListener, wrClientConnection, wrServerConnection, wrClientStream, wrServerStream);
+ }
+
+ var (wrListener, wrClientConnection, wrServerConnection, wrClientStream, wrServerStream) = await GetWeakReferencesAsync();
+
+ for (int i = 0; i < 20; ++i)
+ {
+ GC.Collect();
+ GC.WaitForPendingFinalizers();
+
+ await Task.Delay(100 * i);
+
+ if (TestWeakReferences())
+ {
+ continue;
+ }
+ break;
+
+ bool TestWeakReferences()
+ => wrListener.TryGetTarget(out _) ||
+ wrClientConnection.TryGetTarget(out _) ||
+ wrServerConnection.TryGetTarget(out _) ||
+ wrClientStream.TryGetTarget(out _) ||
+ wrServerStream.TryGetTarget(out _);
+ }
+
+ Assert.False(wrListener.TryGetTarget(out _));
+ Assert.False(wrClientConnection.TryGetTarget(out _));
+ Assert.False(wrServerConnection.TryGetTarget(out _));
+ Assert.False(wrClientStream.TryGetTarget(out _));
+ Assert.False(wrServerStream.TryGetTarget(out _));
+ }
+
+ [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsNotMonoRuntime))]
+ public async Task QuicRootedConnectionGetsReleased_ConnectFails()
+ {
+ WeakReference<QuicConnection> wrServerConnection = default;
+ // Set up all objects, keep their weak reference.
+ var listenerOptions = CreateQuicListenerOptions();
+ listenerOptions.ConnectionOptionsCallback = (connection, _, _) =>
+ {
+ wrServerConnection = new WeakReference<QuicConnection>(connection);
+ var serverConnectionOptions = CreateQuicServerOptions();
+ serverConnectionOptions.ServerAuthenticationOptions = new SslServerAuthenticationOptions();
+ return ValueTask.FromResult(serverConnectionOptions);
+ };
+ QuicListener listener = await CreateQuicListener(listenerOptions);
+
+ await Assert.ThrowsAsync<AuthenticationException>(async () => await CreateConnectedQuicConnection(listener));
+
+ // Dispose everything and check if all weak references are dead.
+ await listener.DisposeAsync();
+
+ for (int i = 0; i < 20; ++i)
+ {
+ GC.Collect();
+ GC.WaitForPendingFinalizers();
+
+ await Task.Delay(100 * i);
+
+ if (TestWeakReferences())
+ {
+ continue;
+ }
+ bool TestWeakReferences()
+ => wrServerConnection.TryGetTarget(out _);
+
+ break;
+ }
+
+ Assert.False(wrServerConnection.TryGetTarget(out _));
+ }
+
[Fact]
public async Task ConnectWithCertificateChain()
{