channel: reclaim departed-member +f flood counters and expired +J entries on the tick (slow high-churn leak)
This commit is contained in:
parent
92a3f5ba86
commit
d6cc813aad
3 changed files with 45 additions and 0 deletions
|
|
@ -608,6 +608,28 @@ impl Server {
|
|||
}
|
||||
}
|
||||
|
||||
/// Drop per-member flood state left behind by users who have left a channel
|
||||
/// (a slow leak on high-churn channels — Uids are never reused, so departed
|
||||
/// entries never get overwritten), and +J rejoin-block entries whose window has
|
||||
/// elapsed. Called from the background tick.
|
||||
pub fn purge_flood_state(&mut self) {
|
||||
let now = now();
|
||||
for ch in self.channels.values_mut() {
|
||||
// +f message-time counters only matter for current members
|
||||
if !ch.msgflood_hits.is_empty() {
|
||||
let members = &ch.members;
|
||||
ch.msgflood_hits.retain(|uid, _| members.contains_key(uid));
|
||||
}
|
||||
// +J recent-kick timestamps only matter within the (current) rejoin-block
|
||||
// window; once it's elapsed — or +J is off — the entry can't block anyone.
|
||||
if !ch.recent_kicks.is_empty() {
|
||||
let window = ch.modes.kicknorejoin.unwrap_or(0) as u64;
|
||||
ch.recent_kicks
|
||||
.retain(|_, ts| window > 0 && now.saturating_sub(*ts) < window);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Force `uid` out of `chan` (SAPART / SVSPART enforcement): announce the PART
|
||||
/// to the channel and to links, drop the membership, reap the channel if empty.
|
||||
/// No-op if the user isn't a member.
|
||||
|
|
|
|||
|
|
@ -639,6 +639,7 @@ impl Ircd {
|
|||
self.server.ping_links(); // keepalive on every server link
|
||||
self.server.purge_xlines(); // drop expired server bans
|
||||
self.server.purge_tbans(); // lift expired timed channel bans (TBAN)
|
||||
self.server.purge_flood_state(); // reclaim per-member +f/+J state of departed users
|
||||
for m in &mut self.modules {
|
||||
m.on_tick(&mut self.server); // timer-driven modules (e.g. reputation)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1278,6 +1278,28 @@ mod tests {
|
|||
Server::new(Config::default(), tx, Arc::new(AtomicU64::new(1)))
|
||||
}
|
||||
|
||||
// The tick purge reclaims per-member flood state of users who left, and +J
|
||||
// rejoin-block entries whose window elapsed — the slow high-churn leak.
|
||||
#[test]
|
||||
fn purge_flood_state_reclaims_departed_and_expired() {
|
||||
use crate::channels::{Channel, Member};
|
||||
let mut s = srv();
|
||||
let mut c = Channel::new("#c");
|
||||
c.members.insert(1, Member::default()); // uid 1 is a current member
|
||||
c.msgflood_hits.insert(1, vec![now()]); // member -> kept
|
||||
c.msgflood_hits.insert(99, vec![now()]); // departed -> dropped
|
||||
c.modes.kicknorejoin = Some(60);
|
||||
c.recent_kicks.insert(99, now().saturating_sub(120)); // expired -> dropped
|
||||
c.recent_kicks.insert(88, now()); // still within window -> kept
|
||||
s.channels.insert("#c".into(), c);
|
||||
s.purge_flood_state();
|
||||
let ch = &s.channels["#c"];
|
||||
assert!(ch.msgflood_hits.contains_key(&1));
|
||||
assert!(!ch.msgflood_hits.contains_key(&99), "departed member's +f state dropped");
|
||||
assert!(!ch.recent_kicks.contains_key(&99), "expired +J entry dropped");
|
||||
assert!(ch.recent_kicks.contains_key(&88), "fresh +J entry kept");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn resolved_host_applied_only_when_configured() {
|
||||
let mut s = srv(); // use_resolved_host = true (default)
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue