I grabbed a dump during the long delay where nothing is happening and the test is waiting for it's timeout. The dump has one QuicConnection rooted still. Which, when you track the reference down, is from QuicConnection._connectedTcs._keepAlive. So basically the QuicConnection is rooting itself. Normally this would be cleaned up by awaiting the task returned via QuicConnection.FinishHandshakeAsync or QuicConnection.FinishConnectAsync so I tracked down which method the rooted connection came from, and it was from FinishHandshakeAsync.
And the reason the ValueTask returned from FinishHandshakeAsync isn't awaited (thus freeing the GCHandle) is because the method throws which keeps the GCHandle alive forever in this case.
The reason the test worked before was because the TCS wasn't disposed before @stephentoub's change which meant there was a 10 second timeout that would eventually cleanup the ValueTask and thus the GCHandle.
So properly disposing the TCS exposed a bug in QuicConnection.
Originally posted by @BrennanConroy in #48558 (comment)
Originally posted by @BrennanConroy in #48558 (comment)