net: add TcpStream::set_zero_linger (#7837)

This commit is contained in:
Alice Ryhl
2026-01-08 08:18:04 +00:00
committed by GitHub
parent 2d4853ea16
commit f1cb007a28
6 changed files with 72 additions and 10 deletions
+25 -1
View File
@@ -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<Duration>) -> 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<Option<Duration>> {
self.inner.linger()
}
+40 -1
View File
@@ -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<Duration>) -> 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<dyn std::error::Error>> {
/// 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.
+2 -3
View File
@@ -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));
});
+1 -3
View File
@@ -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);
+1 -2
View File
@@ -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)));
}
+3
View File
@@ -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());
}