From cd46cfc49fae67e8f260d2d8cef73d305f830671 Mon Sep 17 00:00:00 2001 From: reverse Date: Wed, 19 Aug 2026 01:53:41 +0000 Subject: [PATCH] =?UTF-8?q?securitygroups:=20cache=20parsed=20groups=20in?= =?UTF-8?q?=20a=20config=5Fgen-tagged=20thread=5Flocal=20instead=20of=20re?= =?UTF-8?q?-parsing=20all=20securitygroup=20lines=20on=20every=20g:=20extb?= =?UTF-8?q?an=20match=20and=20WHOIS=20=E2=80=94=20safe=20on=20the=20single?= =?UTF-8?q?-threaded=20core,=20re-parses=20only=20on=20rehash?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/modules/securitygroups.rs | 40 ++++++++++++++++++++++++++++------- 1 file changed, 32 insertions(+), 8 deletions(-) diff --git a/src/modules/securitygroups.rs b/src/modules/securitygroups.rs index f605b1d..fec0e00 100644 --- a/src/modules/securitygroups.rs +++ b/src/modules/securitygroups.rs @@ -45,6 +45,26 @@ fn flag_tri(v: Option<&str>) -> Tri { } } +thread_local! { + /// (config_gen, parsed groups) — re-parsed only when the config changes. The + /// core is single-threaded, so a thread_local cache is safe and lets the + /// `&Server` callers (the `g:` extban match, WHOIS) skip re-parsing per call. + static GROUPS: std::cell::RefCell<(u64, Vec)> = + const { std::cell::RefCell::new((u64::MAX, Vec::new())) }; +} + +/// Run `f` over the security groups, (re)parsing them only when the config changed. +fn with_groups(s: &Server, f: impl FnOnce(&[SecGroup]) -> R) -> R { + GROUPS.with(|cell| { + if cell.borrow().0 != s.config_gen { + let fresh = parse_groups(s); + *cell.borrow_mut() = (s.config_gen, fresh); + } + let guard = cell.borrow(); + f(&guard.1) + }) +} + fn parse_groups(s: &Server) -> Vec { let mut out = Vec::new(); for line in s @@ -144,18 +164,22 @@ fn matches(s: &Server, uid: Uid, g: &SecGroup) -> bool { /// Whether `uid` is a member of the named security group (case-insensitive). pub fn in_group(s: &Server, uid: Uid, name: &str) -> bool { - parse_groups(s) - .iter() - .any(|g| g.name.eq_ignore_ascii_case(name) && matches(s, uid, g)) + with_groups(s, |groups| { + groups + .iter() + .any(|g| g.name.eq_ignore_ascii_case(name) && matches(s, uid, g)) + }) } /// The names of the groups `uid` is in (only public ones unless `include_private`). pub fn user_groups(s: &Server, uid: Uid, include_private: bool) -> Vec { - parse_groups(s) - .into_iter() - .filter(|g| (include_private || g.public) && matches(s, uid, g)) - .map(|g| g.name) - .collect() + with_groups(s, |groups| { + groups + .iter() + .filter(|g| (include_private || g.public) && matches(s, uid, g)) + .map(|g| g.name.clone()) + .collect() + }) } pub fn commands() -> Vec> {