kaspa_utils/
fd_budget.rs

1use std::{
2    ops::Deref,
3    sync::atomic::{AtomicI32, Ordering},
4};
5use thiserror::Error;
6
7static ACQUIRED_FD: AtomicI32 = AtomicI32::new(0);
8#[derive(Debug)]
9pub struct FDGuard(i32);
10
11impl FDGuard {
12    pub fn acquired(&self) -> i32 {
13        self.0
14    }
15}
16
17impl Deref for FDGuard {
18    type Target = i32;
19
20    fn deref(&self) -> &Self::Target {
21        &self.0
22    }
23}
24
25impl Drop for FDGuard {
26    fn drop(&mut self) {
27        ACQUIRED_FD.fetch_sub(self.0, Ordering::SeqCst); // todo ordering??
28    }
29}
30
31#[derive(Copy, Clone, Debug, PartialEq, Error)]
32#[error("Exceeded upper bound, acquired: {acquired}, limit: {limit}")]
33pub struct Error {
34    pub acquired: i32,
35    pub limit: i32,
36}
37
38pub fn acquire_guard(value: i32) -> Result<FDGuard, Error> {
39    loop {
40        let acquired = ACQUIRED_FD.load(Ordering::SeqCst); // todo ordering??
41        let limit = limit();
42        if acquired + value > limit {
43            return Err(Error { acquired, limit });
44        }
45        // todo ordering??
46        match ACQUIRED_FD.compare_exchange(acquired, acquired + value, Ordering::SeqCst, Ordering::SeqCst) {
47            Ok(_) => return Ok(FDGuard(value)),
48            Err(_) => continue, // The global counter was updated by another thread, retry
49        }
50    }
51}
52
53#[cfg(not(target_arch = "wasm32"))]
54pub fn try_set_fd_limit(limit: u64) -> std::io::Result<u64> {
55    cfg_if::cfg_if! {
56        if #[cfg(target_os = "windows")] {
57                rlimit::setmaxstdio(limit as u32).map(|v| v as u64)
58        } else if #[cfg(unix)] {
59            rlimit::increase_nofile_limit(limit)
60        }
61    }
62}
63
64pub fn limit() -> i32 {
65    cfg_if::cfg_if! {
66        if #[cfg(test)] {
67            100
68        }
69        else if #[cfg(target_os = "windows")] {
70            rlimit::getmaxstdio() as i32
71        }
72        else if #[cfg(unix)] {
73            rlimit::getrlimit(rlimit::Resource::NOFILE).unwrap().0 as i32
74        }
75        else {
76            512
77        }
78    }
79}
80
81pub fn remainder() -> i32 {
82    limit() - ACQUIRED_FD.load(Ordering::Relaxed)
83}
84
85#[cfg(test)]
86mod tests {
87    use super::*;
88
89    #[test]
90    fn test_acquire_and_release_guards() {
91        let guard = acquire_guard(30).unwrap();
92        assert_eq!(guard.acquired(), 30);
93        assert_eq!(ACQUIRED_FD.load(Ordering::Relaxed), 30);
94
95        let err = acquire_guard(80).unwrap_err();
96        assert_eq!(err, Error { acquired: 30, limit: 100 });
97        assert_eq!(ACQUIRED_FD.load(Ordering::Relaxed), 30);
98
99        drop(guard);
100        assert_eq!(ACQUIRED_FD.load(Ordering::Relaxed), 0);
101
102        let guard = acquire_guard(100).unwrap();
103        assert_eq!(guard.acquired(), 100);
104        assert_eq!(ACQUIRED_FD.load(Ordering::Relaxed), 100);
105        drop(guard);
106        assert_eq!(ACQUIRED_FD.load(Ordering::Relaxed), 0);
107
108        let err = acquire_guard(101).unwrap_err();
109        assert_eq!(err, Error { acquired: 0, limit: 100 });
110    }
111}