From 5f04d14d8136254f602590ef3a984c8eca8dae3c Mon Sep 17 00:00:00 2001 From: Qi Date: Thu, 31 Jul 2025 19:58:01 +0800 Subject: [PATCH] net: add `TcpStream::quickack` and `TcpStream::set_quickack` (#7490) Signed-off-by: ADD-SP --- tokio/src/net/tcp/stream.rs | 65 +++++++++++++++++++++++++++++++++ tokio/tests/net_quickack.rs | 71 +++++++++++++++++++++++++++++++++++++ 2 files changed, 136 insertions(+) create mode 100644 tokio/tests/net_quickack.rs diff --git a/tokio/src/net/tcp/stream.rs b/tokio/src/net/tcp/stream.rs index f64a526b4..e544ebf56 100644 --- a/tokio/src/net/tcp/stream.rs +++ b/tokio/src/net/tcp/stream.rs @@ -1170,6 +1170,71 @@ impl TcpStream { self.io.set_nodelay(nodelay) } + /// Gets the value of the `TCP_QUICKACK` option on this socket. + /// + /// For more information about this option, see [`TcpStream::set_quickack`]. + /// + /// # Examples + /// + /// ```no_run + /// use tokio::net::TcpStream; + /// + /// # async fn dox() -> Result<(), Box> { + /// let stream = TcpStream::connect("127.0.0.1:8080").await?; + /// + /// stream.quickack()?; + /// # Ok(()) + /// # } + /// ``` + #[cfg(any( + target_os = "linux", + target_os = "android", + target_os = "fuchsia", + target_os = "cygwin", + ))] + #[cfg_attr( + docsrs, + doc(cfg(any(target_os = "linux", target_os = "android", target_os = "fuchsia"))) + )] + pub fn quickack(&self) -> io::Result { + socket2::SockRef::from(self).tcp_quickack() + } + + /// Enable or disable `TCP_QUICKACK`. + /// + /// This flag causes Linux to eagerly send `ACK`s rather than delaying them. + /// Linux may reset this flag after further operations on the socket. + /// + /// See [`man 7 tcp`](https://man7.org/linux/man-pages/man7/tcp.7.html) and + /// [TCP delayed acknowledgment](https://en.wikipedia.org/wiki/TCP_delayed_acknowledgment) + /// for more information. + /// + /// # Examples + /// + /// ```no_run + /// use tokio::net::TcpStream; + /// + /// # async fn dox() -> Result<(), Box> { + /// let stream = TcpStream::connect("127.0.0.1:8080").await?; + /// + /// stream.set_quickack(true)?; + /// # Ok(()) + /// # } + /// ``` + #[cfg(any( + target_os = "linux", + target_os = "android", + target_os = "fuchsia", + target_os = "cygwin", + ))] + #[cfg_attr( + docsrs, + doc(cfg(any(target_os = "linux", target_os = "android", target_os = "fuchsia"))) + )] + pub fn set_quickack(&self, quickack: bool) -> io::Result<()> { + socket2::SockRef::from(self).set_tcp_quickack(quickack) + } + cfg_not_wasi! { /// Reads the linger duration for this socket by getting the `SO_LINGER` /// option. diff --git a/tokio/tests/net_quickack.rs b/tokio/tests/net_quickack.rs new file mode 100644 index 000000000..a5fd35df7 --- /dev/null +++ b/tokio/tests/net_quickack.rs @@ -0,0 +1,71 @@ +#![warn(rust_2018_idioms)] +#![cfg(all( + feature = "net", + any( + target_os = "linux", + target_os = "android", + target_os = "fuchsia", + target_os = "cygwin", + ) +))] +#![cfg(not(miri))] // No `socket` in miri. + +use tokio::io::{AsyncReadExt, AsyncWriteExt}; +use tokio::net::TcpListener; +use tokio::net::TcpStream; +use tokio::sync::oneshot; + +#[tokio::test] +async fn socket_works_with_quickack() { + const MESSAGE: &str = "Hello, tokio!"; + + let (tx_port, rx_port) = oneshot::channel(); + + let server = tokio::spawn(async move { + let listener = TcpListener::bind("127.0.0.1:0").await.unwrap(); + let addr = listener.local_addr().unwrap(); + + tx_port.send(addr.port()).unwrap(); + + let (mut stream, _) = listener.accept().await.unwrap(); + stream.set_quickack(true).unwrap(); + assert!(stream.quickack().unwrap()); + + stream.write_all(MESSAGE.as_bytes()).await.unwrap(); + + let mut buf = vec![0; MESSAGE.len()]; + stream.read_exact(&mut buf).await.unwrap(); + assert_eq!(buf, MESSAGE.as_bytes()); + + // There is nothing special about setting quickack to false + // at this point, we just want to test the `false` case. + stream.set_quickack(false).unwrap(); + assert!(!stream.quickack().unwrap()); + + stream.shutdown().await.unwrap(); + }); + + let port = rx_port.await.unwrap(); + let client = tokio::spawn(async move { + let mut stream = TcpStream::connect(format!("127.0.0.1:{port}")) + .await + .unwrap(); + stream.set_quickack(true).unwrap(); + assert!(stream.quickack().unwrap()); + + let mut buf = vec![0; MESSAGE.len()]; + stream.read_exact(&mut buf).await.unwrap(); + assert_eq!(buf, MESSAGE.as_bytes()); + + stream.write_all(MESSAGE.as_bytes()).await.unwrap(); + + // There is nothing special about setting quickack to false + // at this point, we just want to test the `false` case. + stream.set_quickack(false).unwrap(); + assert!(!stream.quickack().unwrap()); + + stream.shutdown().await.unwrap(); + }); + + tokio::try_join!(server, client).unwrap(); +}