use std::cell::Cell;
use std::ffi::{CString, NulError};
use std::future::Future;
use std::mem::{self, MaybeUninit};
use std::net::{SocketAddr, SocketAddrV4, SocketAddrV6};
use std::os::unix::io::RawFd;
use std::os::unix::prelude::IntoRawFd;
use std::pin::Pin;
use std::rc::Rc;
use std::task::{Context, Poll};
use std::{io, ptr};
#[cfg(feature = "async-std")]
use async_std::io::{Read as AsyncRead, Write as AsyncWrite};
#[cfg(not(feature = "async-std"))]
use futures::{AsyncRead, AsyncWrite};
use crate::ffi::tarantool as ffi;
use crate::fiber::r#async::context::ContextExt;
use crate::fiber::{self, r#async};
#[derive(thiserror::Error, Debug)]
pub enum Error {
#[error("failed to resolve domain name '{0}'")]
ResolveAddress(String),
#[error("input parameters contain ffi incompatible strings: {0}")]
ConstructCString(NulError),
#[error("failed to connect to address '{address}': {error}")]
Connect { error: io::Error, address: String },
#[error("failed to set socket to nonblocking mode: {0}")]
SetNonBlock(io::Error),
#[error("unknown address family: {0}")]
UnknownAddressFamily(u16),
#[error("write half of the stream is closed")]
WriteClosed,
}
#[derive(Debug, Clone)]
pub struct TcpStream {
fd: Rc<Cell<Option<RawFd>>>,
}
impl TcpStream {
pub async fn connect(url: &str, port: u16) -> Result<TcpStream, Error> {
let addrs = unsafe {
let addr_info = get_address_info(url).await?;
let addrs = get_addrs_from_info(addr_info, port);
libc::freeaddrinfo(addr_info);
ipv4_first(addrs?)
};
let stream = crate::unwrap_ok_or!(std::net::TcpStream::connect(addrs.as_slice()),
Err(e) => {
return Err(Error::Connect { error: e, address: format!("{url}:{port}") });
}
);
stream.set_nonblocking(true).map_err(Error::SetNonBlock)?;
let fd = stream.into_raw_fd();
Ok(Self {
fd: Rc::new(Cell::new(Some(fd))),
})
}
#[inline(always)]
#[track_caller]
pub fn close(&mut self) -> io::Result<()> {
let Some(fd) = self.fd.take() else {
return Ok(());
};
let rc = unsafe { ffi::coio_close(fd) };
if rc != 0 {
let e = io::Error::last_os_error();
if e.raw_os_error() == Some(libc::EBADF) {
crate::log::say(
crate::log::SayLevel::Error,
file!(),
line!() as _,
None,
&format!("close({fd}): Bad file descriptor"),
);
if cfg!(debug_assertions) {
panic!("close({}): Bad file descriptor", fd);
}
}
return Err(e);
}
Ok(())
}
}
fn ipv4_first(addrs: impl IntoIterator<Item = SocketAddr>) -> Vec<SocketAddr> {
let (mut ipv4, mut ipv6): (Vec<_>, Vec<_>) = addrs.into_iter().partition(|addr| addr.is_ipv4());
ipv4.append(&mut ipv6);
ipv4
}
unsafe fn get_addrs_from_info(
addrs: *const libc::addrinfo,
port: u16,
) -> Result<Vec<SocketAddr>, Error> {
let mut addr = addrs;
let mut out_addrs = Vec::new();
while !addr.is_null() {
out_addrs.push(to_rs_sockaddr((*addr).ai_addr, port)?);
addr = (*addr).ai_next;
}
Ok(out_addrs)
}
async unsafe fn get_address_info(url: &str) -> Result<*mut libc::addrinfo, Error> {
struct GetAddrInfo(r#async::coio::GetAddrInfo);
impl Future for GetAddrInfo {
type Output = Result<*mut libc::addrinfo, ()>;
fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
unsafe {
if self.0.err.get() {
return Poll::Ready(Err(()));
}
if self.0.res.get().is_null() {
ContextExt::set_coio_getaddrinfo(cx, self.0.clone());
Poll::Pending
} else {
Poll::Ready(Ok(self.0.res.get()))
}
}
}
}
let host = CString::new(url).map_err(Error::ConstructCString)?;
let mut hints = MaybeUninit::<libc::addrinfo>::zeroed().assume_init();
hints.ai_family = libc::AF_UNSPEC;
hints.ai_socktype = libc::SOCK_STREAM;
GetAddrInfo(r#async::coio::GetAddrInfo {
host,
hints,
res: Rc::new(Cell::new(ptr::null_mut())),
err: Rc::new(Cell::new(false)),
})
.await
.map_err(|()| Error::ResolveAddress(url.into()))
}
unsafe fn to_rs_sockaddr(addr: *const libc::sockaddr, port: u16) -> Result<SocketAddr, Error> {
match (*addr).sa_family as libc::c_int {
libc::AF_INET => {
let addr: *mut libc::sockaddr_in = mem::transmute(addr);
(*addr).sin_port = port;
let octets: [u8; 4] = (*addr).sin_addr.s_addr.to_ne_bytes();
Ok(SocketAddr::V4(SocketAddrV4::new(octets.into(), port)))
}
libc::AF_INET6 => {
let addr: *mut libc::sockaddr_in6 = mem::transmute(addr);
(*addr).sin6_port = port;
let octets = (*addr).sin6_addr.s6_addr;
let flow_info = (*addr).sin6_flowinfo;
let scope_id = (*addr).sin6_scope_id;
Ok(SocketAddr::V6(SocketAddrV6::new(
octets.into(),
port,
flow_info,
scope_id,
)))
}
af => Err(Error::UnknownAddressFamily(af as u16)),
}
}
impl AsyncWrite for TcpStream {
fn poll_write(
self: Pin<&mut Self>,
cx: &mut Context<'_>,
buf: &[u8],
) -> Poll<io::Result<usize>> {
let Some(fd) = self.fd.get() else {
let e = io::Error::new(io::ErrorKind::Other, "socket closed already");
return Poll::Ready(Err(e));
};
let (result, err) = (
unsafe { libc::write(fd, buf.as_ptr() as *const libc::c_void, buf.len()) },
io::Error::last_os_error(),
);
if result >= 0 {
return Poll::Ready(Ok(result as usize));
}
match err.kind() {
io::ErrorKind::WouldBlock => {
unsafe { ContextExt::set_coio_wait(cx, fd, ffi::CoIOFlags::WRITE) }
Poll::Pending
}
io::ErrorKind::Interrupted => {
unsafe { ContextExt::set_deadline(cx, fiber::clock()) }
Poll::Pending
}
_ => Poll::Ready(Err(err)),
}
}
fn poll_flush(self: Pin<&mut Self>, _cx: &mut Context<'_>) -> Poll<io::Result<()>> {
if self.fd.get().is_none() {
let e = io::Error::new(io::ErrorKind::Other, "socket closed already");
return Poll::Ready(Err(e));
};
Poll::Ready(Ok(()))
}
fn poll_close(mut self: Pin<&mut Self>, _cx: &mut Context<'_>) -> Poll<io::Result<()>> {
if self.fd.get().is_none() {
let e = io::Error::new(io::ErrorKind::Other, "socket closed already");
return Poll::Ready(Err(e));
};
let res = self.close();
Poll::Ready(res)
}
}
impl AsyncRead for TcpStream {
fn poll_read(
self: Pin<&mut Self>,
cx: &mut Context<'_>,
buf: &mut [u8],
) -> Poll<io::Result<usize>> {
let Some(fd) = self.fd.get() else {
let e = io::Error::new(io::ErrorKind::Other, "socket closed already");
return Poll::Ready(Err(e));
};
let (result, err) = (
unsafe { libc::read(fd, buf.as_mut_ptr() as *mut libc::c_void, buf.len()) },
io::Error::last_os_error(),
);
if result >= 0 {
return Poll::Ready(Ok(result as usize));
}
match err.kind() {
io::ErrorKind::WouldBlock => {
unsafe { ContextExt::set_coio_wait(cx, fd, ffi::CoIOFlags::READ) }
Poll::Pending
}
io::ErrorKind::Interrupted => {
unsafe { ContextExt::set_deadline(cx, fiber::clock()) }
Poll::Pending
}
_ => Poll::Ready(Err(err)),
}
}
}
impl Drop for TcpStream {
fn drop(&mut self) {
if let Err(e) = self.close() {
crate::log::say(
crate::log::SayLevel::Error,
file!(),
line!() as _,
None,
&format!("TcpStream::drop: closing tcp stream failed: {e}"),
);
}
}
}
#[derive(Debug, Clone)]
#[repr(transparent)]
pub struct UnsafeSendSyncTcpStream(pub TcpStream);
unsafe impl Send for UnsafeSendSyncTcpStream {}
unsafe impl Sync for UnsafeSendSyncTcpStream {}
impl AsyncRead for UnsafeSendSyncTcpStream {
#[inline(always)]
fn poll_read(
mut self: Pin<&mut Self>,
cx: &mut Context<'_>,
buf: &mut [u8],
) -> Poll<io::Result<usize>> {
AsyncRead::poll_read(Pin::new(&mut self.0), cx, buf)
}
}
impl AsyncWrite for UnsafeSendSyncTcpStream {
#[inline(always)]
fn poll_write(
mut self: Pin<&mut Self>,
cx: &mut Context<'_>,
buf: &[u8],
) -> Poll<io::Result<usize>> {
AsyncWrite::poll_write(Pin::new(&mut self.0), cx, buf)
}
#[inline(always)]
fn poll_flush(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<io::Result<()>> {
AsyncWrite::poll_flush(Pin::new(&mut self.0), cx)
}
#[inline(always)]
fn poll_close(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<io::Result<()>> {
AsyncWrite::poll_close(Pin::new(&mut self.0), cx)
}
}
#[cfg(feature = "internal_test")]
mod tests {
use super::*;
use crate::fiber;
use crate::fiber::r#async::timeout::{self, IntoTimeout};
use crate::test::util::always_pending;
use crate::test::util::listen_port;
use std::collections::HashSet;
use std::net::{TcpListener, ToSocketAddrs};
use std::thread;
use std::time::Duration;
use futures::{AsyncReadExt, AsyncWriteExt, FutureExt};
const _10_SEC: Duration = Duration::from_secs(10);
const _0_SEC: Duration = Duration::from_secs(0);
#[crate::test(tarantool = "crate")]
fn resolve_address() {
unsafe {
let _ = fiber::block_on(get_address_info("localhost").timeout(_10_SEC)).unwrap();
}
}
#[crate::test(tarantool = "crate")]
async fn resolve_same_as_std() {
let addrs_1: HashSet<_> = unsafe {
get_addrs_from_info(
get_address_info("example.org")
.timeout(_10_SEC)
.await
.unwrap(),
80,
)
.unwrap()
.into_iter()
.collect()
};
let addrs_2: HashSet<_> = ToSocketAddrs::to_socket_addrs("example.org:80")
.unwrap()
.collect();
assert_eq!(addrs_1, addrs_2);
}
#[crate::test(tarantool = "crate")]
fn resolve_address_error() {
unsafe {
let err = fiber::block_on(get_address_info("invalid domain name").timeout(_10_SEC))
.unwrap_err()
.to_string();
assert_eq!(err, "failed to resolve domain name 'invalid domain name'")
}
}
#[crate::test(tarantool = "crate")]
fn connect() {
let _ = fiber::block_on(TcpStream::connect("localhost", listen_port()).timeout(_10_SEC))
.unwrap();
}
#[crate::test(tarantool = "crate")]
async fn read() {
let mut stream = TcpStream::connect("localhost", listen_port())
.timeout(_10_SEC)
.await
.unwrap();
let mut buf = vec![0; 128];
stream.read_exact(&mut buf).timeout(_10_SEC).await.unwrap();
}
#[crate::test(tarantool = "crate")]
async fn read_timeout() {
let mut stream = TcpStream::connect("localhost", listen_port())
.timeout(_10_SEC)
.await
.unwrap();
let mut buf = vec![0; 128];
assert_eq!(
stream
.read_exact(&mut buf)
.timeout(_0_SEC)
.await
.unwrap_err()
.to_string(),
"deadline expired"
);
}
#[crate::test(tarantool = "crate")]
fn write() {
let (sender, receiver) = std::sync::mpsc::channel();
let listener = TcpListener::bind("127.0.0.1:3302").unwrap();
thread::spawn(move || {
for stream in listener.incoming() {
let mut stream = stream.unwrap();
let mut buf = vec![];
<std::net::TcpStream as std::io::Read>::read_to_end(&mut stream, &mut buf).unwrap();
sender.send(buf).unwrap();
}
});
{
fiber::block_on(async {
let mut stream = TcpStream::connect("localhost", 3302)
.timeout(_10_SEC)
.await
.unwrap();
timeout::timeout(_10_SEC, stream.write_all(&[1, 2, 3]))
.await
.unwrap();
timeout::timeout(_10_SEC, stream.write_all(&[4, 5]))
.await
.unwrap();
});
}
let buf = receiver.recv_timeout(Duration::from_secs(5)).unwrap();
assert_eq!(buf, vec![1, 2, 3, 4, 5])
}
#[crate::test(tarantool = "crate")]
fn split() {
let (sender, receiver) = std::sync::mpsc::channel();
let listener = TcpListener::bind("127.0.0.1:3303").unwrap();
thread::spawn(move || {
for stream in listener.incoming() {
let mut stream = stream.unwrap();
let mut buf = vec![0; 5];
<std::net::TcpStream as std::io::Read>::read_exact(&mut stream, &mut buf).unwrap();
<std::net::TcpStream as std::io::Write>::write_all(&mut stream, &buf.clone())
.unwrap();
sender.send(buf).unwrap();
}
});
{
let stream =
fiber::block_on(TcpStream::connect("localhost", 3303).timeout(_10_SEC)).unwrap();
let (mut reader, mut writer) = stream.split();
let reader_handle = fiber::start_async(async move {
let mut buf = vec![0; 5];
timeout::timeout(_10_SEC, reader.read_exact(&mut buf))
.await
.unwrap();
assert_eq!(buf, vec![1, 2, 3, 4, 5])
});
let writer_handle = fiber::start_async(async move {
timeout::timeout(_10_SEC, writer.write_all(&[1, 2, 3]))
.await
.unwrap();
timeout::timeout(_10_SEC, writer.write_all(&[4, 5]))
.await
.unwrap();
});
writer_handle.join();
reader_handle.join();
}
let buf = receiver.recv_timeout(Duration::from_secs(5)).unwrap();
assert_eq!(buf, vec![1, 2, 3, 4, 5])
}
#[crate::test(tarantool = "crate")]
fn join_correct_timeout() {
{
fiber::block_on(async {
let mut stream = TcpStream::connect("localhost", listen_port())
.timeout(_10_SEC)
.await
.unwrap();
let mut buf = vec![0; 128];
let (is_err, is_ok) = futures::join!(
timeout::timeout(_0_SEC, always_pending()),
timeout::timeout(_10_SEC, stream.read_exact(&mut buf))
);
assert_eq!(is_err.unwrap_err().to_string(), "deadline expired");
is_ok.unwrap();
});
}
{
fiber::block_on(async {
let mut stream = TcpStream::connect("localhost", listen_port())
.timeout(_10_SEC)
.await
.unwrap();
let mut buf = vec![0; 128];
let (is_ok, is_err) = futures::join!(
timeout::timeout(_10_SEC, stream.read_exact(&mut buf)),
timeout::timeout(_0_SEC, always_pending())
);
assert_eq!(is_err.unwrap_err().to_string(), "deadline expired");
is_ok.unwrap();
});
}
}
#[crate::test(tarantool = "crate")]
fn select_correct_timeout() {
{
fiber::block_on(async {
let mut stream = TcpStream::connect("localhost", listen_port())
.timeout(_10_SEC)
.await
.unwrap();
let mut buf = vec![0; 128];
let f1 = timeout::timeout(_0_SEC, always_pending()).fuse();
let f2 = timeout::timeout(_10_SEC, stream.read_exact(&mut buf)).fuse();
futures::pin_mut!(f1);
futures::pin_mut!(f2);
let is_err = futures::select!(
res = f1 => res.is_err(),
res = f2 => res.is_err()
);
assert!(is_err);
});
}
{
fiber::block_on(async {
let mut stream = TcpStream::connect("localhost", listen_port())
.timeout(_10_SEC)
.await
.unwrap();
let mut buf = vec![0; 128];
let f1 = timeout::timeout(Duration::from_secs(15), always_pending()).fuse();
let f2 = timeout::timeout(_10_SEC, stream.read_exact(&mut buf)).fuse();
futures::pin_mut!(f1);
futures::pin_mut!(f2);
let is_ok = futures::select!(
res = f1 => res.is_ok(),
res = f2 => res.is_ok()
);
assert!(is_ok);
});
}
}
#[crate::test(tarantool = "crate")]
async fn no_socket_double_close() {
let mut stream = TcpStream::connect("localhost", listen_port())
.timeout(_10_SEC)
.await
.unwrap();
let fd = stream.fd.get().unwrap();
assert_ne!(unsafe { dbg!(libc::fcntl(fd, libc::F_GETFD)) }, -1);
stream.close().unwrap();
assert_eq!(unsafe { dbg!(libc::fcntl(fd, libc::F_GETFD)) }, -1);
assert_ne!(unsafe { libc::dup2(libc::STDOUT_FILENO, fd) }, -1);
assert_ne!(unsafe { dbg!(libc::fcntl(fd, libc::F_GETFD)) }, -1);
drop(stream);
assert_ne!(unsafe { dbg!(libc::fcntl(fd, libc::F_GETFD)) }, -1);
unsafe { libc::close(fd) };
}
}