-
Notifications
You must be signed in to change notification settings - Fork 4.8k
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
[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. |
@@ -467,7 +467,7 @@ public ValueTask CloseAsync(long errorCode, CancellationToken cancellationToken | |||
{ | |||
ObjectDisposedException.ThrowIf(_disposed == 1, this); | |||
|
|||
if (_shutdownTcs.TryInitialize(out ValueTask valueTask, this, cancellationToken)) | |||
if (Interlocked.CompareExchange(ref _shutdownTcs, new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously), null) == null) |
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 |
ValueTaskSource
temporary, which enables receiving the expected MsQuic signal even aftercancellationToken
fired. In this caseSHUTDOWN_COMPLETE
.ValueTaskSource
is cancelled, remember what happened to it before the caller read the OCE so that it can be restored to the task afterwards.ValueTaskSource
to the remembered state (awaiting or completed with the result)._shutdownTcs
inDispose
and 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.