diff --git a/tokio/src/net/tcp/socket.rs b/tokio/src/net/tcp/socket.rs index 43c5011cbb1..48dde0d1fbe 100644 --- a/tokio/src/net/tcp/socket.rs +++ b/tokio/src/net/tcp/socket.rs @@ -429,17 +429,41 @@ impl TcpSocket { /// > it. Rely on the `shutdown()`-followed-by-`read()`-eof technique instead. /// > /// > From [The ultimate `SO_LINGER` page, or: why is my tcp not reliable](https://blog.netherlabs.nl/articles/2009/01/18/the-ultimate-so_linger-page-or-why-is-my-tcp-not-reliable) + /// + /// Although this method is deprecated, it will not be removed from Tokio. + /// + /// Note that the special case of setting `SO_LINGER` to zero does not lead to blocking. Tokio + /// provides [`set_zero_linger`](Self::set_zero_linger) for this purpose. #[deprecated = "`SO_LINGER` causes the socket to block the thread on drop"] pub fn set_linger(&self, dur: Option) -> io::Result<()> { self.inner.set_linger(dur) } + /// Sets a linger duration of zero on this socket by setting the `SO_LINGER` option. + /// + /// This causes the connection to be forcefully aborted ("abortive close") when the socket is + /// dropped or closed. Instead of the normal TCP shutdown handshake (`FIN`/`ACK`), a TCP `RST` + /// (reset) segment is sent to the peer, and the socket immediately discards any unsent data + /// residing in the socket send buffer. This prevents the socket from entering the `TIME_WAIT` + /// state after closing it. + /// + /// This is a destructive action. Any data currently buffered by the OS but not yet transmitted + /// will be lost. The peer will likely receive a "Connection Reset" error rather than a clean + /// end-of-stream. + /// + /// See the documentation for [`set_linger`](Self::set_linger) for additional details on how + /// `SO_LINGER` works. + pub fn set_zero_linger(&self) -> io::Result<()> { + self.inner.set_linger(Some(Duration::ZERO)) + } + /// Reads the linger duration for this socket by getting the `SO_LINGER` /// option. /// - /// For more information about this option, see [`set_linger`]. + /// For more information about this option, see [`set_zero_linger`] and [`set_linger`]. /// /// [`set_linger`]: TcpSocket::set_linger + /// [`set_zero_linger`]: TcpSocket::set_zero_linger pub fn linger(&self) -> io::Result> { self.inner.linger() } diff --git a/tokio/src/net/tcp/stream.rs b/tokio/src/net/tcp/stream.rs index 33b15717a8d..32a1fce4c1b 100644 --- a/tokio/src/net/tcp/stream.rs +++ b/tokio/src/net/tcp/stream.rs @@ -1256,9 +1256,10 @@ impl TcpStream { /// Reads the linger duration for this socket by getting the `SO_LINGER` /// option. /// - /// For more information about this option, see [`set_linger`]. + /// For more information about this option, see [`set_zero_linger`] and [`set_linger`]. /// /// [`set_linger`]: TcpStream::set_linger + /// [`set_zero_linger`]: TcpStream::set_zero_linger /// /// # Examples /// @@ -1295,6 +1296,11 @@ impl TcpStream { /// > /// > From [The ultimate `SO_LINGER` page, or: why is my tcp not reliable](https://blog.netherlabs.nl/articles/2009/01/18/the-ultimate-so_linger-page-or-why-is-my-tcp-not-reliable) /// + /// Although this method is deprecated, it will not be removed from Tokio. + /// + /// Note that the special case of setting `SO_LINGER` to zero does not lead to blocking. + /// Tokio provides [`set_zero_linger`](Self::set_zero_linger) for this purpose. + /// /// # Examples /// /// ```no_run @@ -1312,6 +1318,39 @@ impl TcpStream { pub fn set_linger(&self, dur: Option) -> io::Result<()> { socket2::SockRef::from(self).set_linger(dur) } + + /// Sets a linger duration of zero on this socket by setting the `SO_LINGER` option. + /// + /// This causes the connection to be forcefully aborted ("abortive close") when the socket + /// is dropped or closed. Instead of the normal TCP shutdown handshake (`FIN`/`ACK`), a TCP + /// `RST` (reset) segment is sent to the peer, and the socket immediately discards any + /// unsent data residing in the socket send buffer. This prevents the socket from entering + /// the `TIME_WAIT` state after closing it. + /// + /// This is a destructive action. Any data currently buffered by the OS but not yet + /// transmitted will be lost. The peer will likely receive a "Connection Reset" error + /// rather than a clean end-of-stream. + /// + /// See the documentation for [`set_linger`](Self::set_linger) for additional details on + /// how `SO_LINGER` works. + /// + /// # Examples + /// + /// ```no_run + /// use std::time::Duration; + /// use tokio::net::TcpStream; + /// + /// # async fn dox() -> Result<(), Box> { + /// let stream = TcpStream::connect("127.0.0.1:8080").await?; + /// + /// stream.set_zero_linger()?; + /// assert_eq!(stream.linger()?, Some(Duration::ZERO)); + /// # Ok(()) + /// # } + /// ``` + pub fn set_zero_linger(&self) -> io::Result<()> { + socket2::SockRef::from(self).set_linger(Some(Duration::ZERO)) + } } /// Gets the value of the `IP_TTL` option for this socket. diff --git a/tokio/tests/tcp_connect.rs b/tokio/tests/tcp_connect.rs index d3638701eb5..4f095cefa21 100644 --- a/tokio/tests/tcp_connect.rs +++ b/tokio/tests/tcp_connect.rs @@ -181,7 +181,6 @@ mod linux { use std::{net, thread}; #[tokio::test] - #[expect(deprecated)] // set_linger is deprecated fn poll_hup() { let addr = assert_ok!("127.0.0.1:0".parse()); let mut srv = assert_ok!(TcpListener::bind(&addr)); @@ -189,7 +188,7 @@ mod linux { tokio::spawn(async move { let (mut client, _) = assert_ok!(srv.accept().await); - assert_ok!(client.set_linger(Some(Duration::from_millis(0)))); + assert_ok!(client.set_zero_linger()); assert_ok!(client.write_all(b"hello world").await); // TODO: Drop? @@ -198,7 +197,7 @@ mod linux { /* let t = thread::spawn(move || { let mut client = assert_ok!(srv.accept()).0; - client.set_linger(Some(Duration::from_millis(0))).unwrap(); + client.set_zero_linger().unwrap(); client.write(b"hello world").unwrap(); thread::sleep(Duration::from_millis(200)); }); diff --git a/tokio/tests/tcp_shutdown.rs b/tokio/tests/tcp_shutdown.rs index 130b079131d..75de931d305 100644 --- a/tokio/tests/tcp_shutdown.rs +++ b/tokio/tests/tcp_shutdown.rs @@ -2,7 +2,6 @@ #![cfg(all(feature = "full", not(target_os = "wasi"), not(miri)))] // Wasi doesn't support bind // No `socket` on miri. -use std::time::Duration; use tokio::io::{self, AsyncReadExt, AsyncWriteExt}; use tokio::net::{TcpListener, TcpStream}; use tokio::sync::oneshot::channel; @@ -33,7 +32,6 @@ async fn shutdown() { } #[tokio::test] -#[expect(deprecated)] // set_linger is deprecated async fn shutdown_after_tcp_reset() { let srv = assert_ok!(TcpListener::bind("127.0.0.1:0").await); let addr = assert_ok!(srv.local_addr()); @@ -51,7 +49,7 @@ async fn shutdown_after_tcp_reset() { let (stream, _) = assert_ok!(srv.accept().await); // By setting linger to 0 we will trigger a TCP reset - stream.set_linger(Some(Duration::new(0, 0))).unwrap(); + stream.set_zero_linger().unwrap(); connected_rx.await.unwrap(); drop(stream); diff --git a/tokio/tests/tcp_socket.rs b/tokio/tests/tcp_socket.rs index e5f25dedf94..6c4dcbd9424 100644 --- a/tokio/tests/tcp_socket.rs +++ b/tokio/tests/tcp_socket.rs @@ -62,7 +62,6 @@ async fn bind_before_connect() { } #[tokio::test] -#[expect(deprecated)] // set_linger is deprecated async fn basic_linger() { // Create server let addr = assert_ok!("127.0.0.1:0".parse()); @@ -71,7 +70,7 @@ async fn basic_linger() { assert!(srv.linger().unwrap().is_none()); - srv.set_linger(Some(Duration::new(0, 0))).unwrap(); + srv.set_zero_linger().unwrap(); assert_eq!(srv.linger().unwrap(), Some(Duration::new(0, 0))); } diff --git a/tokio/tests/tcp_stream.rs b/tokio/tests/tcp_stream.rs index c565b9af506..4b21108949f 100644 --- a/tokio/tests/tcp_stream.rs +++ b/tokio/tests/tcp_stream.rs @@ -25,6 +25,9 @@ async fn set_linger() { assert_ok!(stream.set_linger(Some(Duration::from_secs(1)))); assert_eq!(stream.linger().unwrap().unwrap().as_secs(), 1); + assert_ok!(stream.set_zero_linger()); + assert_eq!(stream.linger().unwrap().unwrap().as_secs(), 0); + assert_ok!(stream.set_linger(None)); assert!(stream.linger().unwrap().is_none()); }