use log::debug;
use once_cell::sync::OnceCell;
use rand::Rng;
use tokio::runtime::{Builder, Handle};
use tokio::sync::oneshot::{channel, Sender};
pub(crate) struct OffloadRuntime {
thread_name: &'static str,
shards: usize,
thread_per_shard: usize,
pools: OnceCell<Box<[(Handle, Sender<()>)]>>,
}
impl OffloadRuntime {
#[track_caller]
pub fn new(thread_name: &'static str, shards: usize, thread_per_shard: usize) -> Self {
assert!(shards != 0, "shards must be greater than zero");
assert!(
thread_per_shard != 0,
"thread_per_shard must be greater than zero"
);
OffloadRuntime {
thread_name,
shards,
thread_per_shard,
pools: OnceCell::new(),
}
}
fn init_pools(&self) -> Box<[(Handle, Sender<()>)]> {
let threads = self.shards * self.thread_per_shard;
let mut pools = Vec::with_capacity(threads);
for shard in 0..self.shards {
for thread in 0..self.thread_per_shard {
let rt = Builder::new_current_thread()
.enable_all()
.build()
.expect("failed to build offload runtime");
let handler = rt.handle().clone();
let (tx, rx) = channel::<()>();
let thread_name = format!("{} {shard}.{thread}", self.thread_name);
std::thread::Builder::new()
.name(thread_name.clone())
.spawn(move || {
debug!("{thread_name} started");
rt.block_on(rx)
})
.expect("failed to spawn offload runtime thread");
pools.push((handler, tx));
}
}
pools.into_boxed_slice()
}
pub fn get_runtime(&self, hash: u64) -> &Handle {
let mut rng = rand::thread_rng();
let shard = hash as usize % self.shards;
let thread_in_shard = rng.gen_range(0..self.thread_per_shard);
let pools = self.pools.get_or_init(|| self.init_pools());
&pools[shard * self.thread_per_shard + thread_in_shard].0
}
}