-
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
keep MsQuicConnection alive when streams are pending #52800
Conversation
Tagging subscribers to this area: @dotnet/ncl Issue DetailsThis change has two parts: When there are stream active we need to keep at least the native parts of MsQuick connection alive. This solves problem when dangling stream is disposed by finalizer as well as it covers cases when the connection is explicitly disposed before the stream(s). This is somewhat similar to #52776 but different in details. Instead of exposing the internal state, it mimics what we do with connection and stream in H/2. Connection has stream counter and if there are pending stream we would defer cleanup of the native state. That would happen later when last stream goes. Second part implements suggestion from @geoffkizer to flush the accept queue on connection disposal. When working on that I noticed that the stream may get lost if AcceptQueue.Writer.TryWrite fails (because for example it is already closed. I added logic to check and dispose the stream as well. This change is focused on the relation between connection and stream and it does not touch live cycle of either one. fixes #52048
|
src/libraries/System.Net.Quic/src/System/Net/Quic/Implementations/MsQuic/MsQuicConnection.cs
Show resolved
Hide resolved
This is gonna clash with #52704, just FYI. |
yes, I know @ManickaP. I started the crash investigation independently of what @CarnaViire was boring on. |
src/libraries/System.Net.Quic/src/System/Net/Quic/Implementations/MsQuic/MsQuicConnection.cs
Outdated
Show resolved
Hide resolved
src/libraries/System.Net.Quic/src/System/Net/Quic/Implementations/MsQuic/MsQuicConnection.cs
Show resolved
Hide resolved
ping? Any brave soul for review? ManagedAVE_MinimalFailingTest does not crash but it still sometimes fail with timeout. I think that is separate issue and I filed microsoft/msquic#1668 to investigate. It seems to be specific to calling close from finalizer thread. |
src/libraries/System.Net.Quic/src/System/Net/Quic/Implementations/MsQuic/MsQuicConnection.cs
Outdated
Show resolved
Hide resolved
src/libraries/System.Net.Quic/src/System/Net/Quic/Implementations/MsQuic/MsQuicConnection.cs
Outdated
Show resolved
Hide resolved
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
src/libraries/System.Net.Quic/src/System/Net/Quic/Implementations/MsQuic/MsQuicConnection.cs
Outdated
Show resolved
Hide resolved
{ | ||
GCHandle gcHandle = GCHandle.FromIntPtr(_closingGCHandle); | ||
Debug.Assert(gcHandle.IsAllocated); | ||
if (gcHandle.IsAllocated) gcHandle.Free(); |
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.
if (gcHandle.IsAllocated) gcHandle.Free(); | |
gcHandle.Free(); |
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.
is this generally safe? We have other places with identical check.
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.
IsAllocated just checks whether the GCHandle is not IntPtr.Zero. _closingGCHandle != IntPtr.Zero
above does that already, so it should not be needed here.
The linked issue was resolved by moving Handle.Release() outside of lock block. It seems like finalizing the connection can flush some events and cause unexpected processing. |
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 appart from one question.
src/libraries/System.Net.Quic/src/System/Net/Quic/Implementations/MsQuic/MsQuicConnection.cs
Outdated
Show resolved
Hide resolved
@@ -43,6 +42,7 @@ internal sealed class MsQuicConnection : QuicConnectionProvider | |||
internal sealed class State | |||
{ | |||
public SafeMsQuicConnectionHandle Handle = null!; // set inside of MsQuicConnection ctor. | |||
public GCHandle StateGCHandle; |
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 don't fully understand why was the StateGCHandle
moved inside State
?
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.
When the connection is gone and state is kept alive because of the streams, we still want to lock state in place because we handed pointer to MsQuic. When we release all the streams, the original location is not accessible as we don't have link back to connection (and it may be gone) I did the move (sort of) in previous round using IntPtr. @CarnaViire and @jkotas suggested to preserve the type so I end up with this.
src/libraries/System.Net.Quic/src/System/Net/Quic/Implementations/MsQuic/MsQuicConnection.cs
Outdated
Show resolved
Hide resolved
src/libraries/System.Net.Quic/src/System/Net/Quic/Implementations/MsQuic/MsQuicConnection.cs
Outdated
Show resolved
Hide resolved
src/libraries/System.Net.Quic/src/System/Net/Quic/Implementations/MsQuic/MsQuicConnection.cs
Outdated
Show resolved
Hide resolved
_state.AcceptQueue.Writer.Complete(); | ||
} catch { }; | ||
|
||
await foreach (MsQuicStream item in _state.AcceptQueue.Reader.ReadAllAsync()) |
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.
await foreach (MsQuicStream item in _state.AcceptQueue.Reader.ReadAllAsync()) | |
await foreach (MsQuicStream item in _state.AcceptQueue.Reader.ReadAllAsync().ConfigureAwait(false)) |
src/libraries/System.Net.Quic/src/System/Net/Quic/Implementations/MsQuic/MsQuicConnection.cs
Outdated
Show resolved
Hide resolved
} | ||
} | ||
|
||
FlushAcceptQueue().GetAwaiter().GetResult(); |
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.
Connections are IAsyncDisposable
; we should make dispose do proper await there.
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'm not sure what you suggest. We still need to flush for synchronous Dispose(). Can we do what ever needs to be done as follow-up? I would like to get this this in to get verifications on the crashes.
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.
That's fine.
src/libraries/System.Net.Quic/src/System/Net/Quic/Implementations/MsQuic/MsQuicConnection.cs
Show resolved
Hide resolved
src/libraries/System.Net.Quic/src/System/Net/Quic/Implementations/MsQuic/MsQuicConnection.cs
Show resolved
Hide resolved
Socket failure on macOS is unrelated. |
This change has two parts:
When there are stream active we need to keep at least the native parts of MsQuick connection alive. This solves problem when dangling stream is disposed by finalizer as well as it covers cases when the connection is explicitly disposed before the stream(s).
This is somewhat similar to #52776 but different in details. Instead of exposing the internal state, it mimics what we do with connection and stream in H/2. Connection has stream counter and if there are pending stream we would defer cleanup of the native state. That would happen later when last stream goes.
Second part implements suggestion from @geoffkizer to flush the accept queue on connection disposal. When working on that I noticed that the stream may get lost if AcceptQueue.Writer.TryWrite fails (because for example it is already closed. I added logic to check and dispose the stream as well.
This change is focused on the relation between connection and stream and it does not touch live cycle of either one.
I will to the stream cleanup in follow up PR.
fixes #52048