-
Notifications
You must be signed in to change notification settings - Fork 5.3k
Description
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:
runtime/src/libraries/System.Net.Security/src/System/Net/Security/SslStream.IO.cs
Lines 50 to 63 in 6ddd06c
| 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(); | |
| } |
runtime/src/libraries/System.Net.Security/src/System/Net/Security/SslStream.IO.cs
Lines 763 to 769 in 6ddd06c
| 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()):
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