server: skip the O(users) callerid ACCEPT scrub on quit unless the departing nick is actually accepted by someone — a reverse count (accepted_nicks) maintained through centralized accept_add/accept_remove helpers avoids scanning every user's accept list on each quit (O(users*quits) on a netsplit); the full scrub still runs when the count is nonzero, so the nick-reuse protection is unchanged
This commit is contained in:
parent
ab0ccae71f
commit
fb1a9f3d70
3 changed files with 63 additions and 10 deletions
|
|
@ -637,10 +637,13 @@ pub(crate) fn deliver(s: &mut Server, uid: Uid, params: &[String], notice: bool)
|
||||||
.map(|u| u.nick.to_ascii_lowercase())
|
.map(|u| u.nick.to_ascii_lowercase())
|
||||||
.unwrap_or_default();
|
.unwrap_or_default();
|
||||||
let maxacc = s.conf_num("maxaccept", crate::watch::ACCEPT_MAX);
|
let maxacc = s.conf_num("maxaccept", crate::watch::ACCEPT_MAX);
|
||||||
if let Some(su) = s.users.get_mut(&uid) {
|
let can_add = s
|
||||||
if !tnick.is_empty() && su.accept.len() < maxacc && !su.accept.contains(&tnick) {
|
.users
|
||||||
su.accept.push(tnick);
|
.get(&uid)
|
||||||
}
|
.map(|u| !tnick.is_empty() && u.accept.len() < maxacc && !u.accept.contains(&tnick))
|
||||||
|
.unwrap_or(false);
|
||||||
|
if can_add {
|
||||||
|
s.accept_add(uid, tnick);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else if let Some((uuid, via)) = s.find_remote(target) {
|
} else if let Some((uuid, via)) = s.find_remote(target) {
|
||||||
|
|
|
||||||
|
|
@ -373,8 +373,8 @@ impl Command for Accept {
|
||||||
ERR_ACCEPTFULL,
|
ERR_ACCEPTFULL,
|
||||||
&format!("{name} :Your accept list is full"),
|
&format!("{name} :Your accept list is full"),
|
||||||
);
|
);
|
||||||
} else if let Some(u) = s.users.get_mut(&uid) {
|
} else {
|
||||||
u.accept.push(low);
|
s.accept_add(uid, low);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
let existed = s
|
let existed = s
|
||||||
|
|
@ -388,8 +388,8 @@ impl Command for Accept {
|
||||||
ERR_ACCEPTNOT,
|
ERR_ACCEPTNOT,
|
||||||
&format!("{name} :is not on your accept list"),
|
&format!("{name} :is not on your accept list"),
|
||||||
);
|
);
|
||||||
} else if let Some(u) = s.users.get_mut(&uid) {
|
} else {
|
||||||
u.accept.retain(|x| x != &low);
|
s.accept_remove(uid, &low);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -166,6 +166,11 @@ pub struct Server {
|
||||||
/// cache it in `ext` tagged with this value and re-parse only when it changes,
|
/// cache it in `ext` tagged with this value and re-parse only when it changes,
|
||||||
/// so a REHASH can never leave a stale cache (see e.g. `modules::disable`).
|
/// so a REHASH can never leave a stale cache (see e.g. `modules::disable`).
|
||||||
pub config_gen: u64,
|
pub config_gen: u64,
|
||||||
|
/// How many accept lists contain each (lowercased) nick. A quit only scans every
|
||||||
|
/// user's accept list when the departing nick is actually accepted by someone —
|
||||||
|
/// the common case (count 0) skips the O(users) scan. Maintained solely through
|
||||||
|
/// `accept_add`/`accept_remove` and the quit path.
|
||||||
|
pub accepted_nicks: HashMap<String, u32>,
|
||||||
// labeled-response: while Some((uid, buf)), that client's own responses are
|
// labeled-response: while Some((uid, buf)), that client's own responses are
|
||||||
// diverted into `buf` instead of the socket, so `on_line` can wrap them with
|
// diverted into `buf` instead of the socket, so `on_line` can wrap them with
|
||||||
// the command's `label` (single tag, BATCH, or ACK). RefCell because the
|
// the command's `label` (single tag, BATCH, or ACK). RefCell because the
|
||||||
|
|
@ -222,6 +227,7 @@ impl Server {
|
||||||
webirc: cfg.webirc,
|
webirc: cfg.webirc,
|
||||||
raw_config: cfg.raw,
|
raw_config: cfg.raw,
|
||||||
config_gen: 0,
|
config_gen: 0,
|
||||||
|
accepted_nicks: HashMap::default(),
|
||||||
label_capture: RefCell::new(None),
|
label_capture: RefCell::new(None),
|
||||||
log: RefCell::new(LogState::default()),
|
log: RefCell::new(LogState::default()),
|
||||||
event_tx,
|
event_tx,
|
||||||
|
|
@ -676,10 +682,50 @@ impl Server {
|
||||||
|
|
||||||
/// Remove a user: broadcast QUIT to everyone sharing a channel, drop them
|
/// Remove a user: broadcast QUIT to everyone sharing a channel, drop them
|
||||||
/// from all channels, free the nick, and close the socket.
|
/// from all channels, free the nick, and close the socket.
|
||||||
|
/// Add `nick_low` (already lowercased) to `uid`'s callerid accept list and bump
|
||||||
|
/// the reverse count. Caller has already checked it's absent and under the cap.
|
||||||
|
pub fn accept_add(&mut self, uid: Uid, nick_low: String) {
|
||||||
|
if self.users.get_mut(&uid).map(|u| u.accept.push(nick_low.clone())).is_none() {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
*self.accepted_nicks.entry(nick_low).or_insert(0) += 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Remove `nick_low` from `uid`'s accept list and decrement the reverse count.
|
||||||
|
pub fn accept_remove(&mut self, uid: Uid, nick_low: &str) {
|
||||||
|
let removed = self
|
||||||
|
.users
|
||||||
|
.get_mut(&uid)
|
||||||
|
.map(|u| {
|
||||||
|
let before = u.accept.len();
|
||||||
|
u.accept.retain(|x| x != nick_low);
|
||||||
|
before != u.accept.len()
|
||||||
|
})
|
||||||
|
.unwrap_or(false);
|
||||||
|
if removed {
|
||||||
|
if let Some(c) = self.accepted_nicks.get_mut(nick_low) {
|
||||||
|
*c = c.saturating_sub(1);
|
||||||
|
if *c == 0 {
|
||||||
|
self.accepted_nicks.remove(nick_low);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub fn remove_user(&mut self, uid: Uid, reason: &str) {
|
pub fn remove_user(&mut self, uid: Uid, reason: &str) {
|
||||||
let Some(user) = self.users.remove(&uid) else {
|
let Some(user) = self.users.remove(&uid) else {
|
||||||
return;
|
return;
|
||||||
};
|
};
|
||||||
|
// the departing user's own accept list vanishes with them — drop its nicks
|
||||||
|
// from the reverse count
|
||||||
|
for n in &user.accept {
|
||||||
|
if let Some(c) = self.accepted_nicks.get_mut(n) {
|
||||||
|
*c = c.saturating_sub(1);
|
||||||
|
if *c == 0 {
|
||||||
|
self.accepted_nicks.remove(n);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
self.uuid_local.remove(&user.uuid);
|
self.uuid_local.remove(&user.uuid);
|
||||||
if user.registered {
|
if user.registered {
|
||||||
self.push_whowas(
|
self.push_whowas(
|
||||||
|
|
@ -699,11 +745,15 @@ impl Server {
|
||||||
self.nick_index.remove(&user.nick.to_ascii_lowercase());
|
self.nick_index.remove(&user.nick.to_ascii_lowercase());
|
||||||
// Scrub the departed nick from every +g callerid ACCEPT list, so a new
|
// Scrub the departed nick from every +g callerid ACCEPT list, so a new
|
||||||
// user grabbing this nick can't inherit its acceptance and bypass a gate.
|
// user grabbing this nick can't inherit its acceptance and bypass a gate.
|
||||||
|
// The reverse count lets us skip the O(users) scan unless someone actually
|
||||||
|
// accepted this nick (the common case).
|
||||||
let low = user.nick.to_ascii_lowercase();
|
let low = user.nick.to_ascii_lowercase();
|
||||||
|
if self.accepted_nicks.remove(&low).unwrap_or(0) > 0 {
|
||||||
for u in self.users.values_mut() {
|
for u in self.users.values_mut() {
|
||||||
u.accept.retain(|n| n != &low);
|
u.accept.retain(|n| n != &low);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
if user.registered {
|
if user.registered {
|
||||||
let line = format!(":{} QUIT :{reason}", user.prefix());
|
let line = format!(":{} QUIT :{reason}", user.prefix());
|
||||||
let mut seen: HashSet<Uid> = HashSet::default();
|
let mut seen: HashSet<Uid> = HashSet::default();
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue