1use std::{io, net::SocketAddr, sync::LazyLock, thread};
29
30use compio::{
31 BufResult,
32 io::{AsyncRead, AsyncReadExt, AsyncWriteExt},
33 net::{TcpListener, TcpStream},
34 runtime::Runtime,
35};
36use compio_pool::{LocalPool, ManageConnection, Pool, bind_reuseport, cpu};
37
38static UPSTREAM: LazyLock<SocketAddr> = LazyLock::new(|| {
40 std::env::var("UPSTREAM")
41 .unwrap_or_else(|_| "127.0.0.1:7001".into())
42 .parse()
43 .expect("UPSTREAM must be host:port")
44});
45
46struct Upstream {
49 stream: TcpStream,
50 healthy: bool,
51}
52
53struct Backend;
57
58impl ManageConnection for Backend {
59 type Connection = Upstream;
60 type Error = io::Error;
61
62 async fn connect(&self) -> io::Result<Upstream> {
63 let stream = TcpStream::connect(*UPSTREAM).await?;
64 stream.set_nodelay(true)?;
65 Ok(Upstream {
66 stream,
67 healthy: true,
68 })
69 }
70
71 async fn is_valid(&self, conn: &mut Upstream) -> io::Result<()> {
76 if conn.healthy {
77 Ok(())
78 } else {
79 Err(io::Error::new(io::ErrorKind::BrokenPipe, "upstream marked dead"))
80 }
81 }
82
83 fn has_broken(&self, conn: &mut Upstream) -> bool {
84 !conn.healthy
85 }
86}
87
88fn main() -> io::Result<()> {
89 let addr: SocketAddr = std::env::args()
90 .nth(1)
91 .unwrap_or_else(|| "0.0.0.0:7000".into())
92 .parse()
93 .map_err(|e| io::Error::new(io::ErrorKind::InvalidInput, format!("bad address: {e}")))?;
94 let capacity: u32 = std::env::args()
95 .nth(2)
96 .and_then(|s| s.parse().ok())
97 .unwrap_or(64);
98
99 let pool = Pool::builder().max_size(capacity).build(Backend);
101
102 let cores = cpu::cores();
103 if cores.is_empty() {
104 return Err(io::Error::new(io::ErrorKind::Unsupported, "no cores to run on"));
105 }
106 println!(
107 "tcp_pool on {addr} -> upstream {}: {} workers, {capacity} upstreams/worker",
108 *UPSTREAM,
109 cores.len()
110 );
111
112 let mut handles = Vec::new();
113 for (index, core) in cores.into_iter().enumerate() {
114 let pool = pool.clone();
115 let handle = thread::Builder::new()
116 .name(format!("worker/{index}"))
117 .spawn(move || worker(core, addr, pool))?;
118 handles.push(handle);
119 }
120 for handle in handles {
121 let _ = handle.join();
122 }
123 Ok(())
124}
125
126fn worker(core: cpu::CoreId, addr: SocketAddr, pool: Pool<Backend>) {
129 cpu::pin_current(core);
130
131 let runtime = Runtime::builder().build().expect("build compio runtime");
132 runtime.block_on(async move {
133 let local = pool.local();
134
135 let std_listener = bind_reuseport(addr, 1024, None).expect("bind SO_REUSEPORT");
136 let listener = TcpListener::from_std(std_listener).expect("wrap listener in ring");
137
138 loop {
139 let Ok((stream, _peer)) = listener.accept().await else {
140 continue;
141 };
142 compio::runtime::spawn(serve(local.clone(), stream)).detach();
143 }
144 });
145}
146
147async fn serve(local: LocalPool<Backend>, mut client: TcpStream) {
150 let mut buf = Vec::with_capacity(16 * 1024);
151 loop {
152 buf.clear();
153 let BufResult(read, b) = client.read(buf).await;
154 buf = b;
155 let n = match read {
156 Ok(0) | Err(_) => return,
157 Ok(n) => n,
158 };
159
160 let mut up = match local.get().await {
162 Ok(up) => up,
163 Err(_) => return, };
165
166 let BufResult(written, b) = up.stream.write_all(buf).await;
168 buf = b;
169 if written.is_err() {
170 up.healthy = false;
171 return;
172 }
173
174 let BufResult(got, reply) = up.stream.read_exact(Vec::with_capacity(n)).await;
176 if got.is_err() {
177 up.healthy = false;
178 return;
179 }
180 drop(up); let BufResult(written, _) = client.write_all(reply).await;
183 if written.is_err() {
184 return;
185 }
186 }
187}