Skip to content

Commit 4e066dd

Browse files
committed
Fix various performance issues
This commit includes various fixes that I have found working on bouncer-networks. It includes several performance benefits—such as changing `Server` to be an `Arc<str>` which removes much of the copying overhead. I have also made sure that there is only one copy of the server config in the `Halloy` struct, which was previously duplicated for seemingly no reason. Still, several performance issues go un-addressed. such as the duplication of server configs between threads (should this also be an `Arc<config>`?) and the copying of `client` structs. I have also made some changes to the client. The capability negotiation process has been streamlined, and I've eliminated some branching with preference toward pattern matching. I've also fixed a slight error with halloy, making sure that it detects the end of registration correctly (matching other clients). I have also made changes to the `Cargo.toml` in the `data` crate. This is to fix some compilation issues when building that crate in isolation. I would like this fixed so that it'd be easier to run `data` crate tests in isolation. Ideally I think that each of the sub-crates should be tested individually in CI—but that's out of scope for this PR. In my opinion, this resolves squidowl#640
1 parent 4bf0c5d commit 4e066dd

File tree

5 files changed

+120
-144
lines changed

5 files changed

+120
-144
lines changed

data/Cargo.toml

+4-4
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ flate2 = "1.0"
1717
futures = "0.3.21"
1818
hex = "0.4.3"
1919
iced_core = "0.14.0-dev"
20-
log = "0.4.16"
20+
log = { version = "0.4.16", features = ['std'] }
2121
palette = "0.7.4"
2222
rand = "0.8.4"
2323
rand_chacha = "0.3.0"
@@ -27,8 +27,8 @@ sha2 = "0.10.8"
2727
toml = "0.8.11"
2828
thiserror = "1.0.30"
2929
reqwest = { version = "0.12", features = ["json"] }
30-
tokio = { version = "1.0", features = ["io-util"] }
31-
tokio-stream = { version = "0.1", features = ["time"] }
30+
tokio = { version = "1.0", features = ["io-util", "fs"] }
31+
tokio-stream = { version = "0.1", features = ["time", "fs"] }
3232
itertools = "0.12.1"
3333
timeago = "0.4.2"
3434
url = { version = "2.5.0", features = ["serde"] }
@@ -46,4 +46,4 @@ path = "../irc"
4646

4747
[dependencies.serde]
4848
version = "1.0"
49-
features = ["derive"]
49+
features = ["derive", "rc"]

data/src/client.rs

+65-107
Original file line numberDiff line numberDiff line change
@@ -155,6 +155,20 @@ impl fmt::Debug for Client {
155155
f.debug_struct("Client").finish()
156156
}
157157
}
158+
const UNCONDITIONAL_CAPS: &[&str] = &[
159+
"account-notify",
160+
"away-notify",
161+
"batch",
162+
"chghost",
163+
"draft/read-marker",
164+
"extended-monitor",
165+
"invite-notify",
166+
"labeled-response",
167+
"message-tags",
168+
"multi-prefix",
169+
"server-time",
170+
"userhost-in-names",
171+
];
158172

