Optimize chunked write path and add tryWrite - #1922
Conversation
|
Please feel free to take, change, simplify, or remove any part of this as you If only parts of it are worth keeping, that is perfectly fine too. |
|
Quick scroll through, my gut feeling says it's high quality high probability of being merged but I still need to review this for a long time |
|
Thanks for the quick check. Please take your time. |
|
Addressed the review points. Changes:
|
|
I reduced the follow-up scope and tried to align it with the existing repo structure. The net diff now only contains the tryWrite core in HttpResponse/HttpResponseData plus a separate manual example ( This seemed closer to the existing example-based usage pattern already present in the repo than keeping the previous smoke/CI wiring. |
|
I implemented the The only subtle part was keeping With So the current version does the smallest thing:
Your suggestion pointed to a cleaner path, and my inner monk would not let me leave it half-done once that became obvious. If you want to keep the previous PR shape and leave this optimization for later, I can revert this part. |
|
It would be cool to see a new release of ultimate express with the current write feature before I continue review this |
Released today |
|
I did one more pass after rebasing this PR onto current master and kept the final adjustments contained to the existing implementation. What I changed:
The reviewed Local A/B benchmarkBefore is the rebased PR before this final pass ( Results are medians of five interleaved runs with
At 128 KiB this reduces three separate header/body/trailer sends to one header send with These are local workload-specific results, but they confirm that the new path produces the intended syscall reduction without regressing small or large responses. Required uSockets companion fixI also found that partial The required change is in if (written != header_length + payload_length) {
s->context->loop->data.last_write_failed = 1;
us_poll_change(&s->p, s->context->loop, LIBUS_SOCKET_READABLE | LIBUS_SOCKET_WRITABLE);
}Without I would keep this as a separate one-line uSockets change rather than adding a uSockets submodule update to this PR. All seven local ASan unit executables, strict warning builds, chunk-framing checks, partial body/trailer continuation, and the 16 MiB backpressure case passed. |
Attempt to improve chunked streaming by integrating tryWrite and tightening write
@uNetworkingAB I wanted to try a small uWS-side improvement for the chunked
/ no-content-length HTTP response path discussed around:
tryWritemethod? #884My intent here was not to present this as a perfect or final answer, but to
test whether integrating
tryWriteinto this area could also let me tightenthe existing
writepath with a small and contained change surface.What made this seem worth trying:
tryWriteas not yetintegrated in this path
underlying no-content-length streaming path than toward any higher-level
framework overhead
implementation attempt
What I implemented:
HttpResponse::tryWrite(std::string_view)for chunked responseswrite()onto a shared internal write coreend()through that same core so framing, timeout handling,completion, and close behavior stay aligned
remaining body suffix via
getWriteOffset()Why I thought this was the right experiment:
tryEndwrite,tryWrite, and terminating chunkedendshare oneimplementation instead of evolving independently
architectural change
Local benchmark notes:
writepathmoved from about
1073 req/sbefore this change to about3186 req/safter it
wrkchecks for the newchunked streaming behavior showed:
GET /trywritewith128 KiB:30882.97 req/s,3.77 GB/s,p99 3.74 msGET /trywrite-endwith256 KiB:30500.77 req/s,7.45 GB/s,p99 3.92 msThese are only local workload-specific numbers, but they were encouraging
enough for me to keep the change.
Technical notes:
helper instead of keeping separate behavior between
write()and chunkedend()0\r\n\r\nwhen chunkedend()completesthe code can distinguish between:
suffix should be written
getWriteOffset(), soonWritablecan resume from the remaining body suffix without needing asecond chunk-framing path
tryWrite(...) -> false -> onWritable(...) -> tryWrite(rest)and
tryWrite(...) -> false -> end(rest)follow one consistent modelValidation:
Crc32/tests/smoke.mjsflow to coverwrite,tryWrite, andtryWrite + endpassed in the validated environment