test(curl): tolerate an already closed TLS fixture socket

A client can consume a complete length-delimited or chunked response and
close before the fixture calls shutdown(Write). Treat NotConnected at
that teardown step as complete; keep all other socket errors and the
HTTP framing, invalid-record and certificate assertions unchanged.

The original fixture failed 3 of 128 runs at 32-way parallelism, with
backtraces pointing to the shutdown call. Validation includes the seven
TLS EOF cases, the repeated framing case, cargo fmt --all, strict
workspace Clippy and full cargo nextest run --no-fail-fast.

Final full suite: 20,599 passed, 16 skipped at 32-way parallelism.
The existing ci retry profile was selected, but no test needed a retry.
Earlier loaded-run timing failures are retained in the local evidence.
This commit is contained in:
ldm0
2026-10-11 18:06:45 +08:00
parent 9145d24bbd
commit 30ff1476da
+6 -1
View File
@@ -144,7 +144,12 @@ fn exchange_protocol(
}
// Send TCP EOF and drain any in-flight HTTP/2 SETTINGS ACK. Closing
// a socket with unread data could send RST instead of the intended FIN.
stream.sock.shutdown(Shutdown::Write)?;
// A client can finish a framed response and close before this shutdown.
if let Err(error) = stream.sock.shutdown(Shutdown::Write)
&& error.kind() != std::io::ErrorKind::NotConnected
{
return Err(error.into());
}
let mut pending = [0; 4096];
while matches!(stream.sock.read(&mut pending), Ok(n) if n > 0) {}
// StreamOwned does not send close_notify on Drop.