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
17static MAX_AGE: AtomicUsize = AtomicUsize::new(300);
19static MAX_USE: AtomicUsize = AtomicUsize::new(1024);
21static 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
37pub 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 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}