1use std::{
27 io,
28 net::SocketAddr,
29 path::PathBuf,
30 sync::{
31 LazyLock,
32 atomic::{AtomicU64, Ordering},
33 },
34 thread,
35};
36
37use compio::{
38 BufResult,
39 fs::{File, OpenOptions},
40 io::{AsyncRead, AsyncWriteAtExt, AsyncWriteExt},
41 net::{TcpListener, TcpStream},
42 runtime::Runtime,
43};
44use compio_pool::{LocalPool, ManageConnection, Pool, bind_reuseport, cpu};
45
46static WAL_DIR: LazyLock<PathBuf> = LazyLock::new(|| {
48 PathBuf::from(std::env::var("WAL_DIR").unwrap_or_else(|_| "/tmp/bench-wal".into()))
49});
50
51static SYNC: LazyLock<bool> =
54 LazyLock::new(|| std::env::var("WAL_SYNC").map(|v| v != "0").unwrap_or(true));
55
56static NEXT_SEGMENT: AtomicU64 = AtomicU64::new(0);
58
59struct Wal {
61 file: File,
62 offset: u64,
63}
64
65struct WalManager;
69
70impl ManageConnection for WalManager {
71 type Connection = Wal;
72 type Error = io::Error;
73
74 async fn connect(&self) -> io::Result<Wal> {
75 let id = NEXT_SEGMENT.fetch_add(1, Ordering::Relaxed);
76 let path = WAL_DIR.join(format!("seg-{id}.log"));
77 let file = OpenOptions::new()
78 .write(true)
79 .create(true)
80 .truncate(true)
81 .open(&path)
82 .await?;
83 Ok(Wal { file, offset: 0 })
84 }
85
86 async fn is_valid(&self, _wal: &mut Wal) -> io::Result<()> {
87 Ok(())
88 }
89
90 fn has_broken(&self, _wal: &mut Wal) -> bool {
91 false
92 }
93}
94
95fn main() -> io::Result<()> {
96 let addr: SocketAddr = std::env::args()
97 .nth(1)
98 .unwrap_or_else(|| "0.0.0.0:7000".into())
99 .parse()
100 .map_err(|e| io::Error::new(io::ErrorKind::InvalidInput, format!("bad address: {e}")))?;
101 let capacity: u32 = std::env::args()
102 .nth(2)
103 .and_then(|s| s.parse().ok())
104 .unwrap_or(1024);
105
106 std::fs::create_dir_all(&*WAL_DIR)?;
107 let pool = Pool::builder().max_size(capacity).build(WalManager);
108
109 let cores = cpu::cores();
110 if cores.is_empty() {
111 return Err(io::Error::new(io::ErrorKind::Unsupported, "no cores to run on"));
112 }
113 println!(
114 "wal on {addr} -> {} (fdatasync: {}): {} workers, capacity {capacity}/worker",
115 WAL_DIR.display(),
116 if *SYNC { "on" } else { "off" },
117 cores.len()
118 );
119
120 let mut handles = Vec::new();
121 for (index, core) in cores.into_iter().enumerate() {
122 let pool = pool.clone();
123 let handle = thread::Builder::new()
124 .name(format!("worker/{index}"))
125 .spawn(move || worker(core, addr, pool))?;
126 handles.push(handle);
127 }
128 for handle in handles {
129 let _ = handle.join();
130 }
131 Ok(())
132}
133
134fn worker(core: cpu::CoreId, addr: SocketAddr, pool: Pool<WalManager>) {
135 cpu::pin_current(core);
136
137 let runtime = Runtime::builder().build().expect("build compio runtime");
138 runtime.block_on(async move {
139 let local = pool.local();
140
141 let std_listener = bind_reuseport(addr, 1024, None).expect("bind SO_REUSEPORT");
142 let listener = TcpListener::from_std(std_listener).expect("wrap listener in ring");
143
144 loop {
145 let Ok((stream, _peer)) = listener.accept().await else {
146 continue;
147 };
148 compio::runtime::spawn(serve(local.clone(), stream)).detach();
149 }
150 });
151}
152
153async fn serve(local: LocalPool<WalManager>, mut stream: TcpStream) {
156 let mut lease = match local.get().await {
157 Ok(lease) => lease,
158 Err(_) => return,
159 };
160 let mut buf = Vec::with_capacity(64 * 1024);
161 loop {
162 buf.clear();
163 let BufResult(read, b) = stream.read(buf).await;
164 buf = b;
165 let n = match read {
166 Ok(0) | Err(_) => return,
167 Ok(n) => n,
168 };
169
170 let offset = lease.offset;
173 let BufResult(w, b) = lease.file.write_all_at(buf, offset).await;
174 buf = b;
175 if w.is_err() {
176 return;
177 }
178 lease.offset += n as u64;
179
180 if *SYNC && lease.file.sync_data().await.is_err() {
181 return;
182 }
183
184 let BufResult(w, _) = stream.write_all(b"OK\n".to_vec()).await;
185 if w.is_err() {
186 return;
187 }
188 }
189}