config/
pool.rs

1use crate::{get_current_epoch, LuaConfig, LuaConfigInner};
2use parking_lot::FairMutex as Mutex;
3use std::collections::VecDeque;
4use std::sync::atomic::{AtomicUsize, Ordering};
5use std::sync::LazyLock;
6use std::time::Duration;
7
8static POOL: LazyLock<Mutex<Pool>> = LazyLock::new(|| Mutex::new(Pool::new()));
9static LUA_SPARE_COUNT: LazyLock<metrics::Gauge> = LazyLock::new(|| {
10    metrics::describe_gauge!(
11        "lua_spare_count",
12        "the number of lua contexts available for reuse in the pool"
13    );
14    metrics::gauge!("lua_spare_count")
15});
16
17/// Maximum age of a lua context before we release it, in seconds
18static MAX_AGE: AtomicUsize = AtomicUsize::new(300);
19/// Maximum number of uses of a given lua context before we release it
20static MAX_USE: AtomicUsize = AtomicUsize::new(1024);
21/// Maximum number of spare lua contexts to maintain in the pool
22static MAX_SPARE: AtomicUsize = AtomicUsize::new(8192);
23static GC_ON_PUT: AtomicUsize = AtomicUsize::new(0);
24
25pub fn set_max_use(max_use: usize) {
26    MAX_USE.store(max_use, Ordering::Relaxed);
27}
28
29pub fn set_max_spare(max_spare: usize) {
30    MAX_SPARE.store(max_spare, Ordering::Relaxed);
31}
32
33pub fn set_max_age(max_age: usize) {
34    MAX_AGE.store(max_age, Ordering::Relaxed);
35}
36
37/// Set the gc on put percentage chance, in the range 0-100
38pub fn set_gc_on_put(v: u8) {
39    GC_ON_PUT.store(v as usize, Ordering::Relaxed);
40}
41
42#[derive(Default)]
43pub(crate) struct Pool {
44    pool: VecDeque<LuaConfigInner>,
45}
46
47impl Pool {
48    pub fn new() -> Self {
49        std::thread::Builder::new()
50            .name("config idler".to_string())
51            .spawn(|| loop {
52                std::thread::sleep(Duration::from_secs(30));
53                POOL.lock().expire();
54            })
55            .expect("create config idler thread");
56        Self::default()
57    }
58
59    pub fn expire(&mut self) {
60        let len_before = self.pool.len();
61        let epoch = get_current_epoch();
62        let max_age = Duration::from_secs(MAX_AGE.load(Ordering::Relaxed) as u64);
63        self.pool
64            .retain(|inner| inner.created.elapsed() < max_age && inner.epoch == epoch);
65        let len_after = self.pool.len();
66        let diff = len_before - len_after;
67        if diff > 0 {
68            LUA_SPARE_COUNT.decrement(diff as f64);
69        }
70    }
71
72    pub fn get(&mut self) -> Option<LuaConfigInner> {
73        let max_age = Duration::from_secs(MAX_AGE.load(Ordering::Relaxed) as u64);
74        loop {
75            let mut item = self.pool.pop_front()?;
76            LUA_SPARE_COUNT.decrement(1.);
77            if item.created.elapsed() > max_age || item.epoch != get_current_epoch() {
78                continue;
79            }
80            item.use_count += 1;
81            return Some(item);
82        }
83    }
84
85    pub fn put(&mut self, config: LuaConfigInner) {
86        let epoch = get_current_epoch();
87        if config.epoch != epoch {
88            // Stale; let it drop
89            return;
90        }
91        if self.pool.len() + 1 > MAX_SPARE.load(Ordering::Relaxed) {
92            return;
93        }
94        if config.created.elapsed() > Duration::from_secs(MAX_AGE.load(Ordering::Relaxed) as u64)
95            || config.use_count + 1 > MAX_USE.load(Ordering::Relaxed)
96        {
97            return;
98        }
99        let prob = GC_ON_PUT.load(Ordering::Relaxed);
100        if prob != 0 {
101            let chance = (rand::random::<f32>() * 100.) as usize;
102            if chance <= prob {
103                if let Err(err) = config.lua.gc_collect() {
104                    tracing::error!("Error during gc: {err:#}");
105                    return;
106                }
107            }
108        }
109
110        self.pool.push_back(config);
111        LUA_SPARE_COUNT.increment(1.);
112    }
113}
114
115pub(crate) fn pool_get() -> Option<LuaConfig> {
116    POOL.lock()
117        .get()
118        .map(|inner| LuaConfig { inner: Some(inner) })
119}
120
121pub(crate) fn pool_put(config: LuaConfigInner) {
122    POOL.lock().put(config);
123}