159173
impl Client {
160174
pub fn new(
@@ -646,92 +660,52 @@ impl Client {
646660
)]);
647661
}
648662
}
649-
Command::CAP(_, sub, a, b) if sub == "LS" => {
650-
let (caps, asterisk) = match (a, b) {
651-
(Some(caps), None) => (caps, None),
652-
(Some(asterisk), Some(caps)) => (caps, Some(asterisk)),
653-
// Unreachable
654-
(None, None) | (None, Some(_)) => return Ok(vec![]),
655-
};
656-
663+
Command::CAP(_, sub, Some(_asterisk), Some(caps)) if sub == "LS" => {
664+
self.listed_caps.extend(caps.split(' ').map(String::from));
665+
}
666+
// Finished with LS
667+
Command::CAP(_, sub, Some(caps), None) if sub == "LS" => {
657668
self.listed_caps.extend(caps.split(' ').map(String::from));
658669

659-
// Finished
660-
if asterisk.is_none() {
661-
let mut requested = vec![];
670+
let contains = |s : &str| self.listed_caps.iter().any(|cap| cap == s);
662671

663-
let contains = |s| self.listed_caps.iter().any(|cap| cap == s);
672+
let mut requested: Vec<&str> = UNCONDITIONAL_CAPS
673+
.iter()
674+
.copied()
675+
.filter(|c| contains(c))
676+
.collect();
664677

665-
if contains("invite-notify") {
666-
requested.push("invite-notify");
667-
}
668-
if contains("userhost-in-names") {
669-
requested.push("userhost-in-names");
670-
}
671-
if contains("away-notify") {
672-
requested.push("away-notify");
673-
}
674-
if contains("message-tags") {
675-
requested.push("message-tags");
676-
}
677-
if contains("server-time") {
678-
requested.push("server-time");
679-
}
680-
if contains("chghost") {
681-
requested.push("chghost");
682-
}
683-
if contains("extended-monitor") {
684-
requested.push("extended-monitor");
685-
}
686-
if contains("account-notify") {
687-
requested.push("account-notify");
678+
if contains("account-notify") && contains("extended-join") {
679+
requested.push("extended-join");
680+
}
681+
if contains("batch") && contains("draft/chathistory") {
682+
// We require batch for our chathistory support
683+
requested.push("draft/chathistory");
688684

689-
if contains("extended-join") {
690-
requested.push("extended-join");
691-
}
685+
if contains("draft/event-playback") {
686+
requested.push("draft/event-playback");
692687
}
693-
if contains("batch") {
694-
requested.push("batch");
688+
}
689+
// We require labeled-response so we can properly tag echo-messages
690+
if contains("labeled-response") && contains("echo-message") {
691+
requested.push("echo-message");
692+
}
693+
if self.listed_caps.iter().any(|cap| cap.starts_with("sasl")) {
694+
requested.push("sasl");
695+
}
695696

696-
// We require batch for our chathistory support
697-
if contains("draft/chathistory") {
698-
requested.push("draft/chathistory");
699697

700-
if contains("draft/event-playback") {
701-
requested.push("draft/event-playback");
702-
}
703-
}
704-
}
705-
if contains("labeled-response") {
706-
requested.push("labeled-response");
707-
708-
// We require labeled-response so we can properly tag echo-messages
709-
if contains("echo-message") {
710-
requested.push("echo-message");
711-
}
712-
}
713-
if self.listed_caps.iter().any(|cap| cap.starts_with("sasl")) {
714-
requested.push("sasl");
715-
}
716-
if contains("multi-prefix") {
717-
requested.push("multi-prefix");
718-
}
719-
if contains("draft/read-marker") {
720-
requested.push("draft/read-marker");
721-
}
722-
723-
if !requested.is_empty() {
724-
// Request
725-
self.registration_step = RegistrationStep::Req;
698+
if !requested.is_empty() {
699+
// Request
700+
self.registration_step = RegistrationStep::Req;
726701

727-
for message in group_capability_requests(&requested) {
728-
self.handle.try_send(message)?;
729-
}
730-
} else {
731-
// If none requested, end negotiation
732-
self.registration_step = RegistrationStep::End;
733-
self.handle.try_send(command!("CAP", "END"))?;
702+
for message in group_capability_requests(&requested) {
703+
self.handle.try_send(message)?;
734704
}
705+
} else {
706+
// If none requested, end negotiation
707+
self.registration_step = RegistrationStep::End;
708+
self.handle.try_send(command!("CAP", "END"))?;
735709
}
736710
}
737711
Command::CAP(_, sub, a, b) if sub == "ACK" => {
@@ -791,33 +765,16 @@ impl Client {
791765

792766
let new_caps = caps.split(' ').map(String::from).collect::<Vec<String>>();
793767

794-
let mut requested = vec![];
795-
796768
let newly_contains = |s| new_caps.iter().any(|cap| cap == s);
797769

770+
let mut requested: Vec<&str> = UNCONDITIONAL_CAPS
771+
.iter()
772+
.copied()
773+
.filter(|c| newly_contains(*c))
774+
.collect();
775+
798776
let contains = |s| self.listed_caps.iter().any(|cap| cap == s);
799777

800-
if newly_contains("invite-notify") {
801-
requested.push("invite-notify");
802-
}
803-
if newly_contains("userhost-in-names") {
804-
requested.push("userhost-in-names");
805-
}
806-
if newly_contains("away-notify") {
807-
requested.push("away-notify");
808-
}
809-
if newly_contains("message-tags") {
810-
requested.push("message-tags");
811-
}
812-
if newly_contains("server-time") {
813-
requested.push("server-time");
814-
}
815-
if newly_contains("chghost") {
816-
requested.push("chghost");
817-
}
818-
if newly_contains("extended-monitor") {
819-
requested.push("extended-monitor");
820-
}
821778
if contains("account-notify") || newly_contains("account-notify") {
822779
if newly_contains("account-notify") {
823780
requested.push("account-notify");
@@ -851,12 +808,6 @@ impl Client {
851808
requested.push("echo-message");
852809
}
853810
}
854-
if newly_contains("multi-prefix") {
855-
requested.push("multi-prefix");
856-
}
857-
if newly_contains("draft/read-marker") {
858-
requested.push("draft/read-marker");
859-
}
860811

861812
if !requested.is_empty() {
862813
for message in group_capability_requests(&requested) {
@@ -957,7 +908,7 @@ impl Client {
957908
)]);
958909
}
959910
dcc::Command::Unsupported(command) => {
960-
bail!("Unsupported DCC command: {command}",);
911+
bail!("Unsupported DCC command: {command}");
961912
}
962913
}
963914
} else {
@@ -1122,11 +1073,18 @@ impl Client {
11221073
// Updated actual nick
11231074
let nick = ok!(args.first());
11241075
self.resolved_nick = Some(nick.to_string());
1076+
}
1077+
// end of registration (including ISUPPORT) is indicated by either RPL_ENDOFMOTD or
1078+
// ERR_NOMOTD: https://modern.ircdocs.horse/#connection-registration
1079+
Command::Numeric(RPL_ENDOFMOTD, _args) | Command::Numeric(ERR_NOMOTD, _args) => {
1080+
let Some(nick) = self.resolved_nick.as_deref() else {
1081+
bail!("Error, registration completed without RPL_WELCOME completed");
1082+
};
11251083

11261084
// Send nick password & ghost
11271085
if let Some(nick_pass) = self.config.nick_password.as_ref() {
11281086
// Try ghost recovery if we couldn't claim our nick
1129-
if self.config.should_ghost && nick != &self.config.nickname {
1087+
if self.config.should_ghost && nick != self.config.nickname {
11301088
for sequence in &self.config.ghost_sequence {
11311089
self.handle.try_send(command!(
11321090
"PRIVMSG",

data/src/server.rs

+17-4
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
use std::collections::BTreeMap;
2+
use std::sync::Arc;
23
use std::{fmt, str};
34
use tokio::fs;
45
use tokio::process::Command;
@@ -14,23 +15,35 @@ use crate::config::Error;
1415
pub type Handle = Sender<proto::Message>;
1516

1617
#[derive(Debug, Clone, Hash, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)]
17-
pub struct Server(String);
18+
#[serde(transparent)]
19+
pub struct Server {
20+
name: Arc<str>,
21+
}
22+
23+
impl Server {
24+
pub fn name(&self) -> &str {
25+
&self.name
26+
}
27+
28+
}
1829

1930
impl From<&str> for Server {
2031
fn from(value: &str) -> Self {
21-
Server(value.to_string())
32+
Server {
33+
name: Arc::from(value),
34+
}
2235
}
2336
}
2437

2538
impl fmt::Display for Server {
2639
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
27-
self.0.fmt(f)
40+
self.name.fmt(f)
2841
}
2942
}
3043

3144
impl AsRef<str> for Server {
3245
fn as_ref(&self) -> &str {
33-
&self.0
46+
self.name()
3447
}
3548
}
3649

irc/proto/src/command.rs

+8-5
Original file line numberDiff line numberDiff line change
@@ -149,6 +149,9 @@ impl Command {
149149
params.next()
150150
};
151151
}
152+
macro_rules! remaining {
153+
() => { params.collect() };
154+
}
152155

153156
match tag.as_str() {
154157
"CAP" if len > 0 => {
@@ -183,7 +186,7 @@ impl Command {
183186
"STATS" if len > 0 => STATS(req!(), opt!()),
184187
"HELP" => HELP(opt!()),
185188
"INFO" => INFO,
186-
"MODE" if len > 0 => MODE(req!(), opt!(), Some(params.collect())),
189+
"MODE" if len > 0 => MODE(req!(), opt!(), Some(remaining!())),
187190
"PRIVMSG" if len > 1 => PRIVMSG(req!(), req!()),
188191
"NOTICE" if len > 1 => NOTICE(req!(), req!()),
189192
"WHO" if len > 0 => WHO(req!(), opt!(), opt!()),
@@ -201,11 +204,11 @@ impl Command {
201204
"SQUIT" if len > 1 => SQUIT(req!(), req!()),
202205
"AWAY" => AWAY(opt!()),
203206
"LINKS" => LINKS,
204-
"USERHOST" => USERHOST(params.collect()),
207+
"USERHOST" => USERHOST(remaining!()),
205208
"WALLOPS" if len > 0 => WALLOPS(req!()),
206209
"ACCOUNT" if len > 0 => ACCOUNT(req!()),
207-
"BATCH" if len > 0 => BATCH(req!(), params.collect()),
208-
"CHATHISTORY" if len > 0 => CHATHISTORY(req!(), params.collect()),
210+
"BATCH" if len > 0 => BATCH(req!(), remaining!()),
211+
"CHATHISTORY" if len > 0 => CHATHISTORY(req!(), remaining!()),
209212
"CHGHOST" if len > 1 => CHGHOST(req!(), req!()),
210213
"CNOTICE" if len > 2 => CNOTICE(req!(), req!(), req!()),
211214
"CPRIVMSG" if len > 2 => CPRIVMSG(req!(), req!(), req!()),
@@ -214,7 +217,7 @@ impl Command {
214217
"MONITOR" if len > 0 => MONITOR(req!(), opt!()),
215218
"TAGMSG" if len > 0 => TAGMSG(req!()),
216219
"USERIP" if len > 0 => USERIP(req!()),
217-
_ => Self::Unknown(tag, params.collect()),
220+
_ => Self::Unknown(tag, remaining!()),
218221
}
219222
}
220223

0 commit comments

Comments
 (0)