Skip to content

feat(httpd): lazy streaming response body (ILO-482)#784

Merged
danieljohnmorris merged 5 commits into
mainfrom
feature/httpd-stream-body
May 25, 2026
Merged

feat(httpd): lazy streaming response body (ILO-482)#784
danieljohnmorris merged 5 commits into
mainfrom
feature/httpd-stream-body

Conversation

@danieljohnmorris
Copy link
Copy Markdown
Collaborator

Summary

An ilo httpd handler can now return a lazy response body (a LazyHttpLines/LazyStdinLines line iterator from get-stream/pst-stream or for-line stdin) and httpd streams it chunk-by-chunk: each line the iterator yields is written and flushed as its own chunked-transfer chunk, with no full-body buffering. The connection can stay open indefinitely (SSE, long-poll, tailing a growing source). A client disconnecting mid-stream exits the connection thread cleanly instead of panicking. Eager t (plain string, Content-Length) and L t (eager chunked list) bodies are unchanged.

Motivation: crew's /events/stream live SSE. Note: the held-open file-tail source is the separate ILO-488 tail-file follow-up; this PR is the response-body plumbing that consumes any lazy line source.

Repro before/after

Handler: rsp status:200 body:(get-stream "http://upstream/") proxying a slow upstream that emits one event every 200ms.

  • Before: handle_http_connection materialised the whole body before writing the first byte, so the client got all events at once at the end (no streaming).
  • After: each upstream line is re-emitted as its own chunk and flushed immediately, so the client sees event-0 well before the upstream has produced all events.

What blocked this, and why it's unblocked

The end-to-end streaming test was flaky (~50%). I first suspected get-stream batching, fixed by ILO-489 (merged), and rebased onto it. The test still flaked after the rebase. Root cause turned out to be a test-harness race, not the implementation: spawn_httpd's readiness probe did a raw TcpStream::connect, which httpd accepts as a real connection and dispatches to a handler thread. For the streaming test that startup handler ran get-stream and consumed the single-serve upstream's one accept() before the real test request arrived, so the test saw an empty body. The same probe pattern in tests/httpd_imports.rs was the source of the ILO-505 coverage flake.

Fix: wait for the ilo httpd listening on line on the child's stderr instead of probing the port, so the handler never runs during startup. Applied to both test files. After the fix, lazy_body_streams_incrementally passed 10/10 runs deterministically (~1.0s each; failures previously finished in ~0.63s with an empty first chunk).

What's in the diff (per commit)

  • feat(httpd): stream lazy response body chunk-by-chunk - LazyLines enum + BodyShape::Lazy streaming path in handle_http_connection
  • test(httpd): cover incremental lazy-body streaming - three integration tests + examples/httpd-stream.ilo
  • docs(httpd): document lazy streaming response body - SPEC.md, ai.txt, docs/streaming.md, CHANGELOG
  • test(httpd): wait for listening line, not tcp probe - the harness deflake; also closes ILO-505
  • docs(httpd): regenerate ai.txt from SPEC.md - picks up the richer body comment, satisfies the CI drift guard

Test plan

  • lazy_body_streams_incrementally 10/10 deterministic after the harness fix
  • full httpd_streaming and httpd_imports suites green
  • cargo fmt clean
  • cargo clippy --release --all-targets --features cranelift -- -D warnings clean
  • cargo test --release --features cranelift green except the two known baseline doctests (src/pkg.rs, src/verify.rs)
  • ai.txt drift guard clean (no diff after rebuild)

Follow-ups

  • ILO-488 tail-file source (the held-open growing-file iterator crew's live SSE ultimately needs) - separate issue.

Add LazyLines enum wrapping StdinLinesHandle/HttpLinesHandle behind a
single next_line() interface. Extend handle_http_connection to detect
LazyHttpLines/LazyStdinLines as the body value and stream each line as
its own chunk, flushed immediately - no full-body buffering. Client
disconnect mid-stream exits the thread cleanly instead of panicking.
Eager string and list bodies are unchanged.
Add httpd_streaming integration tests:
- lazy_body_streams_incrementally: proves a get-stream proxy body emits
  chunks as the upstream produces them (pads events past 16 KiB to force
  one line per read and avoid minreq buffer-granularity masking)
- eager_list_body_still_works: regression guard for the List body path
- client_disconnect_midstream_is_clean: server keeps accepting after a
  mid-stream client hangup

Add examples/httpd-stream.ilo showing the handler pattern.
Update SPEC.md with LazyHttpLines/LazyStdinLines body shape and note
that get-stream returns a lazy iterator. Update ai.txt alias line. Add
docs/streaming.md covering the lazy body mechanics, the PAD workaround
for minreq's read-buffer granularity, and the ILO-488 tail-file
follow-up. Add CHANGELOG entry.
…ng upstream (ILO-482)

the spawn_httpd readiness probe did a raw TcpStream::connect, which httpd
accepts as a real connection and dispatches to a handler thread. for the
streaming test that handler proxies an upstream via get-stream, so the
probe consumed the upstream's single accept() before the real test request
arrived and the test saw an empty body. wait for the "listening on" line on
the child's stderr instead, so the handler never runs during startup.

apply the same fix to tests/httpd_imports.rs, whose identical probe was the
source of the ILO-505 single_file_handler coverage flake. closes ILO-505.
build.rs regenerates ai.txt from SPEC.md; pick up the richer response body
comment (t buffered / L t eager chunked / lazy iterator) that the conflict
resolution had left on the older one-line form. satisfies the CI ai.txt
drift guard.
@danieljohnmorris danieljohnmorris added the mac-reviewing Currently being merge-prepped by mac-side agent label May 25, 2026
@codecov
Copy link
Copy Markdown

codecov Bot commented May 25, 2026

Codecov Report

❌ Patch coverage is 0% with 51 lines in your changes missing coverage. Please review.
✅ All tests successful. No failed tests found.

Files with missing lines Patch % Lines
src/main.rs 0.00% 46 Missing ⚠️
src/interpreter/mod.rs 0.00% 5 Missing ⚠️

📢 Thoughts on this report? Let us know!

@danieljohnmorris danieljohnmorris merged commit da2733f into main May 25, 2026
10 of 12 checks passed
@danieljohnmorris danieljohnmorris deleted the feature/httpd-stream-body branch May 25, 2026 00:47
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

mac-reviewing Currently being merge-prepped by mac-side agent

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant