1use core::future::Future;
6use std::io;
7use std::sync::{Arc, OnceLock};
8use std::time::Duration;
9
10use crate::config::NucleiConfig;
11use crate::rt::context;
12use crate::rt::shard::Shared;
13
14#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
16pub enum IoBackend {
17 Kqueue,
19 Epoll,
21 IoUring,
23 Iocp,
25}
26
27#[derive(Debug)]
30pub struct Proactor {
31 config: NucleiConfig,
32}
33
34static PROACTOR: OnceLock<Proactor> = OnceLock::new();
35
36type Waiting = kovan_map::HashMap<usize, Arc<Shared>>;
38
39static WAITING: OnceLock<Waiting> = OnceLock::new();
40
41fn waiting() -> &'static Waiting {
42 WAITING.get_or_init(|| Waiting::with_capacity(64))
44}
45
46fn wake_all(waiting: &Waiting) {
50 for (_, shard) in waiting.iter() {
51 shard.notify();
52 }
53}
54
55struct InWait(usize);
57
58impl InWait {
59 fn new(shard: &Arc<Shared>) -> Self {
60 let key = Arc::as_ptr(shard) as usize;
61 waiting().insert(key, Arc::clone(shard));
62 Self(key)
63 }
64}
65
66impl Drop for InWait {
67 fn drop(&mut self) {
68 waiting().remove(&self.0);
69 }
70}
71
72impl Proactor {
73 pub fn get() -> &'static Proactor {
76 PROACTOR.get_or_init(|| Proactor {
77 config: NucleiConfig::default(),
78 })
79 }
80
81 pub fn with_config(config: NucleiConfig) -> &'static Proactor {
93 let mut config = Some(config);
94 let proactor = PROACTOR.get_or_init(|| Proactor {
95 config: config.take().unwrap_or_default(),
96 });
97 assert!(
98 config.is_none(),
99 "the proactor is configured already: call `with_config` once, before any IO"
100 );
101 proactor
102 }
103
104 pub fn config(&self) -> &NucleiConfig {
106 &self.config
107 }
108
109 pub fn wake(&self) {
111 wake_all(waiting());
112 }
113
114 pub fn wait(&self, max_event_size: usize, duration: Option<Duration>) -> io::Result<usize> {
125 if let Some(events) =
126 context::current(|current| current.map(|s| s.turn(max_event_size, duration)))
127 {
128 return events;
129 }
130 let shard = context::own_shard()?;
131 let _current = context::enter(&shard);
132 #[cfg(port)]
133 shard.take_port_back();
134 let _waiting = InWait::new(&shard.shared);
135 shard.turn(max_event_size, duration)
136 }
137
138 pub fn backend() -> IoBackend {
143 match context::current(|shard| shard.map(|s| s.io_backend())) {
144 Some(running) => running,
145 None => crate::rt::backend::chosen().map_or(POLLER, |b| {
146 if b.rings() {
147 IoBackend::IoUring
148 } else {
149 POLLER
150 }
151 }),
152 }
153 }
154}
155
156#[cfg(any(target_os = "linux", target_os = "android", target_os = "illumos"))]
158pub(crate) const POLLER: IoBackend = IoBackend::Epoll;
159#[cfg(any(
160 target_os = "macos",
161 target_os = "ios",
162 target_os = "freebsd",
163 target_os = "netbsd",
164 target_os = "openbsd",
165 target_os = "dragonfly",
166))]
167pub(crate) const POLLER: IoBackend = IoBackend::Kqueue;
168#[cfg(target_os = "windows")]
169pub(crate) const POLLER: IoBackend = IoBackend::Iocp;
170
171pub fn drive<T>(future: impl Future<Output = T>) -> T {
174 crate::runtime::block_on(future)
175}
176
177#[cfg(test)]
178mod tests {
179 use std::sync::Arc;
180 use std::time::{Duration, Instant};
181
182 use super::{Waiting, wake_all};
183 use crate::rt::context;
184
185 #[test]
188 fn a_wake_before_the_poller_wait_ends_it() {
189 let shard = context::own_shard().unwrap();
190 let _current = context::enter(&shard);
191 let waiting = Waiting::with_capacity(64);
192 waiting.insert(0, Arc::clone(&shard.shared));
193 wake_all(&waiting);
194 let start = Instant::now();
195 shard.turn(64, Some(Duration::from_secs(10))).unwrap();
196 assert!(
197 start.elapsed() < Duration::from_secs(5),
198 "the wake was lost: the wait ran {:?}",
199 start.elapsed()
200 );
201 }
202}