Description
If SslStream is disposed while there is a concurrent call to ReadAsyncInternal(), a misleading exception may be thrown:
Unhandled exception. System.NotSupportedException: This method may not be called when another read operation is pending.
at System.Net.Security.SslStream.ReadAsyncInternal[TIOAdapter](Memory`1 buffer, CancellationToken cancellationToken)
at System.Runtime.CompilerServices.PoolingAsyncValueTaskMethodBuilder`1.StateMachineBox`1.System.Threading.Tasks.Sources.IValueTaskSource<TResult>.GetResult(Int16 token)
at System.Net.Security.SslStream.Read(Byte[] buffer, Int32 offset, Int32 count)
The exception message claims that "ReadAsyncInternal() was called when another read operation is pending", but this is misleading, since there were not concurrent calls to ReadAsyncInternal(). This exception message is raised due to the following codepaths:
|
private void CloseInternal() |
|
{ |
|
_exception = s_disposedSentinel; |
|
CloseContext(); |
|
|
|
// Ensure a Read or Auth operation is not in progress, |
|
// block potential future read and auth operations since SslStream is disposing. |
|
// This leaves the _nestedRead = 1 and _nestedAuth = 1, but that's ok, since |
|
// subsequent operations check the _exception sentinel first |
|
if (Interlocked.Exchange(ref _nestedRead, 1) == 0 && |
|
Interlocked.Exchange(ref _nestedAuth, 1) == 0) |
|
{ |
|
_buffer.ReturnBuffer(); |
|
} |
|
private async ValueTask<int> ReadAsyncInternal<TIOAdapter>(Memory<byte> buffer, CancellationToken cancellationToken) |
|
where TIOAdapter : IReadWriteAdapter |
|
{ |
|
if (Interlocked.Exchange(ref _nestedRead, 1) == 1) |
|
{ |
|
throw new NotSupportedException(SR.Format(SR.net_io_invalidnestedcall, "read")); |
|
} |
For customers trying to debug this issue, it would be very helpful if ReadAsyncInternal() could throw ObjectDisposedException in this case. This makes it clear the issue is dispose during read, not concurrent reads.
Maybe a code change like this?
if (Interlocked.Exchange(ref _nestedRead, 1) == 1)
{
ThrowIfExceptionalOrNotAuthenticated();
throw new NotSupportedException(SR.Format(SR.net_io_invalidnestedcall, "read"));
}
Reproduction Steps
You should be able to repro this by concurrently calling Read() and Dispose() in a loop until you trigger the race condition. Alternatively, you can use the repro I created for the Azure SDK (which has a bug where it calls Dispose() concurrently during a Read()):
Azure/azure-sdk-for-net#32577
Expected behavior
SslStream throws ObjectDisposedException
Actual behavior
SslStream throws NotSupportedException
Regression?
No
Known Workarounds
No response
Configuration
.NET Runtime 7.0.0
Windows 10
x64
Other information
No response
Description
If
SslStreamis disposed while there is a concurrent call toReadAsyncInternal(), a misleading exception may be thrown:The exception message claims that "ReadAsyncInternal() was called when another read operation is pending", but this is misleading, since there were not concurrent calls to
ReadAsyncInternal(). This exception message is raised due to the following codepaths:runtime/src/libraries/System.Net.Security/src/System/Net/Security/SslStream.IO.cs
Lines 50 to 63 in 6ddd06c
runtime/src/libraries/System.Net.Security/src/System/Net/Security/SslStream.IO.cs
Lines 763 to 769 in 6ddd06c
For customers trying to debug this issue, it would be very helpful if
ReadAsyncInternal()could throwObjectDisposedExceptionin this case. This makes it clear the issue is dispose during read, not concurrent reads.Maybe a code change like this?
Reproduction Steps
You should be able to repro this by concurrently calling
Read()andDispose()in a loop until you trigger the race condition. Alternatively, you can use the repro I created for the Azure SDK (which has a bug where it callsDispose()concurrently during aRead()):Azure/azure-sdk-for-net#32577
Expected behavior
SslStreamthrowsObjectDisposedExceptionActual behavior
SslStreamthrowsNotSupportedExceptionRegression?
No
Known Workarounds
No response
Configuration
.NET Runtime 7.0.0
Windows 10
x64
Other information
No response