1#![allow(clippy::cast_possible_truncation)]
2use std::collections::{BTreeMap, VecDeque};
3use std::{cell::RefCell, mem, num::NonZeroUsize, ops, rc::Rc, time::Duration, time::Instant};
4
5use ntex_rt::Arbiter;
6use ntex_util::time::{Millis, Seconds, now, sleep};
7use ntex_util::{HashSet, spawn};
8use slab::Slab;
9
10use crate::IoRef;
11
12const CAP: usize = 32;
13const MAX_CACHED_SET_CAP: usize = 512;
15
16thread_local! {
17 static MANAGER: RefCell<Option<IoManager>> = const { RefCell::new(None) };
18}
19
20#[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Debug, Default)]
21pub struct Id(Option<NonZeroUsize>);
25
26#[derive(Copy, Clone, Default, Debug, Eq, PartialEq, Ord, PartialOrd, Hash)]
27pub struct TimerHandle(u32);
37
38impl TimerHandle {
39 pub const ZERO: TimerHandle = TimerHandle(0);
41
42 pub fn is_set(&self) -> bool {
47 self.0 != 0
48 }
49
50 pub fn remains(&self) -> Seconds {
56 let rem = self.instant().saturating_duration_since(now());
57 let secs = rem.as_secs() + u64::from(rem.subsec_nanos() != 0);
58 Seconds(secs.min(u64::from(u16::MAX)) as u16)
59 }
60
61 pub fn instant(&self) -> Instant {
66 IoManager::with(|mgr| mgr.timers.base + Duration::from_secs(u64::from(self.0)))
67 }
68
69 pub(crate) fn update(self, timeout: Seconds, io: &IoRef) -> TimerHandle {
70 IoManager::with(|mgr| {
71 let new_hnd = mgr.timers.deadline(timeout);
72 if self.0 == new_hnd || self.0 == new_hnd + 1 {
73 self
74 } else {
75 mgr.timers.unregister(self, io);
76 mgr.timers.register(timeout, io)
77 }
78 })
79 }
80
81 pub(crate) fn unregister(self, io: &IoRef) {
82 IoManager::with(|manager| manager.timers.unregister(self, io));
83 }
84
85 pub(crate) fn register(timeout: Seconds, io: &IoRef) -> TimerHandle {
86 IoManager::with(move |mgr| mgr.timers.register(timeout, io))
87 }
88}
89
90impl ops::Add<Seconds> for TimerHandle {
91 type Output = TimerHandle;
92
93 #[inline]
94 fn add(self, other: Seconds) -> TimerHandle {
95 TimerHandle(self.0 + u32::from(other.0))
96 }
97}
98
99struct TimerStorage {
100 running: bool,
101 base: Instant,
102 current: u32,
104 cache: VecDeque<HashSet<Id>>,
105 notifications: BTreeMap<u32, HashSet<Id>>,
106}
107
108impl TimerStorage {
109 fn unregister(&mut self, hnd: TimerHandle, io: &IoRef) {
110 if let Some(items) = self.notifications.get_mut(&hnd.0) {
111 items.remove(&io.id());
112 if items.is_empty() {
113 let items = self.notifications.remove(&hnd.0).unwrap();
115 self.recycle(items);
116 }
117 }
118 }
119
120 fn recycle(&mut self, items: HashSet<Id>) {
123 debug_assert!(items.is_empty());
124 if self.cache.len() < CAP && items.capacity() <= MAX_CACHED_SET_CAP {
125 self.cache.push_back(items);
126 }
127 }
128
129 fn update_current(&mut self) -> u32 {
131 self.current = self.base.elapsed().as_secs() as u32;
132 self.current
133 }
134
135 fn deadline(&mut self, timeout: Seconds) -> u32 {
141 let elapsed = now().saturating_duration_since(self.base);
142 let secs = elapsed.as_secs() as u32;
143 if secs < self.current {
144 return self.current + 1 + u32::from(timeout.0);
146 }
147 self.current = secs;
148
149 if self.notifications.is_empty() {
150 self.base += Duration::new(0, elapsed.subsec_nanos());
154 self.current + u32::from(timeout.0)
155 } else {
156 let partial = u32::from(elapsed.subsec_nanos() != 0);
157 self.current + partial + u32::from(timeout.0)
158 }
159 }
160
161 fn register(&mut self, timeout: Seconds, io: &IoRef) -> TimerHandle {
162 let hnd = self.deadline(timeout);
163 if let Some(items) = self.notifications.get_mut(&hnd) {
164 items.insert(io.id());
165 } else {
166 let mut items = self.cache.pop_front().unwrap_or_default();
167 items.insert(io.id());
168 self.notifications.insert(hnd, items);
169 }
170
171 self.run_timer();
172
173 TimerHandle(hnd)
174 }
175
176 fn run_timer(&mut self) {
177 if self.running {
178 return;
179 }
180 self.running = true;
181
182 spawn(async move {
183 let guard = TimerGuard;
184 let timer = sleep(Self::next_tick());
186 loop {
187 timer.wait().await;
188
189 let stop = IoManager::with(|mgr| {
190 let current = mgr.timers.update_current();
191
192 while let Some(entry) = mgr.timers.notifications.first_entry() {
194 if *entry.key() > current {
195 break;
196 }
197 let mut items = entry.remove();
198 for id in items.drain() {
199 if let Some(io) = mgr.get(id) {
200 io.notify_timeout();
201 }
202 }
203 mgr.timers.recycle(items);
204 }
205
206 if mgr.timers.notifications.is_empty() {
207 mgr.timers.running = false;
208 true
209 } else {
210 false
211 }
212 });
213
214 if stop {
215 break;
216 }
217 timer.reset(Self::next_tick());
218 }
219 drop(guard);
220 });
221 }
222
223 fn next_tick() -> Millis {
226 let next = IoManager::with(|mgr| {
227 let t = &mgr.timers;
228 let next = t.base + Duration::from_secs(u64::from(t.current) + 1);
229 next.saturating_duration_since(Instant::now())
230 });
231 Millis(next.as_millis() as u32 + 1)
232 }
233}
234
235struct TimerGuard;
236
237impl Drop for TimerGuard {
238 fn drop(&mut self) {
239 IoManager::with(|mgr| {
240 mgr.timers.running = false;
241 mgr.timers.notifications.clear();
242 });
243 }
244}
245
246pub(crate) struct IoManager {
247 storage: Slab<Option<IoRef>>,
248 timers: TimerStorage,
249 pub(crate) iops: Iops,
250}
251
252impl Default for IoManager {
253 fn default() -> IoManager {
254 let mut storage = Slab::new();
255 assert_eq!(storage.insert(None), 0);
256
257 IoManager {
258 storage,
259 timers: TimerStorage {
260 running: false,
261 base: Instant::now(),
262 current: 0,
263 cache: VecDeque::with_capacity(CAP),
264 notifications: BTreeMap::default(),
265 },
266 iops: Iops {
267 running: false,
268 ops: Vec::with_capacity(32),
269 },
270 }
271 }
272}
273
274impl IoManager {
275 fn with<F, R>(f: F) -> R
282 where
283 F: FnOnce(&mut IoManager) -> R,
284 {
285 let mut f = Some(f);
286 MANAGER
287 .try_with(|cell| {
288 let mut mgr = cell.borrow_mut();
289 let mgr = mgr.get_or_insert_with(|| {
290 Arbiter::on_shutdown(IoManager::reset);
291 IoManager::default()
292 });
293 (f.take().unwrap())(mgr)
294 })
295 .unwrap_or_else(|_| (f.take().unwrap())(&mut IoManager::default()))
296 }
297
298 fn reset() {
299 let mgr = MANAGER
302 .try_with(|cell| cell.borrow_mut().take())
303 .ok()
304 .flatten();
305 drop(mgr);
306 }
307
308 fn get(&self, id: Id) -> Option<&IoRef> {
309 if let Some(id) = id.0 {
310 self.storage.get(id.get()).and_then(|item| item.as_ref())
311 } else {
312 None
313 }
314 }
315
316 pub(crate) fn register(io: &IoRef) -> Id {
317 IoManager::with(|manager| {
318 let entry = manager.storage.vacant_entry();
319 let id = Id(NonZeroUsize::new(entry.key()));
320 entry.insert(Some(io.clone()));
321 id
322 })
323 }
324
325 pub(crate) fn unregister(io: &IoRef) {
326 if let Some(id) = io.id().0 {
327 io.0.id.set(Id(None));
328 IoManager::with(|manager| {
329 if let Some(Some(item)) = manager.storage.get(id.get())
332 && Rc::ptr_eq(&item.0, &io.0)
333 {
334 manager.storage.remove(id.get());
335 }
336 });
337 }
338 }
339}
340
341pub(crate) struct Iops {
342 running: bool,
343 pub(crate) ops: Vec<Id>,
344}
345
346impl Iops {
347 pub(crate) fn schedule_write(id: Id) {
348 IoManager::with(|mgr| {
349 mgr.iops.ops.push(id);
350
351 if !mgr.iops.running {
352 mgr.iops.running = true;
353 spawn(async move { Iops::run() });
354 }
355 });
356 }
357
358 pub(crate) fn run() {
359 IoManager::with(|mgr| {
360 mgr.iops.running = false;
361
362 let mut ops = mem::take(&mut mgr.iops.ops);
363 for id in ops.drain(..) {
364 if let Some(io) = mgr.get(id) {
365 io.ops_send_buf();
366 }
367 }
368 let _ = mem::replace(&mut mgr.iops.ops, ops);
369 });
370 }
371
372 #[cfg(test)]
373 pub(crate) fn is_registered(io: &IoRef) -> bool {
374 IoManager::with(|mgr| mgr.iops.ops.contains(&io.id()))
375 }
376}
377
378#[cfg(test)]
379mod tests {
380 use ntex::rt::{DefaultRuntime, System};
381
382 use super::*;
383
384 async fn wait_timeout(io: &crate::Io) -> Duration {
385 let start = Instant::now();
386 let st = std::future::poll_fn(|cx| io.poll_status_update(cx)).await;
387 assert!(matches!(st, crate::IoStatusUpdate::Timeout));
388 start.elapsed()
389 }
390
391 #[test]
392 fn large_timer_sets_are_not_cached() {
393 let mut timers = TimerStorage {
394 running: false,
395 base: Instant::now(),
396 current: 0,
397 cache: VecDeque::new(),
398 notifications: BTreeMap::new(),
399 };
400
401 let mut small = HashSet::default();
402 small.insert(Id(NonZeroUsize::new(1)));
403 small.clear();
404 timers.recycle(small);
405 assert_eq!(timers.cache.len(), 1);
406
407 let mut large = HashSet::default();
409 for i in 1..=10_000 {
410 large.insert(Id(NonZeroUsize::new(i)));
411 }
412 large.clear();
413 timers.recycle(large);
414 assert_eq!(timers.cache.len(), 1);
415 assert!(timers.cache[0].capacity() <= MAX_CACHED_SET_CAP);
416
417 for _ in 0..2 * CAP {
419 timers.recycle(HashSet::default());
420 }
421 assert_eq!(timers.cache.len(), CAP);
422 }
423
424 #[ntex::test]
427 async fn timer_expires_after_timeout() {
428 use ntex_service::cfg::SharedCfg;
429
430 use crate::{Io, testing::IoTest};
431
432 let (_client, server) = IoTest::create();
433 let io = Io::new(server, SharedCfg::new("T"));
434
435 sleep(Millis(500)).await;
437
438 io.start_timer(Seconds(1));
439 let elapsed = wait_timeout(&io).await;
440 assert!(
441 elapsed >= Duration::from_secs(1) && elapsed < Duration::from_millis(1300),
442 "elapsed: {elapsed:?}"
443 );
444 }
445
446 #[ntex::test]
449 async fn timer_expires_within_second_after_timeout() {
450 use ntex_service::cfg::SharedCfg;
451
452 use crate::{Io, testing::IoTest};
453
454 let (_client1, server1) = IoTest::create();
455 let (_client2, server2) = IoTest::create();
456 let io1 = Io::new(server1, SharedCfg::new("T"));
457 let io2 = Io::new(server2, SharedCfg::new("T"));
458
459 io1.start_timer(Seconds(10));
460 sleep(Millis(500)).await;
461
462 io2.start_timer(Seconds(1));
464 let elapsed = wait_timeout(&io2).await;
465 assert!(
466 elapsed >= Duration::from_secs(1) && elapsed < Duration::from_millis(1800),
467 "elapsed: {elapsed:?}"
468 );
469 }
470
471 fn has_manager() -> bool {
472 MANAGER.with(|mgr| mgr.borrow().is_some())
473 }
474
475 #[test]
479 fn manager_reset_on_shutdown() {
480 std::thread::spawn(|| {
481 System::new("test", DefaultRuntime).block_on(async {
482 Iops::schedule_write(Id(None));
483 assert!(has_manager());
484 });
485 assert!(!has_manager());
486
487 System::build()
488 .build(DefaultRuntime)
489 .run(|| {
490 Iops::schedule_write(Id(None));
491 System::current().stop();
492 Ok(())
493 })
494 .unwrap();
495 assert!(!has_manager());
496 })
497 .join()
498 .unwrap();
499 }
500}