use std::{
io,
mem::ManuallyDrop,
net::SocketAddr,
sync::{
Arc,
atomic::{AtomicU64, Ordering::Relaxed},
},
time::Duration,
};
use compio::{
buf::BufResult,
io::{AsyncRead, AsyncWriteExt},
net::{TcpListener, TcpStream},
};
use compio_pool::{Config, Manage, Pool, SlotMeta};
#[cfg(unix)]
fn with_std<T>(conn: &TcpStream, f: impl FnOnce(&std::net::TcpStream) -> T) -> T {
use std::os::fd::{AsFd, AsRawFd, FromRawFd};
let raw = conn.as_fd().as_raw_fd();
let borrowed = ManuallyDrop::new(unsafe { std::net::TcpStream::from_raw_fd(raw) });
f(&borrowed)
}
#[cfg(windows)]
fn with_std<T>(conn: &TcpStream, f: impl FnOnce(&std::net::TcpStream) -> T) -> T {
use std::os::windows::io::{AsRawSocket, FromRawSocket};
let raw = conn.as_raw_socket();
let borrowed = ManuallyDrop::new(unsafe { std::net::TcpStream::from_raw_socket(raw) });
f(&borrowed)
}
struct StdTcpManager {
addr: SocketAddr,
connect_timeout: Duration,
dials: Arc<AtomicU64>,
}
impl Manage for StdTcpManager {
type Connection = TcpStream;
type Error = io::Error;
async fn connect(&self) -> io::Result<TcpStream> {
let addr = self.addr;
let connect_timeout = self.connect_timeout;
self.dials.fetch_add(1, Relaxed);
let std_stream = compio::runtime::spawn_blocking(move || {
let stream = std::net::TcpStream::connect_timeout(&addr, connect_timeout)?;
stream.set_nodelay(true)?;
io::Result::Ok(stream)
})
.await
.map_err(|_| io::Error::other("dial task panicked"))??;
TcpStream::from_std(std_stream)
}
async fn recycle(&self, conn: &mut TcpStream, meta: &SlotMeta) -> io::Result<()> {
if let Some(err) = with_std(conn, |std_conn| std_conn.take_error())
.ok()
.flatten()
{
return Err(err);
}
if meta.idle_for() > Duration::from_secs(30) {
conn.peer_addr()?;
}
Ok(())
}
}
fn spawn_echo_server() -> SocketAddr {
let (tx, rx) = std::sync::mpsc::channel();
std::thread::spawn(move || {
compio::runtime::Runtime::new()
.unwrap()
.block_on(async move {
let listener = TcpListener::bind("127.0.0.1:0").await.unwrap();
tx.send(listener.local_addr().unwrap()).unwrap();
loop {
let Ok((mut stream, _)) = listener.accept().await else {
return;
};
compio::runtime::spawn(async move {
loop {
let BufResult(read, buf) = stream.read(vec![0u8; 64]).await;
match read {
Ok(0) | Err(_) => return,
Ok(n) => {
let BufResult(write, _) =
stream.write_all(buf[..n].to_vec()).await;
if write.is_err() {
return;
}
}
}
}
})
.detach();
}
})
});
rx.recv().unwrap()
}
fn main() -> io::Result<()> {
let addr = spawn_echo_server();
let dials = Arc::new(AtomicU64::new(0));
let pool = Pool::new(
StdTcpManager {
addr,
connect_timeout: Duration::from_secs(5),
dials: dials.clone(),
},
Config::new()
.max_size(2) .min_idle(1)
.acquire_timeout(Duration::from_secs(5)),
);
let workers: Vec<_> = (0..4)
.map(|worker| {
let pool = pool.clone();
std::thread::spawn(move || {
compio::runtime::Runtime::new()
.unwrap()
.block_on(async move {
pool.warm().await.expect("warm");
for round in 0..5u8 {
let mut conn = pool.acquire().await.expect("acquire");
let op = conn.begin_op();
let msg = format!("worker {worker} round {round}");
let BufResult(written, msg) = conn.write_all(msg.into_bytes()).await;
written.expect("write");
let BufResult(read, buf) = conn.read(vec![0u8; msg.len()]).await;
let n = read.expect("read");
op.complete_op();
assert_eq!(&buf[..n], &msg[..], "echo mismatch");
}
let conn = pool.acquire().await.expect("acquire");
let nodelay = with_std(&conn, |s| s.nodelay()).expect("nodelay");
assert!(nodelay, "set_nodelay should have survived adoption");
pool.local_size()
})
})
})
.collect();
let held: Vec<usize> = workers.into_iter().map(|w| w.join().unwrap()).collect();
println!("connections held per worker at exit: {held:?}");
println!("total dials: {}", dials.load(Relaxed));
println!("metrics: {:#?}", pool.metrics());
assert_eq!(
dials.load(Relaxed),
4,
"each thread should dial exactly once"
);
println!("\nOK: 4 std sockets adopted, 24 operations across 4 compio threads.");
Ok(())
}