Repository: tokio-rs/mio
Pull request #1387 (2020-11-05 22:52:52)

Support setting SO_LINGER on TcpSocket
Because tokio `TcpStream` and mio `TcpStream` now use `std::net::TcpStream` as the underlying stream, there isn't a way to set linger options.

I'm aware of #1381 and the dislike of duplicating features. This is the last functionality I need.

The plan is to convert the stream by calling `as_raw_fd()` and set the linger option.  


Patch:
diff --git a/src/net/tcp/socket.rs b/src/net/tcp/socket.rs
index b43690b..249505a 100644
--- a/src/net/tcp/socket.rs
+++ b/src/net/tcp/socket.rs
@@ -106,6 +106,11 @@ impl TcpSocket {
         sys::tcp::set_linger(self.sys, dur)
     }
 
+    /// Gets the value of `SO_LINGER` on this socket
+    pub fn get_linger(&self) -> io::Result<Option<Duration>> {
+        sys::tcp::get_linger(self.sys)
+    }
+
     /// Sets the value of `SO_RCVBUF` on this socket.
     pub fn set_recv_buffer_size(&self, size: u32) -> io::Result<()> {
         sys::tcp::set_recv_buffer_size(self.sys, size)
diff --git a/src/sys/shell/tcp.rs b/src/sys/shell/tcp.rs
index b67e33d..f51d6ca 100644
--- a/src/sys/shell/tcp.rs
+++ b/src/sys/shell/tcp.rs
@@ -50,6 +50,10 @@ pub(crate) fn set_linger(_: TcpSocket, _: Option<Duration>) -> io::Result<()> {
     os_required!();
 }
 
+pub(crate) fn get_linger(_: TcpSocket) -> io::Result<Option<Duration>> {
+    os_required!();
+}
+
 pub(crate) fn set_recv_buffer_size(_: TcpSocket, _: u32) -> io::Result<()> {
     os_required!();
 }
diff --git a/src/sys/unix/tcp.rs b/src/sys/unix/tcp.rs
index 8d5f5aa..86d2734 100644
--- a/src/sys/unix/tcp.rs
+++ b/src/sys/unix/tcp.rs
@@ -129,6 +129,25 @@ pub(crate) fn set_linger(socket: TcpSocket, dur: Option<Duration>) -> io::Result
     )).map(|_| ())
 }
 
+pub(crate) fn get_linger(socket: TcpSocket) -> io::Result<Option<Duration>> {
+    let mut val: libc::linger =  unsafe { std::mem::zeroed() };
+    let mut len = mem::size_of::<libc::linger>() as libc::socklen_t;
+
+    syscall!(getsockopt(
+        socket,
+        libc::SOL_SOCKET,
+        libc::SO_LINGER,
+        &mut val as *mut _ as *mut _,
+        &mut len,
+    ))?;
+
+    if val.l_onoff == 0 {
+        Ok(None)
+    } else {
+        Ok(Some(Duration::from_secs(val.l_linger as u64)))
+    }
+}
+
 pub(crate) fn set_recv_buffer_size(socket: TcpSocket, size: u32) -> io::Result<()> {
     let size = size.try_into().ok().unwrap_or_else(i32::max_value);
     syscall!(setsockopt(
diff --git a/src/sys/windows/tcp.rs b/src/sys/windows/tcp.rs
index e14f1c8..4baf556 100644
--- a/src/sys/windows/tcp.rs
+++ b/src/sys/windows/tcp.rs
@@ -128,8 +128,6 @@ pub(crate) fn get_localaddr(socket: TcpSocket) -> io::Result<SocketAddr> {
             }
         },
     }
-
-
 }
 
 pub(crate) fn set_linger(socket: TcpSocket, dur: Option<Duration>) -> io::Result<()> {
@@ -150,6 +148,28 @@ pub(crate) fn set_linger(socket: TcpSocket, dur: Option<Duration>) -> io::Result
     }
 }
 
+pub(crate) fn get_linger(socket: TcpSocket) -> io::Result<Option<Duration>> {
+    let mut val: linger = unsafe { std::mem::zeroed() };
+    let mut len = size_of::<linger>() as c_int;
+
+    match unsafe { getsockopt(
+        socket,
+        SOL_SOCKET,
+        SO_LINGER,
+        &mut val as *mut _ as *mut _,
+        &mut len,
+    ) } {
+        SOCKET_ERROR => Err(io::Error::last_os_error()),
+        _ => {
+            if val.l_onoff == 0 {
+                Ok(None)
+            } else {
+                Ok(Some(Duration::from_secs(val.l_linger as u64)))
+            }
+        },
+    }
+}
+
 
 pub(crate) fn set_recv_buffer_size(socket: TcpSocket, size: u32) -> io::Result<()> {
     let size = size.try_into().ok().unwrap_or_else(i32::max_value);


Test patch:
diff --git a/tests/tcp_socket.rs b/tests/tcp_socket.rs
index bb57ade..e5fc537 100644
--- a/tests/tcp_socket.rs
+++ b/tests/tcp_socket.rs
@@ -2,6 +2,7 @@
 
 use mio::net::TcpSocket;
 use std::io;
+use std::time::Duration;
 
 #[test]
 fn is_send_and_sync() {
@@ -58,6 +59,22 @@ fn get_localaddr() {
     let _ = socket.listen(128).unwrap();
 }
 
+#[test]
+fn set_linger() {
+    let addr = "127.0.0.1:0".parse().unwrap();
+
+    let socket = TcpSocket::new_v4().unwrap();
+    socket.set_linger(Some(Duration::from_secs(1))).unwrap();
+    assert_eq!(socket.get_linger().unwrap().unwrap().as_secs(), 1);
+
+    let _ = socket.set_linger(None);
+    assert_eq!(socket.get_linger().unwrap(), None);
+
+    socket.bind(addr).unwrap();
+
+    let _ = socket.listen(128).unwrap();
+}
+
 #[test]
 fn send_buffer_size_roundtrips() {
     test_buffer_sizes(