use std::future::Future;
use lazy_static::lazy_static;
lazy_static! {
pub(crate) static ref RUNTIME_TYPE: RuntimeType = RuntimeType::new();
}
pub(crate) enum RuntimeType {
Tokio,
#[cfg(target_os = "linux")]
Uring,
}
impl RuntimeType {
fn new() -> Self {
#[cfg(target_os = "linux")]
{
if Self::probe_io_uring() {
return Self::Uring;
}
}
Self::Tokio
}
#[cfg(target_os = "linux")]
fn probe_io_uring() -> bool {
use io_uring::{opcode, IoUring, Probe};
let io_uring = match IoUring::new(1) {
Ok(io_uring) => io_uring,
Err(_) => {
return false;
}
};
let submitter = io_uring.submitter();
let mut probe = Probe::new();
if let Err(_) = submitter.register_probe(&mut probe) {
return false;
}
if !probe.is_supported(opcode::Fsync::CODE) {
return false;
}
if !probe.is_supported(opcode::Read::CODE) {
return false;
}
if !probe.is_supported(opcode::Write::CODE) {
return false;
}
return true;
}
}
pub enum Runtime {
Tokio(tokio::runtime::Runtime),
#[cfg(target_os = "linux")]
Uring(std::sync::Mutex<tokio_uring::Runtime>),
}
impl Runtime {
pub fn new() -> Self {
#[cfg(target_os = "linux")]
if matches!(*RUNTIME_TYPE, RuntimeType::Uring) {
if let Ok(rt) = tokio_uring::Runtime::new(&tokio_uring::builder()) {
return Runtime::Uring(std::sync::Mutex::new(rt));
}
}
let rt = tokio::runtime::Builder::new_current_thread()
.enable_all()
.build()
.expect("utils: failed to create tokio runtime for current thread");
Runtime::Tokio(rt)
}
pub fn block_on<F: Future>(&self, f: F) -> F::Output {
match self {
Runtime::Tokio(rt) => rt.block_on(f),
#[cfg(target_os = "linux")]
Runtime::Uring(rt) => rt.lock().unwrap().block_on(f),
}
}
pub fn spawn<T: std::future::Future + 'static>(
&self,
task: T,
) -> tokio::task::JoinHandle<T::Output> {
match self {
Runtime::Tokio(_) => tokio::task::spawn_local(task),
#[cfg(target_os = "linux")]
Runtime::Uring(_) => tokio_uring::spawn(task),
}
}
}
pub fn start<F: Future>(future: F) -> F::Output {
Runtime::new().block_on(future)
}
impl Default for Runtime {
fn default() -> Self {
Runtime::new()
}
}
pub fn with_runtime<F, R>(f: F) -> R
where
F: FnOnce(&Runtime) -> R,
{
let rt = Runtime::new();
f(&rt)
}
pub fn block_on<F: Future>(f: F) -> F::Output {
Runtime::new().block_on(f)
}
pub fn spawn<T: std::future::Future + 'static>(task: T) -> tokio::task::JoinHandle<T::Output> {
let rt = Runtime::new();
rt.spawn(task)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_with_runtime() {
let res = with_runtime(|rt| rt.block_on(async { 1 }));
assert_eq!(res, 1);
let res = with_runtime(|rt| rt.block_on(async { 3 }));
assert_eq!(res, 3);
}
#[test]
fn test_block_on() {
let res = block_on(async { 1 });
assert_eq!(res, 1);
let res = block_on(async { 3 });
assert_eq!(res, 3);
}
}