1mod configure;
2
3use std::{
4 mem::take,
5 sync::{
6 Arc, Mutex,
7 atomic::{AtomicUsize, Ordering},
8 },
9 thread,
10 thread::JoinHandle,
11};
12
13use async_channel::{QueueStrategy, Receiver, RecvError, Sender};
14use futures::{TryFutureExt, channel::oneshot};
15use oneshot::Sender as ResultSender;
16use rocksdb::Direction;
17use tuwunel_core::{
18 Error, Result, Server, debug, err, error, implement,
19 result::DebugInspect,
20 smallvec::SmallVec,
21 trace,
22 utils::sys::compute::{get_affinity, set_affinity},
23};
24
25use self::configure::configure;
26use crate::{Handle, Map, keyval::KeyBuf, stream};
27
28pub(crate) struct Pool {
34 server: Arc<Server>,
35 queues: Vec<Sender<Cmd>>,
36 workers: Mutex<Vec<JoinHandle<()>>>,
37 topology: Vec<usize>,
38 busy: AtomicUsize,
39 queued_max: AtomicUsize,
40}
41
42pub(crate) enum Cmd {
47 Get(Get),
48 Iter(Seek),
49}
50
51pub(crate) struct Get {
56 pub(crate) map: Arc<Map>,
57 pub(crate) key: BatchQuery<'static>,
58 pub(crate) res: Option<ResultSender<BatchResult<'static>>>,
59}
60
61pub(crate) struct Seek {
67 pub(crate) map: Arc<Map>,
68 pub(crate) state: stream::State<'static>,
69 pub(crate) dir: Direction,
70 pub(crate) key: Option<KeyBuf>,
71 pub(crate) res: Option<ResultSender<stream::State<'static>>>,
72}
73
74pub(crate) type BatchQuery<'a> = SmallVec<[KeyBuf; BATCH_INLINE]>;
79
80pub(crate) type BatchResult<'a> = SmallVec<[ResultHandle<'a>; BATCH_INLINE]>;
85
86pub(crate) type ResultHandle<'a> = Result<Handle<'a>>;
91
92const WORKER_LIMIT: (usize, usize) = (1, 4096);
93const QUEUE_LIMIT: (usize, usize) = (1, 1024);
94const BATCH_INLINE: usize = 1;
95
96const WORKER_STACK_SIZE: usize = 1_048_576;
97const WORKER_NAME: &str = "tuwunel:db";
98
99#[implement(Pool)]
105pub(crate) fn new(server: &Arc<Server>) -> Result<Arc<Self>> {
106 const CHAN_SCHED: (QueueStrategy, QueueStrategy) = (QueueStrategy::Fifo, QueueStrategy::Lifo);
107
108 let (topology, workers, queues) = configure(server);
109
110 let (senders, receivers): (Vec<_>, Vec<_>) = queues
111 .into_iter()
112 .map(|cap| cap.max(QUEUE_LIMIT.0))
113 .map(|cap| async_channel::bounded_with_queue_strategy(cap, CHAN_SCHED))
114 .unzip();
115
116 let pool = Arc::new(Self {
117 server: server.clone(),
118 queues: senders,
119 workers: Vec::new().into(),
120 topology,
121 busy: AtomicUsize::default(),
122 queued_max: AtomicUsize::default(),
123 });
124
125 for (chan_id, &count) in workers.iter().enumerate() {
126 pool.spawn_group(&receivers, chan_id, count)?;
127 }
128
129 Ok(pool)
130}
131
132impl Drop for Pool {
133 fn drop(&mut self) {
134 self.close();
135
136 debug_assert!(
137 self.queues.iter().all(Sender::is_empty),
138 "channel must should not have requests queued on drop"
139 );
140 debug_assert!(
141 self.queues.iter().all(Sender::is_closed),
142 "channel should be closed on drop"
143 );
144 }
145}
146
147#[implement(Pool)]
148#[tracing::instrument(skip_all)]
149pub(crate) fn close(&self) {
150 let workers = take(&mut *self.workers.lock().expect("locked"));
151
152 let senders = self
153 .queues
154 .iter()
155 .map(Sender::sender_count)
156 .sum::<usize>();
157
158 let receivers = self
159 .queues
160 .iter()
161 .map(Sender::receiver_count)
162 .sum::<usize>();
163
164 for queue in &self.queues {
165 queue.close();
166 }
167
168 if workers.is_empty() {
169 return;
170 }
171
172 debug!(
173 senders,
174 receivers,
175 queues = self.queues.len(),
176 workers = workers.len(),
177 "Closing pool. Waiting for workers to join..."
178 );
179
180 workers
181 .into_iter()
182 .map(JoinHandle::join)
183 .map(|result| result.map_err(Error::from_panic))
184 .enumerate()
185 .for_each(|(id, result)| match result {
186 | Ok(()) => trace!(?id, "worker joined"),
187 | Err(error) => error!(?id, "worker joined with error: {error}"),
188 });
189}
190
191#[implement(Pool)]
192fn spawn_group(self: &Arc<Self>, recv: &[Receiver<Cmd>], chan_id: usize, count: usize) -> Result {
193 let mut workers = self.workers.lock().expect("locked");
194 for _ in 0..count {
195 self.clone()
196 .spawn_one(&mut workers, recv, chan_id)?;
197 }
198
199 Ok(())
200}
201
202#[implement(Pool)]
203#[tracing::instrument(
204 name = "spawn",
205 level = "trace",
206 skip_all,
207 fields(id = %workers.len())
208)]
209fn spawn_one(
210 self: Arc<Self>,
211 workers: &mut Vec<JoinHandle<()>>,
212 recv: &[Receiver<Cmd>],
213 chan_id: usize,
214) -> Result {
215 debug_assert!(!self.queues.is_empty(), "Must have at least one queue");
216 debug_assert!(!recv.is_empty(), "Must have at least one receiver");
217
218 let id = workers.len();
219 let recv = recv[chan_id].clone();
220
221 let handle = thread::Builder::new()
222 .name(WORKER_NAME.into())
223 .stack_size(WORKER_STACK_SIZE)
224 .spawn(move || self.worker(id, chan_id, &recv))?;
225
226 workers.push(handle);
227
228 Ok(())
229}
230
231#[implement(Pool)]
232#[tracing::instrument(level = "trace", name = "get", skip(self, cmd))]
233pub(crate) async fn execute_get(self: &Arc<Self>, mut cmd: Get) -> Result<BatchResult<'_>> {
234 let (send, recv) = oneshot::channel();
235 _ = cmd.res.insert(send);
236
237 let queue = self.select_queue();
238 self.execute(queue, Cmd::Get(cmd))
239 .and_then(move |()| {
240 recv.map_ok(into_recv_get)
241 .map_err(|e| err!(error!("recv failed {e:?}")))
242 })
243 .await
244}
245
246#[implement(Pool)]
247#[tracing::instrument(level = "trace", name = "iter", skip(self, cmd))]
248pub(crate) async fn execute_iter(self: &Arc<Self>, mut cmd: Seek) -> Result<stream::State<'_>> {
249 let (send, recv) = oneshot::channel();
250 _ = cmd.res.insert(send);
251
252 let queue = self.select_queue();
253 self.execute(queue, Cmd::Iter(cmd))
254 .and_then(|()| {
255 recv.map_ok(into_recv_seek)
256 .map_err(|e| err!(error!("recv failed {e:?}")))
257 })
258 .await
259}
260
261#[implement(Pool)]
270fn select_queue(&self) -> &Sender<Cmd> {
271 let core_id = get_affinity()
272 .next()
273 .expect("Affinity mask should be available.");
274
275 let chan_id = self.topology[core_id];
276
277 self.queues
278 .get(chan_id)
279 .unwrap_or_else(|| &self.queues[0])
280}
281
282#[implement(Pool)]
283#[tracing::instrument(
284 level = "trace",
285 name = "execute",
286 skip(self, cmd),
287 fields(
288 task = ?tokio::task::try_id(),
289 receivers = queue.receiver_count(),
290 queued = queue.len(),
291 queued_max = self.queued_max.load(Ordering::Relaxed),
292 ),
293)]
294async fn execute(&self, queue: &Sender<Cmd>, cmd: Cmd) -> Result {
295 if cfg!(debug_assertions) {
296 self.queued_max
297 .fetch_max(queue.len(), Ordering::Relaxed);
298 }
299
300 queue
301 .send(cmd)
302 .await
303 .map_err(|e| err!(error!("send failed {e:?}")))
304}
305
306#[implement(Pool)]
307#[tracing::instrument(
308 parent = None,
309 level = "debug",
310 skip_all,
311 fields(
312 id,
313 chan_id,
314 thread_id = ?thread::current().id(),
315 ),
316)]
317fn worker(self: Arc<Self>, id: usize, chan_id: usize, recv: &Receiver<Cmd>) {
318 self.worker_init(id, chan_id);
319 self.worker_loop(recv);
320}
321
322#[implement(Pool)]
323fn worker_init(&self, id: usize, chan_id: usize) {
324 let affinity = self
325 .topology
326 .iter()
327 .enumerate()
328 .filter(|_| self.server.config.db_pool_affinity)
329 .filter_map(|(core_id, &queue_id)| (chan_id == queue_id).then_some(core_id));
330
331 set_affinity(affinity.clone());
333
334 trace!(
335 ?id,
336 ?chan_id,
337 affinity = ?affinity.collect::<Vec<_>>(),
338 "worker ready"
339 );
340}
341
342#[implement(Pool)]
343fn worker_loop(self: &Arc<Self>, recv: &Receiver<Cmd>) {
344 self.busy.fetch_add(1, Ordering::Relaxed);
346
347 while let Ok(cmd) = self.worker_wait(recv) {
348 worker_handle(cmd);
349 }
350}
351
352#[implement(Pool)]
353#[tracing::instrument(
354 name = "wait",
355 level = "trace",
356 skip_all,
357 fields(
358 receivers = recv.receiver_count(),
359 queued = recv.len(),
360 busy = self.busy.fetch_sub(1, Ordering::AcqRel) - 1,
361 ),
362)]
363fn worker_wait(self: &Arc<Self>, recv: &Receiver<Cmd>) -> Result<Cmd, RecvError> {
364 recv.recv_blocking().debug_inspect(|_| {
365 self.busy.fetch_add(1, Ordering::Relaxed);
366 })
367}
368
369fn worker_handle(cmd: Cmd) {
370 match cmd {
371 | Cmd::Get(cmd) if cmd.key.len() == 1 => handle_get(cmd),
372 | Cmd::Get(cmd) => handle_batch(cmd),
373 | Cmd::Iter(cmd) => handle_iter(cmd),
374 }
375}
376
377#[tracing::instrument(
378 name = "iter",
379 level = "trace",
380 skip_all,
381 fields(%cmd.map),
382)]
383fn handle_iter(mut cmd: Seek) {
384 let chan = cmd.res.take().expect("missing result channel");
385
386 if chan.is_canceled() {
387 return;
388 }
389
390 let from = cmd.key.as_deref();
391
392 let result = match cmd.dir {
393 | Direction::Forward => cmd.state.init_fwd(from),
394 | Direction::Reverse => cmd.state.init_rev(from),
395 };
396
397 let chan_result = chan.send(into_send_seek(result));
398
399 let _chan_sent = chan_result.is_ok();
400}
401
402#[tracing::instrument(
403 name = "batch",
404 level = "trace",
405 skip_all,
406 fields(
407 %cmd.map,
408 keys = %cmd.key.len(),
409 ),
410)]
411fn handle_batch(mut cmd: Get) {
412 debug_assert!(cmd.key.len() > 1, "should have more than one key");
413 debug_assert!(!cmd.key.iter().any(SmallVec::is_empty), "querying for empty key");
414
415 let chan = cmd.res.take().expect("missing result channel");
416
417 if chan.is_canceled() {
418 return;
419 }
420
421 let keys = cmd.key.iter();
422
423 let result: SmallVec<_> = cmd.map.get_batch_blocking(keys).collect();
424
425 let chan_result = chan.send(into_send_get(result));
426
427 let _chan_sent = chan_result.is_ok();
428}
429
430#[tracing::instrument(
431 name = "get",
432 level = "trace",
433 skip_all,
434 fields(%cmd.map),
435)]
436fn handle_get(mut cmd: Get) {
437 debug_assert!(!cmd.key[0].is_empty(), "querying for empty key");
438
439 let chan = cmd.res.take().expect("missing result channel");
441
442 if chan.is_canceled() {
445 return;
446 }
447
448 let result = cmd.map.get_blocking(&cmd.key[0]);
452
453 let chan_result = chan.send(into_send_get([result].into()));
455
456 let _chan_sent = chan_result.is_ok();
458}
459
460fn into_send_get(result: BatchResult<'_>) -> BatchResult<'static> {
461 unsafe { std::mem::transmute(result) }
466}
467
468fn into_recv_get<'a>(result: BatchResult<'static>) -> BatchResult<'a> {
469 unsafe { std::mem::transmute(result) }
471}
472
473pub(crate) fn into_send_seek(result: stream::State<'_>) -> stream::State<'static> {
474 unsafe { std::mem::transmute(result) }
476}
477
478fn into_recv_seek<'a>(result: stream::State<'static>) -> stream::State<'a> {
479 unsafe { std::mem::transmute(result) }
481}