tcplistener-accept-timeout 0.0.0

TcpListener.accept() with a timeout and a check for a pending connection
Documentation
//! `TcpListener::accept()` with a timeout (`TcpListener::accept_timeout()`), and `TcpListener::connection_pending()`.
//!
//! See [`TcpListenerAcceptTimeout`].


#[cfg(not(target_os = "windows"))]
extern crate libc;
#[cfg(not(target_os = "windows"))]
use libc::{time_t, c_long, POLLIN, timespec, pollfd, ppoll};
#[cfg(not(target_os = "windows"))]
use std::os::fd::AsRawFd;
#[cfg(not(target_os = "windows"))]
use std::ptr;

#[cfg(target_os = "windows")]
extern crate windows_sys;
#[cfg(target_os = "windows")]
use windows_sys::Win32::Networking::WinSock::{POLLIN, WSAPOLLFD as pollfd, WSAPoll as poll};
#[cfg(target_os = "windows")]
use std::os::windows::io::AsRawSocket;

use std::net::{TcpListener, TcpStream, SocketAddr};
use std::io::Result as IoResult;
use std::time::Duration;


/// `TcpListener::accept_timeout()` and `TcpListener::connection_pending()`.
pub trait TcpListenerAcceptTimeout {
    /// Returns either `None` after a time-out, or `accept()`.
    ///
    /// This is not actually a primitive provided for sockets,
    /// so if at least one thread is using `accept_timeout()` simultaneously with any other using `accept()` or `accept_timeout()` then:
    ///   * if non-blocking, `Some(Err(ErrorKind::WouldBlock))` may still be returned
    ///   * if blocking, this may still sleep
    ///
    /// since this thread may wake up because there's a connection pending, then another thread may accept it, and this thread is left in `accept()`.
    fn accept_timeout(&self, timeout: Option<Duration>) -> Option<IoResult<(TcpStream, SocketAddr)>>;

    /// True if `accept()` on this TcpListener will return successfully without blocking.
    ///
    /// A time-out of `None` waits forever until the condition is met (or an error occurs);<br />
    /// a time-out of `Some(Duration::ZERO)` never sleeps.
    ///
    /// Equivalent to `ppoll([{&self, POLLIN}], timeout.unwrap_or(NULL)) == {1, POLLIN}`.
    fn connection_pending(&self, timeout: Option<Duration>) -> bool;
}

impl TcpListenerAcceptTimeout for TcpListener {
    fn accept_timeout(&self, timeout: Option<Duration>) -> Option<IoResult<(TcpStream, SocketAddr)>> {
        if !self.connection_pending(timeout) {
            return None;
        }
        Some(self.accept())
    }

    #[cfg(not(target_os = "windows"))]
    fn connection_pending(&self, timeout: Option<Duration>) -> bool {
        let mut fd = pollfd {
            fd: self.as_raw_fd(),
            events: POLLIN,
            revents: 0,
        };
        unsafe {
            ppoll(&mut fd,
                  1,
                  timeout.map(|timeout| {
                          timespec {
                              tv_sec: timeout.as_secs() as time_t,
                              tv_nsec: timeout.subsec_nanos() as c_long,
                          }
                      })
                      .as_ref()
                      .map(|timeout| timeout as *const _)
                      .unwrap_or(ptr::null()),
                  ptr::null()) == 1 && (fd.revents & POLLIN) != 0
        }
    }

    #[cfg(target_os = "windows")]
    fn connection_pending(&self, timeout: Option<Duration>) -> bool {
        let mut fd = pollfd {
            fd: self.as_raw_socket() as usize,
            events: POLLIN,
            revents: 0,
        };
        unsafe {
            match timeout {
                None => poll(&mut fd, 1, -1) == 1 && (fd.revents & POLLIN) != 0,
                Some(timeout) => {
                    let mut ms = timeout.as_millis();
                    while ms > i32::MAX as u128 {
                        let ret = poll(&mut fd, 1, i32::MAX);
                        if ret != 0 {
                            return ret == 1 && (fd.revents & POLLIN) != 0;
                        }
                        ms -= i32::MAX as u128;
                    }
                    poll(&mut fd, 1, ms as i32) == 1 && (fd.revents & POLLIN) != 0
                }
            }
        }
    }
}