Skip to content
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
26 changes: 25 additions & 1 deletion tokio/src/net/tcp/socket.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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()
}
Expand Down
41 changes: 40 additions & 1 deletion tokio/src/net/tcp/stream.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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
///
Expand Down Expand Up @@ -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
Expand All @@ -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.
Expand Down
5 changes: 2 additions & 3 deletions tokio/tests/tcp_connect.rs
Original file line number Diff line number Diff line change
Expand Up @@ -181,15 +181,14 @@ 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));
let addr = assert_ok!(srv.local_addr());

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?
Expand All @@ -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));
});
Expand Down
4 changes: 1 addition & 3 deletions tokio/tests/tcp_shutdown.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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());
Expand All @@ -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);
Expand Down
3 changes: 1 addition & 2 deletions tokio/tests/tcp_socket.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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());
Expand All @@ -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)));
}

Expand Down
3 changes: 3 additions & 0 deletions tokio/tests/tcp_stream.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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());
}
Expand Down
Loading