tokio-rs / tokio

A runtime for writing reliable asynchronous applications with Rust. Provides I/O, networking, scheduling, timers, ...
https://tokio.rs
MIT License
27.14k stars 2.5k forks source link

Handle `pid_shutdown_sockets` when accepting connections #6394

Open dlon opened 8 months ago

dlon commented 8 months ago

Version 1.36.0

Platform Darwin Kernel Version 23.2.0 macOS 14.2.1

Description TcpListener::accept() doesn't fail when the socket is forcibly closed, whereas std::net::TcpListener does.

macOS has a syscall pid_shutdown_sockets which can close sockets for arbitrary processes. I want to be able to handle this situation.

Run this code:

use tokio::net::TcpListener;
use std::{time::Duration, os::fd::AsRawFd, ffi::c_int};

#[tokio::main]
async fn main() {

    //let mut listener = std::net::TcpListener::bind("127.0.0.1:0").unwrap();
    let mut listener = TcpListener::bind("127.0.0.1:0").await.unwrap();
    let fd = listener.as_raw_fd();

    std::thread::spawn(move || {
        std::thread::sleep(Duration::from_secs(5));

        extern "C" {
            fn pid_shutdown_sockets(pid: c_int, level: c_int) -> c_int;
        }
        const SHUTDOWN_SOCKET_LEVEL_DISCONNECT_ALL: i32 = 2;
        unsafe {
            pid_shutdown_sockets(std::process::id() as _, SHUTDOWN_SOCKET_LEVEL_DISCONNECT_ALL);
        }
    });

    loop {
        // this fails, as expected
        //let result = listener.accept();

        // this keeps waiting forever:
        let result = listener.accept().await;

        if result.is_ok() {
            println!("accepted stream!");
        } else {
            eprintln!("error: {result:?}");
            break;
        }
    }
}

Expected: TcpListener::accept should return (fail) when pid_shutdown_sockets shuts down the socket. This is what occurs using std::net::TcpListener. Instead, accept never returns.

Darksonn commented 8 months ago

If it doesn't already work, then there's a good chance that this is not possible.

But I would be happy to hear from someone who knows more about kqueue.

tglane commented 8 months ago

I would agree that this is not possible. Kqueue removes the file descriptor from its set when the file descriptor gets closed. As far as I know there is no event that you can register to Kqueue to notify you when a socket descriptor gets closed. Since a call to pid_shutdown_sockets will close all file descriptors forcibly it will just be removed from the Kqueue set without notifying the tokio::net::TcpListener. See Kqueue man for reference.

The reason why a call to std::net::TcpListener::accept will return an error is that it calls libc::accept and blocks. When the FD gets closed this syscall returns with an error.