Gatekeeper review of PR #63 (comment 15139):
- the 60 s default is what production runs (cmd/tapir never sets
UploadIdleTimeout) and no test covered it; 1 ns, 60 ms or 24 h all left
the suite green. Pinned via export_test.go, plus a socket test with the
timeout unset that accepts a body with 100 ms gaps (60 ms mutant red).
- the "upload idle bound unavailable" warning, the only signal that the
control did not apply, is now asserted (removing it goes red).
- the deadline clear on io.EOF was redundant (net/http resets the
deadline before its background read) and is removed; ADR-033 text
corrected (the part can end before the body, so "cleared on clean
EOF" did not hold).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A body that stopped arriving held one of the receive slots (#47) and a
spool file indefinitely. Every read of the upload body now pushes the
connection read deadline UploadIdleTimeout (default 60 s) ahead, so a
stalled body fails its read, Accept returns and the slot is released,
while a slow but moving body is never cut (ReadTimeout would cut it).
Two things found by running it over a socket:
- metrics.statusWriter had no Unwrap, so http.ResponseController could
not reach the connection and the bound silently did not apply in the
production handler stack. It now unwraps; a writer that still cannot
set a deadline logs "upload idle bound unavailable".
- clearing the deadline on any read error left net/http's pre-reply
drain of the unread remainder unbounded, so the connection hung. The
deadline is now cleared only on a clean io.EOF.
ADR-033 limitation amended. A 1-byte-per-59-s drip is still unbounded.
Closes#56
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>