feat(quic): ClientQUIC::OnClosed tells the owner the connection is over
A ListenerQUIC hands each accepted connection to its callback as a heap-allocated ClientQUIC the callback owns, and nothing said when to delete it: the SHUTDOWN_COMPLETE handler closed the msquic handle of a connection the peer or the idle timeout ended, but the object stayed, so a server either leaked its peers or deleted them blind, and one still alive at exit holds msquic's registration open until its idle timeout. OnClosed(callback) is told once when the connection has shut down for any reason but this object's destruction: the peer closed it, the idle timeout or a transport error ended it, or Stop() was called. It runs on the msquic worker, in the SHUTDOWN_COMPLETE handler, after that handler has claimed and closed the handle, so deleting the ClientQUIC from inside it is safe: ~ClientQUIC then finds no handle to claim and waits for nothing. Delivering it there, rather than as a pool task, keeps it independent of a pool the host shares and possibly saturates; the cost is the usual worker rule, which the interface spells out (no blocking, no *Sync call, no deleting another ClientQUIC from inside it). A callback set after the close, which happens when the peer goes before a ListenerQUIC's connectCallback runs, fires as a pool task instead, never from inside OnClosed, where the caller is still using the object. Its state is a shared CloseNotice rather than part of the Impl, since the handler fires it after giving up the handle, when ~ClientQUIC on another thread no longer waits for the worker and may free the Impl. The rules are std::stop_callback's: ~ClientQUIC marks the owner gone first, after which the callback never starts; one already running on another thread is waited for, so the owner may free what it uses as soon as the destructor returns; one on the destructor's own thread, a callback deleting its connection, is not. The handler takes the handle and the notice under one lock, so the destructor cannot slip between. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
parent
be55036b18
commit
f0769838e5
3 changed files with 136 additions and 7 deletions
|
|
@ -270,6 +270,30 @@ namespace Crafter {
|
|||
// Block the caller until the next datagram arrives; returns it.
|
||||
// Throws QUICClosedException if the connection closes first.
|
||||
std::vector<char> RecieveDatagramSync();
|
||||
|
||||
// Told once when the connection has shut down for any reason other
|
||||
// than this object being destroyed: the peer closed it, the idle
|
||||
// timeout or a transport error ended it, or Stop() was called. This
|
||||
// is how the owner of a connection a ListenerQUIC accepted learns
|
||||
// that it is time to delete it.
|
||||
//
|
||||
// - It runs on the connection's msquic worker thread, once the
|
||||
// connection's handle is closed; if the connection had already
|
||||
// closed when OnClosed was called, it runs as a pool task
|
||||
// instead, never inside OnClosed itself.
|
||||
// - Deleting this ClientQUIC from inside it is safe, and the usual
|
||||
// thing to do. Do nothing else that blocks: deleting a
|
||||
// *different* ClientQUIC, or any *Sync call, waits on msquic
|
||||
// workers, this one perhaps among them. It must not throw; an
|
||||
// exception leaving it ends the program.
|
||||
// - Once ~ClientQUIC has begun it never starts, and a run already
|
||||
// under way on another thread finishes before ~ClientQUIC goes
|
||||
// on, as with std::stop_callback (one on this thread, deleting
|
||||
// its own connection, is not waited for). So do not destroy the
|
||||
// ClientQUIC while holding a lock the callback takes.
|
||||
// - Calling OnClosed again replaces the callback, and an empty one
|
||||
// unregisters it; once it has fired, later calls do nothing.
|
||||
void OnClosed(std::function<void()> callback);
|
||||
#endif
|
||||
|
||||
// Cleanly shut down the connection.
|
||||
|
|
|
|||
Loading…
Reference in a new issue