file_server/
file_server.rs1use std::{io, net::SocketAddr, path::PathBuf, sync::LazyLock, thread};
27
28use compio::{
29 BufResult,
30 fs::File,
31 io::{AsyncRead, AsyncReadAt, AsyncWriteExt},
32 net::{TcpListener, TcpStream},
33 runtime::Runtime,
34};
35use compio_pool::{LocalPool, ManageConnection, Pool, bind_reuseport, cpu};
36
37static BASE_DIR: LazyLock<PathBuf> = LazyLock::new(|| {
39 PathBuf::from(std::env::var("FILE_DIR").unwrap_or_else(|_| "/tmp/bench-files".into()))
40});
41
42struct Buffers;
44
45impl ManageConnection for Buffers {
46 type Connection = Vec<u8>;
47 type Error = io::Error;
48
49 async fn connect(&self) -> io::Result<Vec<u8>> {
50 Ok(Vec::with_capacity(64 * 1024))
51 }
52
53 async fn is_valid(&self, _buf: &mut Vec<u8>) -> io::Result<()> {
54 Ok(())
55 }
56
57 fn has_broken(&self, _buf: &mut Vec<u8>) -> bool {
58 false
59 }
60}
61
62fn main() -> io::Result<()> {
63 let addr: SocketAddr = std::env::args()
64 .nth(1)
65 .unwrap_or_else(|| "0.0.0.0:7000".into())
66 .parse()
67 .map_err(|e| io::Error::new(io::ErrorKind::InvalidInput, format!("bad address: {e}")))?;
68 let capacity: u32 = std::env::args()
69 .nth(2)
70 .and_then(|s| s.parse().ok())
71 .unwrap_or(1024);
72
73 let pool = Pool::builder().max_size(capacity).build(Buffers);
74
75 let cores = cpu::cores();
76 if cores.is_empty() {
77 return Err(io::Error::new(io::ErrorKind::Unsupported, "no cores to run on"));
78 }
79 println!(
80 "file_server on {addr} serving {}: {} workers, capacity {capacity}/worker",
81 BASE_DIR.display(),
82 cores.len()
83 );
84
85 let mut handles = Vec::new();
86 for (index, core) in cores.into_iter().enumerate() {
87 let pool = pool.clone();
88 let handle = thread::Builder::new()
89 .name(format!("worker/{index}"))
90 .spawn(move || worker(core, addr, pool))?;
91 handles.push(handle);
92 }
93 for handle in handles {
94 let _ = handle.join();
95 }
96 Ok(())
97}
98
99fn worker(core: cpu::CoreId, addr: SocketAddr, pool: Pool<Buffers>) {
100 cpu::pin_current(core);
101
102 let runtime = Runtime::builder().build().expect("build compio runtime");
103 runtime.block_on(async move {
104 let local = pool.local();
105
106 let std_listener = bind_reuseport(addr, 1024, None).expect("bind SO_REUSEPORT");
107 let listener = TcpListener::from_std(std_listener).expect("wrap listener in ring");
108
109 loop {
110 let Ok((stream, _peer)) = listener.accept().await else {
111 continue;
112 };
113 let _ = stream.set_nodelay(true);
117 compio::runtime::spawn(serve(local.clone(), stream)).detach();
118 }
119 });
120}
121
122async fn serve(local: LocalPool<Buffers>, mut stream: TcpStream) {
125 let mut req = Vec::with_capacity(1024);
126 loop {
127 req.clear();
128 let BufResult(read, b) = stream.read(req).await;
129 req = b;
130 match read {
131 Ok(0) | Err(_) => return,
132 Ok(_) => {}
133 }
134
135 let name = match std::str::from_utf8(&req) {
137 Ok(s) => s.trim_end_matches(['\r', '\n']).to_string(),
138 Err(_) => {
139 if reply_err(&mut stream, "utf8").await.is_err() {
140 return;
141 }
142 continue;
143 }
144 };
145 if name.is_empty() || name.contains('/') || name.contains("..") {
146 if reply_err(&mut stream, "bad name").await.is_err() {
147 return;
148 }
149 continue;
150 }
151
152 let path = BASE_DIR.join(&name);
153 let file = match File::open(&path).await {
154 Ok(f) => f,
155 Err(e) => {
156 if reply_err(&mut stream, &e.to_string()).await.is_err() {
157 return;
158 }
159 continue;
160 }
161 };
162 let size = match file.metadata().await {
163 Ok(m) => m.len(),
164 Err(e) => {
165 if reply_err(&mut stream, &e.to_string()).await.is_err() {
166 return;
167 }
168 continue;
169 }
170 };
171
172 let BufResult(w, _) = stream.write_all(format!("{size}\n").into_bytes()).await;
174 if w.is_err() {
175 return;
176 }
177
178 let mut lease = match local.get().await {
179 Ok(lease) => lease,
180 Err(_) => return,
181 };
182 let mut data = std::mem::take(&mut *lease);
183 let mut offset = 0u64;
184 while offset < size {
185 data.clear();
186 let BufResult(r, d) = file.read_at(data, offset).await;
187 data = d;
188 let n = match r {
189 Ok(0) => break,
190 Ok(n) => n,
191 Err(_) => {
192 *lease = data;
193 return;
194 }
195 };
196 offset += n as u64;
197 let BufResult(w, d) = stream.write_all(data).await;
198 data = d;
199 if w.is_err() {
200 *lease = data;
201 return;
202 }
203 }
204 *lease = data; }
206}
207
208async fn reply_err(stream: &mut TcpStream, why: &str) -> io::Result<()> {
210 let BufResult(w, _) = stream.write_all(format!("ERR {why}\n").into_bytes()).await;
211 w
212}