fix(transport): treat Windows WSAENOBUFS as a transient send drop, not a stream teardown
windows-msix / package (x64, C:\Users\Public\ffmpeg, , x86_64-pc-windows-msvc, C:\t) (push) Successful in 7m18s
windows / build (aarch64-pc-windows-msvc) (push) Successful in 8m14s
windows / build (x86_64-pc-windows-msvc) (push) Successful in 9m17s
ci / web (push) Successful in 55s
ci / docs-site (push) Successful in 57s
docker / build-push (--build-arg FEDORA_VERSION=44, ci, ci/fedora-rpm.Dockerfile, punktfunk-fedora44-rpm) (push) Successful in 10s
decky / build-publish (push) Successful in 21s
apple / swift (push) Successful in 1m20s
docker / build-push (., web/Dockerfile, punktfunk-web) (push) Successful in 17s
docker / build-push (ci, ci/fedora-rpm.Dockerfile, punktfunk-fedora-rpm) (push) Successful in 15s
docker / build-push (ci, ci/rust-ci.Dockerfile, punktfunk-rust-ci) (push) Successful in 12s
docker / build-push (docs-site, docs-site/Dockerfile, punktfunk-docs) (push) Successful in 11s
ci / bench (push) Successful in 7m7s
flatpak / build-publish (push) Failing after 8m7s
docker / deploy-docs (push) Successful in 22s
release / apple (push) Successful in 9m3s
windows-host / package (push) Failing after 11m41s
deb / build-publish (push) Successful in 13m34s
ci / rust (push) Failing after 14m2s
arch / build-publish (push) Successful in 14m59s
android / android (push) Successful in 16m45s
rpm / build-publish (43, bazzite, punktfunk-fedora-rpm) (push) Successful in 15m16s
apple / screenshots (push) Successful in 6m38s
windows-msix / package (arm64, C:\Users\Public\ffmpeg-arm64, --no-default-features, aarch64-pc-windows-msvc, C:\t-a64) (push) Successful in 7m16s
rpm / build-publish (44, fedora-44, punktfunk-fedora44-rpm) (push) Successful in 17m38s
windows-msix / package (x64, C:\Users\Public\ffmpeg, , x86_64-pc-windows-msvc, C:\t) (push) Successful in 7m18s
windows / build (aarch64-pc-windows-msvc) (push) Successful in 8m14s
windows / build (x86_64-pc-windows-msvc) (push) Successful in 9m17s
ci / web (push) Successful in 55s
ci / docs-site (push) Successful in 57s
docker / build-push (--build-arg FEDORA_VERSION=44, ci, ci/fedora-rpm.Dockerfile, punktfunk-fedora44-rpm) (push) Successful in 10s
decky / build-publish (push) Successful in 21s
apple / swift (push) Successful in 1m20s
docker / build-push (., web/Dockerfile, punktfunk-web) (push) Successful in 17s
docker / build-push (ci, ci/fedora-rpm.Dockerfile, punktfunk-fedora-rpm) (push) Successful in 15s
docker / build-push (ci, ci/rust-ci.Dockerfile, punktfunk-rust-ci) (push) Successful in 12s
docker / build-push (docs-site, docs-site/Dockerfile, punktfunk-docs) (push) Successful in 11s
ci / bench (push) Successful in 7m7s
flatpak / build-publish (push) Failing after 8m7s
docker / deploy-docs (push) Successful in 22s
release / apple (push) Successful in 9m3s
windows-host / package (push) Failing after 11m41s
deb / build-publish (push) Successful in 13m34s
ci / rust (push) Failing after 14m2s
arch / build-publish (push) Successful in 14m59s
android / android (push) Successful in 16m45s
rpm / build-publish (43, bazzite, punktfunk-fedora-rpm) (push) Successful in 15m16s
apple / screenshots (push) Successful in 6m38s
windows-msix / package (arm64, C:\Users\Public\ffmpeg-arm64, --no-default-features, aarch64-pc-windows-msvc, C:\t-a64) (push) Successful in 7m16s
rpm / build-publish (44, fedora-44, punktfunk-fedora44-rpm) (push) Successful in 17m38s
`is_transient_io` only recognized `ENOBUFS` on unix; on Windows the `#[cfg(not(unix))]` arm returned `false`, so `WSAENOBUFS` (10055) — which Rust maps to `ErrorKind::Uncategorized`, missing the `WouldBlock` arm — propagated out of the USO `send_gso` path through `Session::send_sealed` and killed the session with `native::stream` "send failed — stopping stream". A high-bitrate keyframe burst (one `WSASendMsg` USO super-buffer is up to ~512 segments) momentarily exhausts the socket send buffer / AFD non-paged pool; it's a lossy drop that FEC + the next frame recover, exactly like the unix `ath11k` `ENOBUFS` case the classifier already handles. Fires independently of client platform (Windows/macOS clients both saw the crash) because it's the Windows *host's* send socket. Add a `#[cfg(windows)]` arm matching `WSAENOBUFS` plus the `WSAENET*`/`WSAEHOST*` network-path family (the Windows counterparts of the droppable unix set), and extend the classifier test with per-platform raw-errno coverage so the Windows CI runner exercises the 10055 path. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -45,12 +45,20 @@ const RECV_BUF: usize = MAX_DATAGRAM_BYTES + 1;
|
|||||||
/// died at full rate over WiFi). Same lossy-drop contract as `WouldBlock`; FEC + the next frame
|
/// died at full rate over WiFi). Same lossy-drop contract as `WouldBlock`; FEC + the next frame
|
||||||
/// recover. Asynchronous network-path blips (`ENETUNREACH`/`EHOSTUNREACH`/`ENETDOWN`/`EHOSTDOWN`)
|
/// recover. Asynchronous network-path blips (`ENETUNREACH`/`EHOSTUNREACH`/`ENETDOWN`/`EHOSTDOWN`)
|
||||||
/// are droppable for the same reason a stale ICMP is.
|
/// are droppable for the same reason a stale ICMP is.
|
||||||
|
/// - Windows `WSAENOBUFS` (10055): the exact analogue of unix `ENOBUFS` — a high-bitrate keyframe
|
||||||
|
/// burst (one `WSASendMsg` USO super-buffer is up to ~512 segments ≈ 700 KB) momentarily exhausts
|
||||||
|
/// the socket send buffer / AFD non-paged pool, and Winsock reports `WSAENOBUFS`, which Rust maps
|
||||||
|
/// to `ErrorKind::Uncategorized` (so the `WouldBlock` arm misses it, exactly like unix `ENOBUFS`).
|
||||||
|
/// Without treating it as transient a Windows host tears the whole session down under load
|
||||||
|
/// (observed live: `native::stream` "send failed — stopping stream" on a paced video burst). Same
|
||||||
|
/// lossy-drop contract; FEC + the next frame recover. The `WSAENET*`/`WSAEHOST*` family is the
|
||||||
|
/// Windows counterpart of the droppable unix network-path blips above.
|
||||||
fn is_transient_io(e: &std::io::Error) -> bool {
|
fn is_transient_io(e: &std::io::Error) -> bool {
|
||||||
use std::io::ErrorKind::{ConnectionRefused, ConnectionReset, WouldBlock};
|
use std::io::ErrorKind::{ConnectionRefused, ConnectionReset, WouldBlock};
|
||||||
if matches!(e.kind(), WouldBlock | ConnectionRefused | ConnectionReset) {
|
if matches!(e.kind(), WouldBlock | ConnectionRefused | ConnectionReset) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
// `ENOBUFS` & friends have no stable `ErrorKind`, so match the raw errno (unix only).
|
// `ENOBUFS` & friends have no stable `ErrorKind`, so match the raw errno.
|
||||||
#[cfg(unix)]
|
#[cfg(unix)]
|
||||||
{
|
{
|
||||||
matches!(
|
matches!(
|
||||||
@@ -62,7 +70,20 @@ fn is_transient_io(e: &std::io::Error) -> bool {
|
|||||||
| Some(libc::EHOSTDOWN)
|
| Some(libc::EHOSTDOWN)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
#[cfg(not(unix))]
|
// Windows Winsock codes (WSAE*), raw like the sibling `uso_unsupported`. WSAEWOULDBLOCK (10035)
|
||||||
|
// already maps to `ErrorKind::WouldBlock` above, so it isn't repeated here.
|
||||||
|
#[cfg(windows)]
|
||||||
|
{
|
||||||
|
matches!(
|
||||||
|
e.raw_os_error(),
|
||||||
|
Some(10055) // WSAENOBUFS — tx queue / send buffer full (the dominant high-bitrate drop)
|
||||||
|
| Some(10051) // WSAENETUNREACH
|
||||||
|
| Some(10065) // WSAEHOSTUNREACH
|
||||||
|
| Some(10050) // WSAENETDOWN
|
||||||
|
| Some(10064) // WSAEHOSTDOWN
|
||||||
|
)
|
||||||
|
}
|
||||||
|
#[cfg(not(any(unix, windows)))]
|
||||||
{
|
{
|
||||||
false
|
false
|
||||||
}
|
}
|
||||||
@@ -324,6 +345,53 @@ mod tests {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// The raw-errno tx-queue-full / network-blip codes have no stable `ErrorKind` (they surface as
|
||||||
|
/// `Uncategorized`), so they only get caught by the platform `raw_os_error()` arms. A burst that
|
||||||
|
/// momentarily exhausts the send buffer must stay a lossy drop, never a teardown — this is the
|
||||||
|
/// regression guard for the Windows `WSAENOBUFS` (10055) session crash and the unix `ENOBUFS`
|
||||||
|
/// wlan-driver case. Gated per platform because a code is only classified on its own OS.
|
||||||
|
#[test]
|
||||||
|
fn transient_io_covers_raw_tx_queue_and_path_codes() {
|
||||||
|
use std::io::Error;
|
||||||
|
|
||||||
|
#[cfg(unix)]
|
||||||
|
{
|
||||||
|
for code in [
|
||||||
|
libc::ENOBUFS,
|
||||||
|
libc::ENETUNREACH,
|
||||||
|
libc::EHOSTUNREACH,
|
||||||
|
libc::ENETDOWN,
|
||||||
|
libc::EHOSTDOWN,
|
||||||
|
] {
|
||||||
|
assert!(
|
||||||
|
is_transient_io(&Error::from_raw_os_error(code)),
|
||||||
|
"unix errno {code} should be transient"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
// A genuine failure with no stable ErrorKind must still tear down.
|
||||||
|
assert!(
|
||||||
|
!is_transient_io(&Error::from_raw_os_error(libc::EACCES)),
|
||||||
|
"EACCES must stay fatal"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(windows)]
|
||||||
|
{
|
||||||
|
// WSAENOBUFS / WSAENETUNREACH / WSAEHOSTUNREACH / WSAENETDOWN / WSAEHOSTDOWN.
|
||||||
|
for code in [10055, 10051, 10065, 10050, 10064] {
|
||||||
|
assert!(
|
||||||
|
is_transient_io(&Error::from_raw_os_error(code)),
|
||||||
|
"WSA code {code} should be transient"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
// WSAEACCES (10013) — a real failure that must stay fatal.
|
||||||
|
assert!(
|
||||||
|
!is_transient_io(&Error::from_raw_os_error(10013)),
|
||||||
|
"WSAEACCES must stay fatal"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// `send_batch` delivers a whole frame's worth of packets over real loopback UDP — exercising
|
/// `send_batch` delivers a whole frame's worth of packets over real loopback UDP — exercising
|
||||||
/// the `sendmmsg` path on Linux (the scalar-loop default elsewhere). 100 × 200 B = 20 KB fits
|
/// the `sendmmsg` path on Linux (the scalar-loop default elsewhere). 100 × 200 B = 20 KB fits
|
||||||
/// the socket buffer, so loopback is lossless and every packet must arrive intact + in order.
|
/// the socket buffer, so loopback is lossless and every packet must arrive intact + in order.
|
||||||
|
|||||||
Reference in New Issue
Block a user