use std::sync::Arc;
use crate::driver::Driver;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum DriverType {
Epoll,
IOUring,
Kqueue,
Auto,
}
#[derive(Debug, Clone, Copy)]
pub struct DriverConfig {
pub entries: u32,
pub submit_wait: bool,
pub cpu_affinity: Option<usize>,
pub defer_wakeup: bool,
pub max_ops_per_fd: u32,
}
impl Default for DriverConfig {
fn default() -> Self {
Self {
entries: 256,
submit_wait: false,
cpu_affinity: None,
defer_wakeup: true,
max_ops_per_fd: 32,
}
}
}
#[derive(Debug, Clone)]
pub struct DriverConfigBuilder {
config: DriverConfig,
}
impl DriverConfigBuilder {
#[must_use]
pub fn new() -> Self {
Self {
config: DriverConfig::default(),
}
}
#[must_use]
pub fn entries(mut self, entries: u32) -> Self {
self.config.entries = entries.next_power_of_two();
self
}
#[must_use]
pub const fn submit_wait(mut self, wait: bool) -> Self {
self.config.submit_wait = wait;
self
}
#[must_use]
pub const fn cpu_affinity(mut self, core: usize) -> Self {
self.config.cpu_affinity = Some(core);
self
}
#[must_use]
pub const fn no_affinity(mut self) -> Self {
self.config.cpu_affinity = None;
self
}
#[must_use]
pub const fn defer_wakeup(mut self, defer: bool) -> Self {
self.config.defer_wakeup = defer;
self
}
#[must_use]
pub const fn max_ops_per_fd(mut self, max: u32) -> Self {
self.config.max_ops_per_fd = max;
self
}
#[must_use]
pub const fn build(self) -> DriverConfig {
self.config
}
}
impl Default for DriverConfigBuilder {
fn default() -> Self {
Self::new()
}
}
pub struct DriverFactory;
impl DriverFactory {
pub fn create(driver_type: DriverType) -> std::io::Result<Arc<dyn Driver>> {
Self::create_with_config(driver_type, DriverConfig::default())
}
pub fn create_with_config(
driver_type: DriverType,
config: DriverConfig,
) -> std::io::Result<Arc<dyn Driver>> {
let ty = if matches!(driver_type, DriverType::Auto) {
Self::detect_best_driver()?
} else {
driver_type
};
match ty {
#[cfg(target_os = "linux")]
DriverType::Epoll => {
Ok(Arc::new(crate::driver::epoll::EpollDriver::with_config(config)?))
},
#[cfg(target_os = "linux")]
DriverType::IOUring => {
Ok(Arc::new(crate::driver::iouring::IoUringDriver::with_config(config)?))
},
#[cfg(any(
target_os = "macos",
target_os = "freebsd",
target_os = "netbsd",
target_os = "openbsd",
target_os = "dragonfly"
))]
DriverType::Kqueue => {
Ok(Arc::new(crate::driver::kqueue::KqueueDriver::with_config(config)?))
},
#[cfg(not(target_os = "linux"))]
DriverType::Epoll => Err(std::io::Error::new(
std::io::ErrorKind::Unsupported,
"epoll driver is only available on Linux",
)),
#[cfg(not(target_os = "linux"))]
DriverType::IOUring => Err(std::io::Error::new(
std::io::ErrorKind::Unsupported,
"io-uring driver is only available on Linux",
)),
#[cfg(not(any(
target_os = "macos",
target_os = "freebsd",
target_os = "netbsd",
target_os = "openbsd",
target_os = "dragonfly"
)))]
DriverType::Kqueue => Err(std::io::Error::new(
std::io::ErrorKind::Unsupported,
"kqueue driver is only available on macOS/BSD",
)),
DriverType::Auto => {
Err(std::io::Error::new(
std::io::ErrorKind::Unsupported,
"Failed to detect a suitable driver for this platform",
))
},
}
}
fn detect_best_driver() -> std::io::Result<DriverType> {
#[cfg(target_os = "linux")]
{
if Self::has_io_uring_support() {
Ok(DriverType::IOUring)
} else {
Ok(DriverType::Epoll)
}
}
#[cfg(any(
target_os = "macos",
target_os = "freebsd",
target_os = "netbsd",
target_os = "openbsd",
target_os = "dragonfly"
))]
{
Ok(DriverType::Kqueue)
}
#[cfg(not(any(
target_os = "linux",
target_os = "macos",
target_os = "freebsd",
target_os = "netbsd",
target_os = "openbsd",
target_os = "dragonfly"
)))]
{
Err(std::io::Error::new(
std::io::ErrorKind::Unsupported,
"No suitable driver found for this platform",
))
}
}
#[cfg(target_os = "linux")]
fn has_io_uring_support() -> bool {
let mut uname = libc::utsname {
sysname: [0; 65],
nodename: [0; 65],
release: [0; 65],
version: [0; 65],
machine: [0; 65],
domainname: [0; 65],
};
unsafe {
if libc::uname(&mut uname) != 0 {
return false;
}
let release = std::ffi::CStr::from_ptr(uname.release.as_ptr()).to_string_lossy();
if let Some((major, rest)) = release.split_once('.') {
if let Some((minor, _)) = rest.split_once('.') {
if let (Ok(maj), Ok(min)) = (major.parse::<u32>(), minor.parse::<u32>()) {
return maj > 5 || (maj == 5 && min >= 1);
}
}
}
}
false
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_config_builder() {
let config = DriverConfigBuilder::new()
.entries(512)
.submit_wait(true)
.cpu_affinity(0)
.defer_wakeup(false)
.build();
assert_eq!(config.entries, 512);
assert!(config.submit_wait);
assert_eq!(config.cpu_affinity, Some(0));
assert!(!config.defer_wakeup);
}
#[test]
fn test_config_rounding() {
let config = DriverConfigBuilder::new().entries(100).build();
assert_eq!(config.entries, 128);
}
#[test]
fn test_config_default() {
let config = DriverConfig::default();
assert_eq!(config.entries, 256);
assert!(!config.submit_wait);
assert_eq!(config.cpu_affinity, None);
assert!(config.defer_wakeup);
}
}