use std::{io, net::SocketAddr, sync::LazyLock, thread};
use compio::{
BufResult,
io::{AsyncRead, AsyncReadExt, AsyncWriteExt},
net::{TcpListener, TcpStream},
runtime::Runtime,
};
use compio_pool::{LocalPool, ManageConnection, Pool, bind_reuseport, cpu};
static UPSTREAM: LazyLock<SocketAddr> = LazyLock::new(|| {
std::env::var("UPSTREAM")
.unwrap_or_else(|_| "127.0.0.1:7001".into())
.parse()
.expect("UPSTREAM must be host:port")
});
struct Upstream {
stream: TcpStream,
healthy: bool,
}
struct Backend;
impl ManageConnection for Backend {
type Connection = Upstream;
type Error = io::Error;
async fn connect(&self) -> io::Result<Upstream> {
let stream = TcpStream::connect(*UPSTREAM).await?;
stream.set_nodelay(true)?;
Ok(Upstream {
stream,
healthy: true,
})
}
async fn is_valid(&self, conn: &mut Upstream) -> io::Result<()> {
if conn.healthy {
Ok(())
} else {
Err(io::Error::new(io::ErrorKind::BrokenPipe, "upstream marked dead"))
}
}
fn has_broken(&self, conn: &mut Upstream) -> bool {
!conn.healthy
}
}
fn main() -> io::Result<()> {
let addr: SocketAddr = std::env::args()
.nth(1)
.unwrap_or_else(|| "0.0.0.0:7000".into())
.parse()
.map_err(|e| io::Error::new(io::ErrorKind::InvalidInput, format!("bad address: {e}")))?;
let capacity: u32 = std::env::args()
.nth(2)
.and_then(|s| s.parse().ok())
.unwrap_or(64);
let pool = Pool::builder().max_size(capacity).build(Backend);
let cores = cpu::cores();
if cores.is_empty() {
return Err(io::Error::new(io::ErrorKind::Unsupported, "no cores to run on"));
}
println!(
"tcp_pool on {addr} -> upstream {}: {} workers, {capacity} upstreams/worker",
*UPSTREAM,
cores.len()
);
let mut handles = Vec::new();
for (index, core) in cores.into_iter().enumerate() {
let pool = pool.clone();
let handle = thread::Builder::new()
.name(format!("worker/{index}"))
.spawn(move || worker(core, addr, pool))?;
handles.push(handle);
}
for handle in handles {
let _ = handle.join();
}
Ok(())
}
fn worker(core: cpu::CoreId, addr: SocketAddr, pool: Pool<Backend>) {
cpu::pin_current(core);
let runtime = Runtime::builder().build().expect("build compio runtime");
runtime.block_on(async move {
let local = pool.local();
let std_listener = bind_reuseport(addr, 1024, None).expect("bind SO_REUSEPORT");
let listener = TcpListener::from_std(std_listener).expect("wrap listener in ring");
loop {
let Ok((stream, _peer)) = listener.accept().await else {
continue;
};
compio::runtime::spawn(serve(local.clone(), stream)).detach();
}
});
}
async fn serve(local: LocalPool<Backend>, mut client: TcpStream) {
let mut buf = Vec::with_capacity(16 * 1024);
loop {
buf.clear();
let BufResult(read, b) = client.read(buf).await;
buf = b;
let n = match read {
Ok(0) | Err(_) => return,
Ok(n) => n,
};
let mut up = match local.get().await {
Ok(up) => up,
Err(_) => return, };
let BufResult(written, b) = up.stream.write_all(buf).await;
buf = b;
if written.is_err() {
up.healthy = false;
return;
}
let BufResult(got, reply) = up.stream.read_exact(Vec::with_capacity(n)).await;
if got.is_err() {
up.healthy = false;
return;
}
drop(up);
let BufResult(written, _) = client.write_all(reply).await;
if written.is_err() {
return;
}
}
}