server: scrub a departed user's pending invites via a User.invited reverse index instead of scanning every channel on the network per quit — the old O(channels)-per-quit path was O(channels*quits) on a netsplit; the index is maintained at the 4 invite add/remove sites (INVITE cmd, S2S INVITE, join-consume, UNINVITE)
This commit is contained in:
parent
5101b1361d
commit
ab0ccae71f
7 changed files with 23 additions and 2 deletions
|
|
@ -907,6 +907,7 @@ impl Server {
|
||||||
ch.recent_kicks.remove(&uid); // they got back in; clear any +J rejoin timer
|
ch.recent_kicks.remove(&uid); // they got back in; clear any +J rejoin timer
|
||||||
if let Some(u) = self.users.get_mut(&uid) {
|
if let Some(u) = self.users.get_mut(&uid) {
|
||||||
u.channels.insert(key.clone());
|
u.channels.insert(key.clone());
|
||||||
|
u.invited.remove(&key); // invite consumed — drop it from the reverse index
|
||||||
}
|
}
|
||||||
// chancreate: snotice when a brand-new channel comes into being
|
// chancreate: snotice when a brand-new channel comes into being
|
||||||
if is_new
|
if is_new
|
||||||
|
|
|
||||||
|
|
@ -323,6 +323,9 @@ impl Command for Invite {
|
||||||
if let Some(ch) = s.channels.get_mut(&key) {
|
if let Some(ch) = s.channels.get_mut(&key) {
|
||||||
ch.invites.insert(tuid);
|
ch.invites.insert(tuid);
|
||||||
}
|
}
|
||||||
|
if let Some(u) = s.users.get_mut(&tuid) {
|
||||||
|
u.invited.insert(key.clone()); // reverse index for O(1) quit scrub
|
||||||
|
}
|
||||||
let who = s.users[&tuid].nick.clone();
|
let who = s.users[&tuid].nick.clone();
|
||||||
s.numeric(uid, RPL_INVITING, &format!("{who} {chan}"));
|
s.numeric(uid, RPL_INVITING, &format!("{who} {chan}"));
|
||||||
let prefix = s.users[&uid].prefix();
|
let prefix = s.users[&uid].prefix();
|
||||||
|
|
@ -390,6 +393,9 @@ impl Command for Uninvite {
|
||||||
.get_mut(&key)
|
.get_mut(&key)
|
||||||
.map(|ch| ch.invites.remove(&tuid))
|
.map(|ch| ch.invites.remove(&tuid))
|
||||||
.unwrap_or(false);
|
.unwrap_or(false);
|
||||||
|
if let Some(u) = s.users.get_mut(&tuid) {
|
||||||
|
u.invited.remove(&key); // keep the reverse index in sync
|
||||||
|
}
|
||||||
let who = s.users[&tuid].nick.clone();
|
let who = s.users[&tuid].nick.clone();
|
||||||
let word = if removed {
|
let word = if removed {
|
||||||
"is no longer invited to"
|
"is no longer invited to"
|
||||||
|
|
|
||||||
|
|
@ -1344,6 +1344,9 @@ impl Server {
|
||||||
if let Some(ch) = self.channels.get_mut(&key) {
|
if let Some(ch) = self.channels.get_mut(&key) {
|
||||||
ch.invites.insert(luid);
|
ch.invites.insert(luid);
|
||||||
}
|
}
|
||||||
|
if let Some(u) = self.users.get_mut(&luid) {
|
||||||
|
u.invited.insert(key.clone()); // reverse index for O(1) quit scrub
|
||||||
|
}
|
||||||
let prefix = self.uuid_prefix(&src).unwrap_or_else(|| src.clone());
|
let prefix = self.uuid_prefix(&src).unwrap_or_else(|| src.clone());
|
||||||
let nick = self.users.get(&luid).map(|u| u.nick.clone()).unwrap_or_default();
|
let nick = self.users.get(&luid).map(|u| u.nick.clone()).unwrap_or_default();
|
||||||
self.send(luid, format!(":{prefix} INVITE {nick} :{chan}"));
|
self.send(luid, format!(":{prefix} INVITE {nick} :{chan}"));
|
||||||
|
|
@ -2768,6 +2771,7 @@ mod tests {
|
||||||
caps: Caps::default(),
|
caps: Caps::default(),
|
||||||
sasl_mech: None,
|
sasl_mech: None,
|
||||||
channels: HashSet::default(),
|
channels: HashSet::default(),
|
||||||
|
invited: HashSet::default(),
|
||||||
watch: Vec::new(),
|
watch: Vec::new(),
|
||||||
monitor: Vec::new(),
|
monitor: Vec::new(),
|
||||||
silence: Vec::new(),
|
silence: Vec::new(),
|
||||||
|
|
@ -2855,6 +2859,7 @@ mod tests {
|
||||||
caps,
|
caps,
|
||||||
sasl_mech: None,
|
sasl_mech: None,
|
||||||
channels: HashSet::default(),
|
channels: HashSet::default(),
|
||||||
|
invited: HashSet::default(),
|
||||||
watch: Vec::new(),
|
watch: Vec::new(),
|
||||||
monitor: Vec::new(),
|
monitor: Vec::new(),
|
||||||
silence: Vec::new(),
|
silence: Vec::new(),
|
||||||
|
|
|
||||||
|
|
@ -392,6 +392,7 @@ mod tests {
|
||||||
caps: Caps::default(),
|
caps: Caps::default(),
|
||||||
sasl_mech: None,
|
sasl_mech: None,
|
||||||
channels: chans,
|
channels: chans,
|
||||||
|
invited: crate::map::HashSet::default(),
|
||||||
watch: Vec::new(),
|
watch: Vec::new(),
|
||||||
monitor: Vec::new(),
|
monitor: Vec::new(),
|
||||||
silence: Vec::new(),
|
silence: Vec::new(),
|
||||||
|
|
|
||||||
|
|
@ -92,6 +92,7 @@ impl Node {
|
||||||
caps: Caps::default(),
|
caps: Caps::default(),
|
||||||
sasl_mech: None,
|
sasl_mech: None,
|
||||||
channels: HashSet::default(),
|
channels: HashSet::default(),
|
||||||
|
invited: HashSet::default(),
|
||||||
watch: Vec::new(),
|
watch: Vec::new(),
|
||||||
monitor: Vec::new(),
|
monitor: Vec::new(),
|
||||||
silence: Vec::new(),
|
silence: Vec::new(),
|
||||||
|
|
|
||||||
|
|
@ -373,6 +373,7 @@ impl Server {
|
||||||
caps: Caps::default(),
|
caps: Caps::default(),
|
||||||
sasl_mech: None,
|
sasl_mech: None,
|
||||||
channels: HashSet::default(),
|
channels: HashSet::default(),
|
||||||
|
invited: HashSet::default(),
|
||||||
watch: Vec::new(),
|
watch: Vec::new(),
|
||||||
monitor: Vec::new(),
|
monitor: Vec::new(),
|
||||||
silence: Vec::new(),
|
silence: Vec::new(),
|
||||||
|
|
@ -724,8 +725,12 @@ impl Server {
|
||||||
// Scrub any pending +i invite for this user from channels they never joined
|
// Scrub any pending +i invite for this user from channels they never joined
|
||||||
// (a member consumes their invite on join; a never-joined invite for a now-
|
// (a member consumes their invite on join; a never-joined invite for a now-
|
||||||
// departed uid would otherwise linger forever on a persistent channel).
|
// departed uid would otherwise linger forever on a persistent channel).
|
||||||
for ch in self.channels.values_mut() {
|
// The `invited` reverse index gives the exact channels, so this is O(pending
|
||||||
ch.invites.remove(&uid);
|
// invites) rather than a scan of every channel on the network.
|
||||||
|
for key in &user.invited {
|
||||||
|
if let Some(ch) = self.channels.get_mut(key) {
|
||||||
|
ch.invites.remove(&uid);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
self.channels.retain(|_, c| c.keep_alive());
|
self.channels.retain(|_, c| c.keep_alive());
|
||||||
self.watch_notify_offline(&user.nick); // tell WATCH/MONITOR watchers
|
self.watch_notify_offline(&user.nick); // tell WATCH/MONITOR watchers
|
||||||
|
|
@ -1424,6 +1429,7 @@ mod tests {
|
||||||
caps: Caps::default(),
|
caps: Caps::default(),
|
||||||
sasl_mech: None,
|
sasl_mech: None,
|
||||||
channels: HashSet::default(),
|
channels: HashSet::default(),
|
||||||
|
invited: HashSet::default(),
|
||||||
watch: Vec::new(),
|
watch: Vec::new(),
|
||||||
monitor: Vec::new(),
|
monitor: Vec::new(),
|
||||||
silence: Vec::new(),
|
silence: Vec::new(),
|
||||||
|
|
|
||||||
|
|
@ -248,6 +248,7 @@ pub struct User {
|
||||||
pub caps: Caps, // enabled IRCv3 capabilities
|
pub caps: Caps, // enabled IRCv3 capabilities
|
||||||
pub sasl_mech: Option<String>, // SASL mechanism chosen, mid-handshake
|
pub sasl_mech: Option<String>, // SASL mechanism chosen, mid-handshake
|
||||||
pub channels: HashSet<String>, // lowercased channel keys
|
pub channels: HashSet<String>, // lowercased channel keys
|
||||||
|
pub invited: HashSet<String>, // channels this user has a pending +i invite to (reverse index)
|
||||||
pub watch: Vec<String>, // WATCH list — lowercased nicks
|
pub watch: Vec<String>, // WATCH list — lowercased nicks
|
||||||
pub monitor: Vec<String>, // MONITOR list — lowercased nicks
|
pub monitor: Vec<String>, // MONITOR list — lowercased nicks
|
||||||
pub silence: Vec<String>, // SILENCE masks — nick!user@host globs
|
pub silence: Vec<String>, // SILENCE masks — nick!user@host globs
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue