Skip to content

Commit 1197c11

Browse files
committed
Log and ignore transmit errors
Some more transient transmit errors have been observed. E.g. for some peers transmission failed with ``` error: Os { code: 101, kind: Other, message: "Network is unreachable" } ``` That lead the endpoint to shut down and not process any messages for any other clients. To prevent this, this changes the behavior to ignore errors inside the endpoint. In order to not lose all visibility, the change however adds low frequency logging for the errors.
1 parent 5425389 commit 1197c11

3 files changed

Lines changed: 80 additions & 29 deletions

File tree

quinn/src/platform/fallback.rs

Lines changed: 24 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,13 +2,15 @@ use std::{
22
io::{self, IoSliceMut},
33
net::SocketAddr,
44
task::{Context, Poll},
5+
time::Instant,
56
};
67

78
use futures::ready;
89
use proto::Transmit;
910
use tokio::io::ReadBuf;
11+
use tracing::warn;
1012

11-
use super::RecvMeta;
13+
use super::{RecvMeta, IO_ERROR_LOG_INTERVAL};
1214

1315
/// Tokio-compatible UDP socket with some useful specializations.
1416
///
@@ -17,18 +19,21 @@ use super::RecvMeta;
1719
#[derive(Debug)]
1820
pub struct UdpSocket {
1921
io: tokio::net::UdpSocket,
22+
last_send_error: Instant,
2023
}
2124

2225
impl UdpSocket {
2326
pub fn from_std(socket: std::net::UdpSocket) -> io::Result<UdpSocket> {
2427
socket.set_nonblocking(true)?;
28+
let now = Instant::now();
2529
Ok(UdpSocket {
2630
io: tokio::net::UdpSocket::from_std(socket)?,
31+
last_send_error: now.checked_sub(2 * IO_ERROR_LOG_INTERVAL).unwrap_or(now),
2732
})
2833
}
2934

3035
pub fn poll_send(
31-
&self,
36+
&mut self,
3237
cx: &mut Context,
3338
transmits: &[Transmit],
3439
) -> Poll<Result<usize, io::Error>> {
@@ -45,7 +50,23 @@ impl UdpSocket {
4550
// errors being either harmlessly transient (in the case of WouldBlock) or
4651
// recurring on the next call.
4752
Poll::Ready(Err(_)) | Poll::Pending if sent != 0 => return Poll::Ready(Ok(sent)),
48-
Poll::Ready(Err(e)) => return Poll::Ready(Err(e)),
53+
Poll::Ready(Err(e)) => {
54+
// WouldBlock is expected to be returned as `Poll::Pending`
55+
debug_assert!(e.kind() != io::ErrorKind::WouldBlock);
56+
57+
// Errors are ignored, since they will ususally be handled
58+
// by higher level retransmits and timeouts.
59+
// - PermissionDenied errors have been observed due to iptable rules.
60+
// Those are not fatal errors, since the
61+
// configuration can be dynamically changed.
62+
// - Destination unreachable errors have been observed for other
63+
let now = Instant::now();
64+
if now.saturating_duration_since(self.last_send_error) > IO_ERROR_LOG_INTERVAL {
65+
self.last_send_error = now;
66+
warn!("sendmsg error: {:?}, transmit: {:?}", e, transmit);
67+
}
68+
sent += 1;
69+
}
4970
Poll::Pending => return Poll::Pending,
5071
}
5172
}

quinn/src/platform/mod.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -62,3 +62,7 @@ impl Default for RecvMeta {
6262
}
6363
}
6464
}
65+
66+
/// Log at most 1 IO error per minute
67+
#[cfg(unix)]
68+
const IO_ERROR_LOG_INTERVAL: std::time::Duration = std::time::Duration::from_secs(60);

quinn/src/platform/unix.rs

Lines changed: 52 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -6,14 +6,16 @@ use std::{
66
os::unix::io::AsRawFd,
77
ptr,
88
task::{Context, Poll},
9+
time::Instant,
910
};
1011

1112
use futures::ready;
1213
use lazy_static::lazy_static;
1314
use proto::{EcnCodepoint, Transmit};
1415
use tokio::io::unix::AsyncFd;
16+
use tracing::warn;
1517

16-
use super::{cmsg, RecvMeta, UdpCapabilities};
18+
use super::{cmsg, RecvMeta, UdpCapabilities, IO_ERROR_LOG_INTERVAL};
1719

1820
#[cfg(target_os = "freebsd")]
1921
type IpTosTy = libc::c_uchar;
@@ -27,26 +29,30 @@ type IpTosTy = libc::c_int;
2729
#[derive(Debug)]
2830
pub struct UdpSocket {
2931
io: AsyncFd<mio::net::UdpSocket>,
32+
last_send_error: Instant,
3033
}
3134

3235
impl UdpSocket {
3336
pub fn from_std(socket: std::net::UdpSocket) -> io::Result<UdpSocket> {
3437
socket.set_nonblocking(true)?;
3538
let io = mio::net::UdpSocket::from_std(socket);
3639
init(&io)?;
40+
let now = Instant::now();
3741
Ok(UdpSocket {
3842
io: AsyncFd::new(io)?,
43+
last_send_error: now.checked_sub(2 * IO_ERROR_LOG_INTERVAL).unwrap_or(now),
3944
})
4045
}
4146

4247
pub fn poll_send(
43-
&self,
48+
&mut self,
4449
cx: &mut Context,
4550
transmits: &[Transmit],
4651
) -> Poll<Result<usize, io::Error>> {
4752
loop {
53+
let last_send_error = &mut self.last_send_error;
4854
let mut guard = ready!(self.io.poll_write_ready(cx))?;
49-
if let Ok(res) = guard.try_io(|io| send(io.get_ref(), transmits)) {
55+
if let Ok(res) = guard.try_io(|io| send(io.get_ref(), last_send_error, transmits)) {
5056
return Poll::Ready(res);
5157
}
5258
}
@@ -184,7 +190,11 @@ fn init(io: &mio::net::UdpSocket) -> io::Result<()> {
184190
}
185191

186192
#[cfg(not(any(target_os = "macos", target_os = "ios")))]
187-
fn send(io: &mio::net::UdpSocket, transmits: &[Transmit]) -> io::Result<usize> {
193+
fn send(
194+
io: &mio::net::UdpSocket,
195+
last_send_error: &mut Instant,
196+
transmits: &[Transmit],
197+
) -> io::Result<usize> {
188198
let mut msgs: [libc::mmsghdr; BATCH_SIZE] = unsafe { mem::zeroed() };
189199
let mut iovecs: [libc::iovec; BATCH_SIZE] = unsafe { mem::zeroed() };
190200
let mut cmsgs = [cmsg::Aligned([0u8; CMSG_LEN]); BATCH_SIZE];
@@ -224,23 +234,38 @@ fn send(io: &mio::net::UdpSocket, transmits: &[Transmit]) -> io::Result<usize> {
224234
// Retry the transmission
225235
continue;
226236
}
227-
io::ErrorKind::PermissionDenied => {
228-
// Transmissions can fail with permission errors for example
229-
// due to iptable rules. Those are not fatal errors, since the
230-
// configuration can be dynamically changed.
231-
// In this case we drop the outgoing packets and let higher
232-
// layers retransmit if required.
233-
return Ok(num_transmits);
237+
io::ErrorKind::WouldBlock => return Err(e),
238+
_ => {
239+
// Other errors are ignored, since they will ususally be handled
240+
// by higher level retransmits and timeouts.
241+
// - PermissionDenied errors have been observed due to iptable rules.
242+
// Those are not fatal errors, since the
243+
// configuration can be dynamically changed.
244+
// - Destination unreachable errors have been observed for other
245+
let now = Instant::now();
246+
if now.saturating_duration_since(*last_send_error) > IO_ERROR_LOG_INTERVAL {
247+
*last_send_error = now;
248+
warn!("sendmmsg error: {:?}, transmits: {:?}", e, transmits);
249+
}
250+
251+
// The ERRORS section in https://man7.org/linux/man-pages/man2/sendmmsg.2.html
252+
// describes that errors will only be returned if no message could be transmitted
253+
// at all. Therefore drop the first (problematic) message,
254+
// and retry the remaining ones.
255+
return Ok(num_transmits.min(1));
234256
}
235-
_ => return Err(e),
236257
}
237258
}
238259
return Ok(n as usize);
239260
}
240261
}
241262

242263
#[cfg(any(target_os = "macos", target_os = "ios"))]
243-
fn send(io: &mio::net::UdpSocket, transmits: &[Transmit]) -> io::Result<usize> {
264+
fn send(
265+
io: &mio::net::UdpSocket,
266+
last_send_error: &mut Instant,
267+
transmits: &[Transmit],
268+
) -> io::Result<usize> {
244269
let mut hdr: libc::msghdr = unsafe { mem::zeroed() };
245270
let mut iov: libc::iovec = unsafe { mem::zeroed() };
246271
let mut ctrl = cmsg::Aligned([0u8; CMSG_LEN]);
@@ -255,21 +280,22 @@ fn send(io: &mio::net::UdpSocket, transmits: &[Transmit]) -> io::Result<usize> {
255280
io::ErrorKind::Interrupted => {
256281
// Retry the transmission
257282
}
258-
io::ErrorKind::PermissionDenied => {
259-
// Transmissions can fail with permission errors for example
260-
// due to iptable rules. Those are not fatal errors, since the
261-
// configuration can be dynamically changed.
262-
// In this case we drop the outgoing packets and let higher
263-
// layers retransmit if required.
283+
io::ErrorKind::WouldBlock if sent != 0 => return Ok(sent),
284+
io::ErrorKind::WouldBlock => return Err(e),
285+
_ => {
286+
// Other errors are ignored, since they will ususally be handled
287+
// by higher level retransmits and timeouts.
288+
// - PermissionDenied errors have been observed due to iptable rules.
289+
// Those are not fatal errors, since the
290+
// configuration can be dynamically changed.
291+
// - Destination unreachable errors have been observed for other
292+
let now = Instant::now();
293+
if now.saturating_duration_since(*last_send_error) > IO_ERROR_LOG_INTERVAL {
294+
*last_send_error = now;
295+
warn!("sendmsg error: {:?}, transmit: {:?}", e, &transmits[sent]);
296+
}
264297
sent += 1;
265298
}
266-
_ if sent != 0 => {
267-
// We need to report that some packets were sent in this case, so we rely on
268-
// errors being either harmlessly transient (in the case of WouldBlock) or
269-
// recurring on the next call.
270-
return Ok(sent);
271-
}
272-
_ => return Err(e),
273299
}
274300
} else {
275301
sent += 1;

0 commit comments

Comments
 (0)