-
Notifications
You must be signed in to change notification settings - Fork 5.2k
[QUIC] Call silent shutdown in case CloseAsync failed.
#96807
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
[QUIC] Call silent shutdown in case CloseAsync failed.
#96807
Conversation
|
Tagging subscribers to this area: @dotnet/ncl Issue Details
Changes are in ValueTaskSource and QuicConnection. Other changes are cosmetic, not functional (comments, code formatting). Fixes #78641 I also did a manual testing with cancellation between CloseAsync --> connection.Shutdown and SHUTDOWN_COMPLETED by inserting delay to the event handler, but this as this is timing sensitive it's impossible to do reliably in a test.
|
|
Tagging subscribers to this area: @dotnet/ncl Issue Details
Changes are in ValueTaskSource and QuicConnection. Other changes are cosmetic, not functional (comments, code formatting). Fixes #78641 I also did a manual testing with cancellation between CloseAsync --> connection.Shutdown and SHUTDOWN_COMPLETED by inserting delay to the event handler, but this as this is timing sensitive it's impossible to do reliably in a test.
|
|
I can still get DisposeAsync to throw like this. var (clientOptions, _, listenerOptions) = RuntimeSandbox.Helpers.GetDefaultOptions();
await using var pair = await RuntimeSandbox.Helpers.GetConnectedPair(clientOptions, listenerOptions);
var (client, server) = pair;
var cts = new CancellationTokenSource();
var task = client.CloseAsync(0).AsTask();
cts.Cancel(); // throws even without this line
await client.DisposeAsync();
// Unhandled exception. System.InvalidOperationException: Operation is not valid due to the current state of the object.
// at System.Threading.Tasks.Sources.ManualResetValueTaskSourceCore`1.OnCompleted(Action`1 continuation, Object state, Int16 token, ValueTaskSourceOnCompletedFlags flags)
// at System.Net.Quic.ValueTaskSource.System.Threading.Tasks.Sources.IValueTaskSource.OnCompleted(Action`1 continuation, Object state, Int16 token, ValueTaskSourceOnCompletedFlags flags)
// at System.Runtime.CompilerServices.AsyncTaskMethodBuilder`1.AwaitUnsafeOnCompleted[TAwaiter](TAwaiter& awaiter, IAsyncStateMachineBox box)
// --- End of stack trace from previous location ---
// at System.Threading.Tasks.Task.<>c.<ThrowAsync>b__128_1(Object state)
// at System.Threading.QueueUserWorkItemCallbackDefaultContext.Execute()
// at System.Threading.ThreadPoolWorkQueue.Dispatch()
// at System.Threading.PortableThreadPool.WorkerThread.WorkerThreadStart()
// at System.Threading.Thread.StartCallback()Do we care? If not then LGTM. |
|
Ok, I've re-read the stack-trace, and now I don't know if it's related or not 😅 but I'm curious whether this was happening before. But I think we should care 🤔 I don't think it's ok to throw if dispose and/or close are called in parallel. |
src/libraries/System.Net.Quic/src/System/Net/Quic/Internal/ValueTaskSource.cs
Outdated
Show resolved
Hide resolved
src/libraries/System.Net.Quic/src/System/Net/Quic/Internal/ValueTaskSource.cs
Outdated
Show resolved
Hide resolved
Just FYI, this happens without this change as well. I'll try looking into it if it's something small that I could put into this PR and if not I'll file separate issue. |
I think we should never throw from Dispose(Async) |
e56c45d to
7438127
Compare
|
Radek's problem fixed and added test for that. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You are creating an instance here every time regardless whether the exchange happened or not... is that expected?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I did not bother with optimizing it. It'll be at most 2 instances (in Gen0) in vain per connection lifetime. I can change it to use another field as a sentinel (e.g. int shutdownCalled) or to preallocate this in other field for a swap. Or just not guard against multiple Shutdown calls 🤷
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
LGTM
39d4fd0 to
b52fa38
Compare
|
Just FYI, using ordinary TCS brought back an issue with GC eating non-rooted |
b52fa38 to
5b3b3d4
Compare
This reverts commit 636037cf9e85bc1f275d6cb7346866d4b86c5938.
This reverts commit f393313f886bf2073ddaea9b02f9bb30b80f047b.
e196f02 to
85cd4eb
Compare
|
Failure is unrelated and known: #97546 |
ValueTaskSourcetemporary, which enables receiving the expected MsQuic signal even aftercancellationTokenfired. In this caseSHUTDOWN_COMPLETE.ValueTaskSourceis cancelled, remember what happened to it before the caller read the OCE so that it can be restored to the task afterwards.ValueTaskSourceto the remembered state (awaiting or completed with the result)._shutdownTcsinDisposeand decide whether the silent shutdown needs to be called.Changes are in ValueTaskSource and QuicConnection. Other changes are cosmetic, not functional (comments, code formatting).
Fixes #78641
I also did a manual testing with cancellation between CloseAsync --> connection.Shutdown and SHUTDOWN_COMPLETED by inserting delay to the event handler, but this as this is timing sensitive it's impossible to do reliably in a test.