From ac3e66e9e04bbaf367f7a9b9436dc27964a12c16 Mon Sep 17 00:00:00 2001 From: reverse Date: Wed, 19 Aug 2026 01:20:23 +0000 Subject: [PATCH] =?UTF-8?q?metadata:=20prune=20a=20channel's=20metadata=20?= =?UTF-8?q?once=20the=20channel=20no=20longer=20exists=20(tick=20GC)=20?= =?UTF-8?q?=E2=80=94=20#channel=20entries=20were=20never=20freed=20(unlike?= =?UTF-8?q?=20user=20entries=20on=20quit),=20so=20setting=20metadata=20on?= =?UTF-8?q?=20a=20channel=20that=20later=20empties=20leaked=20memory=20and?= =?UTF-8?q?=20kept=20re-persisting=20to=20disk=20forever;=20+P=20channels?= =?UTF-8?q?=20stay=20live=20so=20keep=20theirs?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/modules/metadata.rs | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/src/modules/metadata.rs b/src/modules/metadata.rs index bd5872c..a74e3ef 100644 --- a/src/modules/metadata.rs +++ b/src/modules/metadata.rs @@ -37,6 +37,30 @@ impl Module for Metadata { st.0.remove(&format!("u{uid}")); } } + fn on_tick(&mut self, s: &mut Server) { + // Free channel metadata (`#name` keys) once the channel is gone — user + // entries are pruned on quit, and +P channels stay in s.channels so keep + // theirs. Without this, metadata on a channel that empties leaks forever + // (and is re-persisted to disk). + let orphans: Vec = match s.ext.get::() { + Some(st) => st + .0 + .keys() + .filter(|k| k.starts_with('#') && !s.channels.contains_key(k.as_str())) + .cloned() + .collect(), + None => return, + }; + if orphans.is_empty() { + return; + } + if let Some(st) = s.ext.get_mut::() { + for k in &orphans { + st.0.remove(k); + } + } + save(s); + } } pub fn commands() -> Vec> {