diff --git a/src/libraries/System.Net.Quic/src/Resources/Strings.resx b/src/libraries/System.Net.Quic/src/Resources/Strings.resx
index 09e0e5dc47fd95..0aff3df0a71f73 100644
--- a/src/libraries/System.Net.Quic/src/Resources/Strings.resx
+++ b/src/libraries/System.Net.Quic/src/Resources/Strings.resx
@@ -171,6 +171,9 @@
Connection timed out waiting for a response from the peer.
+
+ Connection handshake was canceled due to the configured timeout of {0} seconds elapsing.
+
'{0}' is not supported by System.Net.Quic.
@@ -220,7 +223,7 @@
Binding to socket failed, likely caused by a family mismatch between local and remote address.
- Authentication failed. {0}
+ Authentication failed: {0}.
diff --git a/src/libraries/System.Net.Quic/src/System/Net/Quic/QuicConnection.cs b/src/libraries/System.Net.Quic/src/System/Net/Quic/QuicConnection.cs
index d46ce9ecff409c..fdc06d31632249 100644
--- a/src/libraries/System.Net.Quic/src/System/Net/Quic/QuicConnection.cs
+++ b/src/libraries/System.Net.Quic/src/System/Net/Quic/QuicConnection.cs
@@ -399,7 +399,7 @@ public async ValueTask AcceptInboundStreamAsync(CancellationToken ca
}
catch (ChannelClosedException ex) when (ex.InnerException is not null)
{
- ExceptionDispatchInfo.Capture(ex.InnerException).Throw();
+ ExceptionDispatchInfo.Throw(ex.InnerException);
throw;
}
finally
@@ -608,7 +608,7 @@ private static unsafe int NativeCallback(QUIC_HANDLE* connection, void* context,
}
///
- /// If not closed explicitly by , closes the connection silently (leading to idle timeout on the peer side).
+ /// If not closed explicitly by , closes the connection with the .
/// And releases all resources associated with the connection.
///
/// A task that represents the asynchronous dispose operation.
@@ -619,7 +619,7 @@ public async ValueTask DisposeAsync()
return;
}
- // Check if the connection has been shut down and if not, shut it down silently.
+ // Check if the connection has been shut down and if not, shut it down.
if (_shutdownTcs.TryInitialize(out ValueTask valueTask, this))
{
unsafe
diff --git a/src/libraries/System.Net.Quic/src/System/Net/Quic/QuicDefaults.cs b/src/libraries/System.Net.Quic/src/System/Net/Quic/QuicDefaults.cs
index 08eacf261caef9..4715effc5dbaf2 100644
--- a/src/libraries/System.Net.Quic/src/System/Net/Quic/QuicDefaults.cs
+++ b/src/libraries/System.Net.Quic/src/System/Net/Quic/QuicDefaults.cs
@@ -32,4 +32,9 @@ internal static partial class QuicDefaults
/// Max value for application error codes that can be sent by QUIC, see .
///
public const long MaxErrorCodeValue = (1L << 62) - 1;
+
+ ///
+ /// Our own imposed timeout in the handshake process, since in certain cases MsQuic will not impose theirs, see .
+ ///
+ public static readonly TimeSpan HandshakeTimeout = TimeSpan.FromSeconds(10);
}
diff --git a/src/libraries/System.Net.Quic/src/System/Net/Quic/QuicException.cs b/src/libraries/System.Net.Quic/src/System/Net/Quic/QuicException.cs
index f254c9b2f7a86b..83b2cb588a1e99 100644
--- a/src/libraries/System.Net.Quic/src/System/Net/Quic/QuicException.cs
+++ b/src/libraries/System.Net.Quic/src/System/Net/Quic/QuicException.cs
@@ -17,7 +17,18 @@ public sealed class QuicException : IOException
/// The application protocol error code associated with the error.
/// The message for the exception.
public QuicException(QuicError error, long? applicationErrorCode, string message)
- : base(message)
+ : this(error, applicationErrorCode, message, null)
+ { }
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// The error associated with the exception.
+ /// The application protocol error code associated with the error.
+ /// The message for the exception.
+ /// The exception that is the cause of the current exception, or a null reference if no inner exception is specified.
+ internal QuicException(QuicError error, long? applicationErrorCode, string message, Exception? innerException)
+ : base(message, innerException)
{
QuicError = error;
ApplicationErrorCode = applicationErrorCode;
diff --git a/src/libraries/System.Net.Quic/src/System/Net/Quic/QuicListener.PendingConnection.cs b/src/libraries/System.Net.Quic/src/System/Net/Quic/QuicListener.PendingConnection.cs
deleted file mode 100644
index e69527eea783de..00000000000000
--- a/src/libraries/System.Net.Quic/src/System/Net/Quic/QuicListener.PendingConnection.cs
+++ /dev/null
@@ -1,107 +0,0 @@
-// 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.Security;
-using System.Threading;
-using System.Threading.Tasks;
-
-namespace System.Net.Quic;
-
-public sealed partial class QuicListener
-{
- ///
- /// Represents a connection that's been received via NEW_CONNECTION but not accepted yet.
- ///
- ///
- /// When a new connection is being received, the handshake process needs to get started.
- /// More specifically, the server-side connection options, including server certificate, need to selected and provided back to MsQuic.
- /// Finally, after the handshake completes and the connection is established, the result needs to be stored and subsequently retrieved from within .
- ///
- private sealed class PendingConnection : IAsyncDisposable
- {
- ///
- /// Our own imposed timeout in the handshake process, since in certain cases MsQuic will not impose theirs, see .
- ///
- ///
- private static readonly TimeSpan s_handshakeTimeout = TimeSpan.FromSeconds(10);
-
- ///
- /// It will contain the established in case of a successful handshake; otherwise, null.
- ///
- private readonly TaskCompletionSource _finishHandshakeTask;
- ///
- /// Use to impose the handshake timeout.
- ///
- private readonly CancellationTokenSource _cancellationTokenSource;
-
- public PendingConnection()
- {
- _finishHandshakeTask = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously);
- _cancellationTokenSource = new CancellationTokenSource();
- }
-
- ///
- /// Kicks off the handshake process. It doesn't propagate the result outside directly but rather stores it in a task available via .
- ///
- ///
- /// The method is async void on purpose so it starts an operation but doesn't wait for the result from the caller's perspective.
- /// It does await but that never gets propagated to the caller for which the method ends with the first asynchronously processed await.
- /// Once the asynchronous processing finishes, the result is stored in the task field that gets exposed via .
- ///
- /// The new connection.
- /// The TLS ClientHello data.
- /// The connection options selection callback.
- public async void StartHandshake(QuicConnection connection, SslClientHelloInfo clientHello, Func> connectionOptionsCallback)
- {
- try
- {
- _cancellationTokenSource.CancelAfter(s_handshakeTimeout);
- QuicServerConnectionOptions options = await connectionOptionsCallback(connection, clientHello, _cancellationTokenSource.Token).ConfigureAwait(false);
- options.Validate(nameof(options)); // Validate and fill in defaults for the options.
- await connection.FinishHandshakeAsync(options, clientHello.ServerName, _cancellationTokenSource.Token).ConfigureAwait(false);
- _finishHandshakeTask.SetResult(connection);
- }
- catch (Exception ex)
- {
- // Handshake failed:
- // 1. Connection cannot be handed out since it's useless --> return null, listener will wait for another one.
- // 2. Shutdown the connection to send a transport error to the peer --> application error code doesn't matter here, use default.
-
- if (NetEventSource.Log.IsEnabled())
- {
- NetEventSource.Error(connection, $"{connection} Connection handshake failed: {ex}");
- }
-
- await connection.CloseAsync(default).ConfigureAwait(false);
- await connection.DisposeAsync().ConfigureAwait(false);
- _finishHandshakeTask.SetException(ex);
- }
- }
-
- ///
- /// Provides access to the result of the handshake started with .
- ///
- /// A cancellation token that can be used to cancel the asynchronous operation.
- /// An asynchronous task that completes with the established connection if it succeeded or null if it failed.
- public ValueTask FinishHandshakeAsync(CancellationToken cancellationToken = default)
- => new(_finishHandshakeTask.Task.WaitAsync(cancellationToken));
-
-
- ///
- /// Cancels the handshake in progress and awaits for it so that the connection can be safely cleaned from the listener queue.
- ///
- /// A task that represents the asynchronous dispose operation.
- public async ValueTask DisposeAsync()
- {
- _cancellationTokenSource.Cancel();
- try
- {
- await _finishHandshakeTask.Task.ConfigureAwait(false);
- }
- catch
- {
- // Just swallow the exception, we don't want it to propagate outside of dispose and it has already been logged in StartHandshake catch block.
- }
- }
- }
-}
diff --git a/src/libraries/System.Net.Quic/src/System/Net/Quic/QuicListener.cs b/src/libraries/System.Net.Quic/src/System/Net/Quic/QuicListener.cs
index de41265c05c962..a8d115e28e0f5f 100644
--- a/src/libraries/System.Net.Quic/src/System/Net/Quic/QuicListener.cs
+++ b/src/libraries/System.Net.Quic/src/System/Net/Quic/QuicListener.cs
@@ -1,6 +1,7 @@
// 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.Runtime.CompilerServices;
using System.Runtime.ExceptionServices;
@@ -78,15 +79,26 @@ public static ValueTask ListenAsync(QuicListenerOptions options, C
///
private readonly ValueTaskSource _shutdownTcs = new ValueTaskSource();
+ ///
+ /// Used to stop pending connections when is requested.
+ ///
+ private readonly CancellationTokenSource _disposeCts = new CancellationTokenSource();
+
///
/// Selects connection options for incoming connections.
///
private readonly Func> _connectionOptionsCallback;
///
- /// Incoming connections waiting to be accepted via AcceptAsync.
+ /// Incoming connections waiting to be accepted via AcceptAsync. The item will either be fully connected or if the handshake failed.
///
- private readonly Channel _acceptQueue;
+ private readonly Channel