|
4 | 4 | using System.IO;
|
5 | 5 | using System.Security.Cryptography.X509Certificates;
|
6 | 6 | using System.Threading;
|
| 7 | +using System.Security.Authentication; |
7 | 8 | using System.Threading.Tasks;
|
8 | 9 |
|
9 | 10 | using Xunit;
|
@@ -59,6 +60,7 @@ public async Task Dispose_PendingReadAsync_ThrowsODE(bool bufferedRead)
|
59 | 60 | using CancellationTokenSource cts = new CancellationTokenSource();
|
60 | 61 | cts.CancelAfter(TestConfiguration.PassingTestTimeout);
|
61 | 62 |
|
| 63 | + |
62 | 64 | (SslStream client, SslStream server) = TestHelper.GetConnectedSslStreams(leaveInnerStreamOpen: true);
|
63 | 65 | using (client)
|
64 | 66 | using (server)
|
@@ -102,5 +104,65 @@ await TestConfiguration.WhenAllOrAnyFailedWithTimeout(
|
102 | 104 | await Assert.ThrowsAnyAsync<ObjectDisposedException>(() => client.ReadAsync(readBuffer, cts.Token).AsTask());
|
103 | 105 | }
|
104 | 106 | }
|
| 107 | + |
| 108 | + [Fact] |
| 109 | + [OuterLoop("Computationally expensive")] |
| 110 | + public async Task Dispose_ParallelWithHandshake_ThrowsODE() |
| 111 | + { |
| 112 | + using CancellationTokenSource cts = new CancellationTokenSource(); |
| 113 | + cts.CancelAfter(TestConfiguration.PassingTestTimeout); |
| 114 | + |
| 115 | + await Parallel.ForEachAsync(System.Linq.Enumerable.Range(0, 10000), cts.Token, async (i, token) => |
| 116 | + { |
| 117 | + (Stream clientStream, Stream serverStream) = TestHelper.GetConnectedStreams(); |
| 118 | + |
| 119 | + using SslStream client = new SslStream(clientStream); |
| 120 | + using SslStream server = new SslStream(serverStream); |
| 121 | + using X509Certificate2 serverCertificate = Configuration.Certificates.GetServerCertificate(); |
| 122 | + using X509Certificate2 clientCertificate = Configuration.Certificates.GetClientCertificate(); |
| 123 | + |
| 124 | + SslClientAuthenticationOptions clientOptions = new SslClientAuthenticationOptions() |
| 125 | + { |
| 126 | + TargetHost = Guid.NewGuid().ToString("N"), |
| 127 | + RemoteCertificateValidationCallback = (sender, certificate, chain, sslPolicyErrors) => true, |
| 128 | + }; |
| 129 | + |
| 130 | + SslServerAuthenticationOptions serverOptions = new SslServerAuthenticationOptions() |
| 131 | + { |
| 132 | + ServerCertificate = serverCertificate, |
| 133 | + }; |
| 134 | + |
| 135 | + var clientTask = Task.Run(() => client.AuthenticateAsClientAsync(clientOptions, cts.Token)); |
| 136 | + var serverTask = Task.Run(() => server.AuthenticateAsServerAsync(serverOptions, cts.Token)); |
| 137 | + |
| 138 | + // Dispose the instances while the handshake is in progress. |
| 139 | + client.Dispose(); |
| 140 | + server.Dispose(); |
| 141 | + |
| 142 | + await ValidateExceptionAsync(clientTask); |
| 143 | + await ValidateExceptionAsync(serverTask); |
| 144 | + }); |
| 145 | + |
| 146 | + static async Task ValidateExceptionAsync(Task task) |
| 147 | + { |
| 148 | + try |
| 149 | + { |
| 150 | + await task; |
| 151 | + } |
| 152 | + catch (InvalidOperationException ex) when (ex.StackTrace?.Contains("System.IO.StreamBuffer.WriteAsync") ?? true) |
| 153 | + { |
| 154 | + // Writing to a disposed ConnectedStream (test only, does not happen with NetworkStream) |
| 155 | + return; |
| 156 | + } |
| 157 | + catch (Exception ex) when (ex |
| 158 | + is ObjectDisposedException // disposed locally |
| 159 | + or IOException // disposed remotely (received unexpected EOF) |
| 160 | + or AuthenticationException) // disposed wrapped in AuthenticationException or error from platform library |
| 161 | + { |
| 162 | + // expected |
| 163 | + return; |
| 164 | + } |
| 165 | + } |
| 166 | + } |
105 | 167 | }
|
106 | 168 | }
|
0 commit comments