nghttp2: streamed response bodies, and the ASP.NET sample moves into the Playground - #177
Merged
Conversation
Examples.AspNet was the last thing left at the repo root from the old Examples project, which the Playground replaced. It is a sample like every other one, so it lives with them: Playground/AspNet, Playground.AspNet, and the solution and README point at the new path. Nothing about what it does changes - still ioxide.Kestrel against a stock Kestrel baseline, still switched by TRANSPORT. Verified by running it: / and /plaintext both answer on the io_uring transport.
nghttp2 always supported this - it is what nghttp2_data_provider, NGHTTP2_ERR_DEFERRED and nghttp2_session_resume_data exist for. What did not support it was our shim, which only ever exposed the whole-body submit. So this is mostly C. Three new entry points: ih2_submit_response_stream opens a stream with a provider and no bytes, ih2_stream_write appends and resumes, ih2_stream_close says there will be no more. read_body is where the actual behaviour lives - on an empty window it now returns NGHTTP2_ERR_DEFERRED for a streamed body instead of flagging EOF, which is what holds the stream open through a slow producer rather than truncating it at its first quiet moment. The model is PULL, and the C# hides it: Nghttp2ResponseWriter is an IBufferWriter<byte> that reads like a push, while underneath nghttp2 asks for bytes when it is ready to frame them. A flush here means "handed over", not "on the wire" - unlike ioxide.http2, which owns its framing and stages a DATA frame the moment you flush. Request bodies are still buffered; that direction needs manual window accounting to be worth anything, so it is not done here. One bug worth recording: resume_data returns INVALID_ARGUMENT when the stream is not currently deferred, which happens whenever a write beats nghttp2 to it - most of them. Treating that as an error killed the connection after a single chunk, which is exactly what the first run did: 8192 bytes came back as 0, and /feed stopped at 1024. Verified on the wire, because a length check would not have caught coalescing: nghttp shows HEADERS, then EIGHT separate 1024-byte DATA frames, then a zero-length DATA with END_STREAM. /feed streams 251 MB in three seconds. The regression test counts frames rather than bytes for the same reason. Unit 36, Chaos 47, Http 38, E2E 46.
Eleven packages this time - ioxide.nghttp2 is among them again, and it is the one with something new in it: streamed response bodies, which means the shipped native library changes as well as the managed assembly. Verified by packing: the nupkg carries the rebuilt runtimes/linux-x64/native/libioxide_nghttp2.so, and that .so exports ih2_submit_response_stream, ih2_stream_write and ih2_stream_close.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
nghttp2 streams response bodies now. It always could - our shim was the part that couldn't.
The gap was ours, not nghttp2's
nghttp2_data_provider,NGHTTP2_ERR_DEFERREDandnghttp2_session_resume_dataexist for exactly this.ioxide_nghttp2_shim.conly ever exposed the whole-body submit, so there was nothing for a writer to push into. That makes this mostly a C change.Three new entry points:
ih2_submit_response_streamih2_stream_writeih2_stream_closeThe behaviour actually lives in
read_body: on an empty window it now returnsNGHTTP2_ERR_DEFERREDfor a streamed body instead of flagging EOF. That is what holds a stream open through a slow producer rather than truncating it at its first quiet moment.It is a PULL, and the C# hides that
Nghttp2ResponseWriteris anIBufferWriter<byte>and reads like a push, but underneath nghttp2 asks for bytes when it is ready to frame them. So a flush here means handed over, not on the wire - nghttp2 owns frame boundaries and timing.ioxide.http2owns its own framing and stages a DATA frame the moment you flush; that difference is the whole reason this needed native work instead of a C# writer.Verified on the wire
A byte count cannot tell streaming from one coalesced blob, so
nghttpwas the check:/feedstreams 251 MB in three seconds. The regression test counts frames, not bytes, for the same reason.One bug worth recording because it will catch the next person:
resume_datareturnsINVALID_ARGUMENTwhen the stream is not currently deferred, which happens whenever a write beats nghttp2 to it - most of them. Treating it as an error killed the connection after a single chunk: the finite case returned 0 bytes and/feedstopped dead at 1024.Also here
Examples.AspNetwas the last thing left at the repo root from the old Examples project. It moves toPlayground/AspNetand is verified still serving on the io_uring transport.0.4.177 across eleven packages.
ioxide.nghttp2is the one carrying a changed native library, and the packed nupkg was checked to contain the rebuilt.soexporting all three new symbols.Not done
on_data_chunk_recv), so a reader is nearly all C# - but nghttp2 credits the flow-control window automatically, so it would be a reader with no backpressure. Real credit-as-you-read needsnghttp2_option_set_no_auto_window_updateplusnghttp2_session_consume..sois linux-x64 only, built from nghttp2masterrather than a pinned tag. Worth pinning before a release goes out on it.Unit 36, Chaos 47, Http 38, E2E 46 - all 0 failed, 0 skipped.