Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ private SafeMsQuicConfigurationHandle()
protected override bool ReleaseHandle()
{
MsQuicApi.Api.ConfigurationCloseDelegate(handle);
SetHandle(IntPtr.Zero);
return true;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ public SafeMsQuicConnectionHandle(IntPtr connectionHandle)
protected override bool ReleaseHandle()
{
MsQuicApi.Api.ConnectionCloseDelegate(handle);
SetHandle(IntPtr.Zero);
return true;
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ private SafeMsQuicListenerHandle()
protected override bool ReleaseHandle()
{
MsQuicApi.Api.ListenerCloseDelegate(handle);
SetHandle(IntPtr.Zero);
return true;
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ private SafeMsQuicRegistrationHandle()
protected override bool ReleaseHandle()
{
MsQuicApi.Api.RegistrationCloseDelegate(handle);
SetHandle(IntPtr.Zero);
return true;
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ public SafeMsQuicStreamHandle(IntPtr streamHandle)
protected override bool ReleaseHandle()
{
MsQuicApi.Api.StreamCloseDelegate(handle);
SetHandle(IntPtr.Zero);
return true;
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ internal sealed class MsQuicConnection : QuicConnectionProvider
{
private static readonly Oid s_clientAuthOid = new Oid("1.3.6.1.5.5.7.3.2", "1.3.6.1.5.5.7.3.2");
private static readonly Oid s_serverAuthOid = new Oid("1.3.6.1.5.5.7.3.1", "1.3.6.1.5.5.7.3.1");
private const uint DefaultResetValue = 0xffffffff; // Arbitrary value unlikely to conflict with application protocols.

// Delegate that wraps the static function that will be called when receiving an event.
private static readonly ConnectionCallbackDelegate s_connectionDelegate = new ConnectionCallbackDelegate(NativeCallbackHandler);
Expand Down Expand Up @@ -70,7 +71,7 @@ internal sealed class State
SingleWriter = true,
});

public void RemoveStream(MsQuicStream stream)
public void RemoveStream(MsQuicStream? stream)
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why are we even passing in the stream? Doesn't seem to be used at all.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I was going back and forth on this one. When debugging it is very nice to have the stream here so one can emit traces from single location. This is basically same for the AddStream.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm trying to understand why we need this at all... It seems like we are deferring Dispose of the connection until all streams in use on it are completed. Is that correct? Why do we want to do this? Can we just get rid of this tracking completely?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yes, that is the intend. We saw crashes without it but it can perhaps be done in different way. If so I would do it in separate PR. The reason why this was touches is that the HandleEventShutdownComplete does not have access to the Stream itself so it would pass NULL.
I'm OK removing the parameter completely as it is not use for product or perhaps using it Debug builds only and for example dump the Stream info if we are on path to Assert.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I agree, we should consider for a separate PR.

Mostly I want to make sure we are making an intentional decision about behavior here.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

When chatting with @nibanks he sad we would not get HandleEventShutdownComplete with pending streams. So if we delay cleanup in Dispose and if we can relay on that we could perhaps remove to logic. It would be nice to have stress tests up at that time so we can verify the impact.

{
bool releaseHandles;
lock (this)
Expand Down Expand Up @@ -252,7 +253,7 @@ private static uint HandleEventShutdownComplete(State state, ref ConnectionEvent
state.ShutdownTcs.SetResult(MsQuicStatusCodes.Success);

// Stop accepting new streams.
state.AcceptQueue.Writer.Complete();
state.AcceptQueue.Writer.TryComplete();
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It seems like there are a bunch of places now where we either call Writer.Complete or Writer.TryComplete. Are all of these necessary? I would assume we only have to do this in a limited number of places, e.g. HandleEventShutdownInitiatedByTransport and HandleEventShutdownInitiatedByPeer, and maybe one other for when we initiate shutdown ourselves.

I always get nervous when we do stuff like call TryComplete instead of Complete, because it seems like we don't have a clean handling of exactly when the Complete operation needs to happen. I'd prefer to see the TryComplete calls changed to either Complete or to assert that it's already completed.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In this case I was getting exception when when the connection was already Disposed and that triggered flush of events and ShutdownComplete. I could wrap it with if (_disposed == 0) but changing it to Try look better to me. Since this only means we will not put more items to the queue.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I look at the cases @geoffkizer and it is not that we would not know if the Complete operation should happen. We just don't know if happened before. Like in this case the shutdown may be initiated by peer or somebody may Dispose the connection but than we get this final event and it will throw if we try to complete it again. Unless I missed something I did not find API on the Writer to know if it was completed or not.
One option I can think of you we making this nullable and set it to null after completion. Or we can add yet another state. Let me know what you want me to do.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think it's fine for this PR, but we should revisit the logic here moving forward.


// Stop notifying about available streams.
TaskCompletionSource? unidirectionalTcs = null;
Expand Down Expand Up @@ -625,7 +626,7 @@ private static uint NativeCallbackHandler(
{
if (NetEventSource.Log.IsEnabled())
{
NetEventSource.Error(state, $"Exception occurred during connection callback: {ex.Message}");
NetEventSource.Error(state, $"[Connection#{state.GetHashCode()}] Exception occurred during handling {connectionEvent.Type} connection callback: {ex.Message}");
}

// TODO: trigger an exception on any outstanding async calls.
Expand All @@ -648,9 +649,17 @@ public override void Dispose()
private async Task FlushAcceptQueue()
{
_state.AcceptQueue.Writer.TryComplete();
await foreach (MsQuicStream item in _state.AcceptQueue.Reader.ReadAllAsync().ConfigureAwait(false))
await foreach (MsQuicStream stream in _state.AcceptQueue.Reader.ReadAllAsync().ConfigureAwait(false))
{
item.Dispose();
if (stream.CanRead)
{
stream.AbortRead(DefaultResetValue);
}
if (stream.CanWrite)
{
stream.AbortWrite(DefaultResetValue);
}
stream.Dispose();
}
}

Expand Down Expand Up @@ -681,7 +690,8 @@ private void Dispose(bool disposing)
_configuration?.Dispose();
if (releaseHandles)
{
_state!.Handle?.Dispose();
// We may not be fully initialized if constructor fails.
_state.Handle?.Dispose();
if (_state.StateGCHandle.IsAllocated) _state.StateGCHandle.Free();
}
}
Expand Down
Loading