use core::future::Future;
use std::io;
use std::sync::{Arc, OnceLock};
use std::time::Duration;
use crate::config::NucleiConfig;
use crate::rt::context;
use crate::rt::shard::Shared;
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
pub enum IoBackend {
Kqueue,
Epoll,
IoUring,
Iocp,
}
#[derive(Debug)]
pub struct Proactor {
config: NucleiConfig,
}
static PROACTOR: OnceLock<Proactor> = OnceLock::new();
type Waiting = kovan_map::HashMap<usize, Arc<Shared>>;
static WAITING: OnceLock<Waiting> = OnceLock::new();
fn waiting() -> &'static Waiting {
WAITING.get_or_init(|| Waiting::with_capacity(64))
}
fn wake_all(waiting: &Waiting) {
for (_, shard) in waiting.iter() {
shard.notify();
}
}
struct InWait(usize);
impl InWait {
fn new(shard: &Arc<Shared>) -> Self {
let key = Arc::as_ptr(shard) as usize;
waiting().insert(key, Arc::clone(shard));
Self(key)
}
}
impl Drop for InWait {
fn drop(&mut self) {
waiting().remove(&self.0);
}
}
impl Proactor {
pub fn get() -> &'static Proactor {
PROACTOR.get_or_init(|| Proactor {
config: NucleiConfig::default(),
})
}
pub fn with_config(config: NucleiConfig) -> &'static Proactor {
let mut config = Some(config);
let proactor = PROACTOR.get_or_init(|| Proactor {
config: config.take().unwrap_or_default(),
});
assert!(
config.is_none(),
"the proactor is configured already: call `with_config` once, before any IO"
);
proactor
}
pub fn config(&self) -> &NucleiConfig {
&self.config
}
pub fn wake(&self) {
wake_all(waiting());
}
pub fn wait(&self, max_event_size: usize, duration: Option<Duration>) -> io::Result<usize> {
if let Some(events) =
context::current(|current| current.map(|s| s.turn(max_event_size, duration)))
{
return events;
}
let shard = context::own_shard()?;
let _current = context::enter(&shard);
#[cfg(port)]
shard.take_port_back();
let _waiting = InWait::new(&shard.shared);
shard.turn(max_event_size, duration)
}
pub fn backend() -> IoBackend {
match context::current(|shard| shard.map(|s| s.io_backend())) {
Some(running) => running,
None => crate::rt::backend::chosen().map_or(POLLER, |b| {
if b.rings() {
IoBackend::IoUring
} else {
POLLER
}
}),
}
}
}
#[cfg(any(target_os = "linux", target_os = "android", target_os = "illumos"))]
pub(crate) const POLLER: IoBackend = IoBackend::Epoll;
#[cfg(any(
target_os = "macos",
target_os = "ios",
target_os = "freebsd",
target_os = "netbsd",
target_os = "openbsd",
target_os = "dragonfly",
))]
pub(crate) const POLLER: IoBackend = IoBackend::Kqueue;
#[cfg(target_os = "windows")]
pub(crate) const POLLER: IoBackend = IoBackend::Iocp;
pub fn drive<T>(future: impl Future<Output = T>) -> T {
crate::runtime::block_on(future)
}
#[cfg(test)]
mod tests {
use std::sync::Arc;
use std::time::{Duration, Instant};
use super::{Waiting, wake_all};
use crate::rt::context;
#[test]
fn a_wake_before_the_poller_wait_ends_it() {
let shard = context::own_shard().unwrap();
let _current = context::enter(&shard);
let waiting = Waiting::with_capacity(64);
waiting.insert(0, Arc::clone(&shard.shared));
wake_all(&waiting);
let start = Instant::now();
shard.turn(64, Some(Duration::from_secs(10))).unwrap();
assert!(
start.elapsed() < Duration::from_secs(5),
"the wake was lost: the wait ran {:?}",
start.elapsed()
);
}
}