Over HTTP/2 and HTTP/3, the ioxide engine can't tell a streamed response that its client has gone, so a server-sent event source keeps producing for a closed tab. Over HTTP/1.1 this already works: Http1WriterStream.FlushAsync throws ConnectionClosedException when ioxide reports a completed flush, and EventConnection.SendAsync turns that IOException into Connected = false.
What ioxide changed
MDA2AV/ioxide#269, which was filed from this engine, is fixed in ioxide 0.16.281 by MDA2AV/ioxide#274. The HTTP/2 and HTTP/3 response writers' FlushAsync now returns ValueTask<FlushResult>. IsCompleted is true once the peer has reset or stopped the stream, or the connection is gone.
Before that, ioxide got this case wrong itself. An HTTP/2 writer parked forever waiting for flow-control credit, and an nghttp3 writer spun its reactor at 100% CPU.
What the engine needs
Http2Driver and Http3Driver write bodies through StreamedResponder.WriteBodyAsync and StreamedSink, which take a Func<ValueTask> and so never see a result. They should take Func<ValueTask<FlushResult>> and throw ConnectionClosedException on a completed flush, the same way Http1WriterStream does.
0.16.281 needs that same signature change to compile, so the code is the one already posted on #929.
Done when
An SSE client that disconnects over HTTP/2, and one over HTTP/3, each sees IEventConnection.Connected turn false and its source stop, as over HTTP/1.1.
Depends on #929 (moving to 0.16.281).
Over HTTP/2 and HTTP/3, the ioxide engine can't tell a streamed response that its client has gone, so a server-sent event source keeps producing for a closed tab. Over HTTP/1.1 this already works:
Http1WriterStream.FlushAsyncthrowsConnectionClosedExceptionwhen ioxide reports a completed flush, andEventConnection.SendAsyncturns thatIOExceptionintoConnected = false.What ioxide changed
MDA2AV/ioxide#269, which was filed from this engine, is fixed in ioxide 0.16.281 by MDA2AV/ioxide#274. The HTTP/2 and HTTP/3 response writers'
FlushAsyncnow returnsValueTask<FlushResult>.IsCompletedis true once the peer has reset or stopped the stream, or the connection is gone.Before that, ioxide got this case wrong itself. An HTTP/2 writer parked forever waiting for flow-control credit, and an nghttp3 writer spun its reactor at 100% CPU.
What the engine needs
Http2DriverandHttp3Driverwrite bodies throughStreamedResponder.WriteBodyAsyncandStreamedSink, which take aFunc<ValueTask>and so never see a result. They should takeFunc<ValueTask<FlushResult>>and throwConnectionClosedExceptionon a completed flush, the same wayHttp1WriterStreamdoes.0.16.281 needs that same signature change to compile, so the code is the one already posted on #929.
Done when
An SSE client that disconnects over HTTP/2, and one over HTTP/3, each sees
IEventConnection.Connectedturn false and its source stop, as over HTTP/1.1.Depends on #929 (moving to 0.16.281).