Implement cancellation token for SslStream new AuthenticateAs*Async methods - #24857
Conversation
| /// Otherwise, reads as directed or completes "request" with an Exception. | ||
| /// </summary> | ||
| public static async void ReadPacketAsync(Stream transport, AsyncProtocolRequest request) // "async Task" might result in additional, unnecessary allocation | ||
| public static async void ReadPacketAsync(Stream transport, AsyncProtocolRequest request, CancellationToken cancellationToken) // "async Task" might result in additional, unnecessary allocation |
There was a problem hiding this comment.
As long as you're editing this line, you can remove the comment at the end; that's no longer the case.
| ForceAuthentication(Context.IsServer, null, asyncRequest); | ||
| if (asyncRequest != null) | ||
| { | ||
| cancellationToken.ThrowIfCancellationRequested(); |
There was a problem hiding this comment.
Cancellation exceptions shouldn't be thrown out of the invocations synchronously; they should be passed out in a task. That's going to require more refactoring, e.g. to do what @Drawaes has been doing and converting things from being Task-over-APM to instead be APM-over-Task, at which point implementing the cancellation support in that way will be trivial.
| _Framing = Framing.Unknown; | ||
|
|
||
| // Throw if cancellation requested. | ||
| cancellationToken.ThrowIfCancellationRequested(); |
There was a problem hiding this comment.
Pretty much any of these places where you've got a ThrowIfCancellationRequested and it's not an async method is likely not going to do the right thing, as the exception will be thrown out synchronously rather than propagate out through the returned Task.
There was a problem hiding this comment.
Also, do we need all of these polling checks for cancellation? Seems like there should be a single check up front and then it should just be passed to all of the stream operations.
There was a problem hiding this comment.
That assumes the underlying stream fully implements cancellation.
There was a problem hiding this comment.
That assumes the underlying stream fully implements cancellation.
No, it doesn't. It assumes the underlying stream at least does a single check for cancellation, which most every implementation does... even the base stream's implementation layered on Begin/End or the sync methods does so. We should not litter polling checks everywhere.
| else | ||
| { | ||
| // Throw before starting async write | ||
| cancellationToken.ThrowIfCancellationRequested(); |
There was a problem hiding this comment.
Why this rather than passing the cancellation token into the WriteAsync call?
| serverOptions.ServerCertificate = certificate; | ||
| serverOptions.RemoteCertificateValidationCallback = AllowAnyServerCertificate; | ||
|
|
||
| CancellationTokenSource cts = new CancellationTokenSource(); |
There was a problem hiding this comment.
What's the purpose of invoking Cancel in a Task? This is introducing a race condition such that we won't always be testing the same thing. If the goal is to have a canceled token before we call the operations, you can either just call cts.Cancel() synchronously or just use new CancellationToken(true). If the goal is to have cancellation occur once the operation is in progress, it'd be better to make the cts.Cancel() call synchronously below after you've got the clientTask and serverTask; that way, you know the cancellation request is coming in after all of the synchronous work done by those methods while they're actually waiting on IO.
| serverOptions.ServerCertificate = certificate; | ||
| serverOptions.RemoteCertificateValidationCallback = AllowAnyServerCertificate; | ||
|
|
||
| CancellationTokenSource cts = new CancellationTokenSource(); |
| public void SslStream_StreamToStream_ClientCancellation_Throws() | ||
| { | ||
| VirtualNetwork network = new VirtualNetwork(); | ||
| using (var clientStream = new VirtualNetworkStream(network, false)) |
There was a problem hiding this comment.
Nit: please name the bool args to VirtualNetworkStream; it's not clear at the call site what the false and true mean.
| } | ||
|
|
||
| sslState.CheckCompletionBeforeNextReceive((ProtocolToken)asyncState, asyncRequest); | ||
| // Not allowing cancellation in the callback. |
| { | ||
| if (asyncRequest != null) | ||
| { | ||
| cancellationToken.ThrowIfCancellationRequested(); |
There was a problem hiding this comment.
What guarantees if any do we make if authentication is canceled in the middle of it? Presumably no guarantees and at that point you can't use the SslStream for anything else?
There was a problem hiding this comment.
Yes, the exception thrown for cancellation will fail the handshake, and make the SslStream invalid.
| if (asyncRequest != null && asyncRequest.CancellationToken.IsCancellationRequested) | ||
| { | ||
| // Cancel async operation, before I/O starts. | ||
| asyncRequest.CompleteUserWithError(new TaskCanceledException()); |
There was a problem hiding this comment.
Nit: instead of new TaskCanceledException(), it'd be better to do new OperationCanceledException(asyncRequest.CancellationToken), so that the token that caused the cancellation is included in the exception.
(Same applies to the other cases of this elsewhere.)
There was a problem hiding this comment.
So I had that initially, and when I commented out all of the OperationCanceledException instance, and made cancellation throw from the InnerStream.WriteAsync/ReadAsync methods, it threw TaskCanceledException. Hence made these the same, to have uniform cancellation exception.
| { | ||
| if (asyncRequest != null && asyncRequest.CancellationToken.IsCancellationRequested) | ||
| { | ||
| // Return async operation if cancellation requested. |
|
Ubuntu failure: |
|
I had set the RemoteCertificateValidationCallback on server options as well, and the client was not sending any clientcertificates, hence the error about RemoteCertificateNotAvailable. But this verification happens only in CompleteHandshake phase, which means the server didn't timeout before that, so the cancellation didn't happen during the handshake. This could also result in flaky test, hence movign the cancellation trigger before starting the client and server tasks. |
|
The windows test failure is unrelated to this change, in System.Diagnostics.EventLog.Tests |
|
@Priya91, please make sure you run outerloop tests before merging any more changes like this. Thanks. |
Implement cancellation token for SslStream new AuthenticateAs*Async methods Commit migrated from dotnet/corefx@7cbb09b
also fixes #24853
cc @stephentoub @Tratcher @Drawaes