diff --git a/crates/engine/src/events.rs b/crates/engine/src/events.rs index fbc1b9b6c..05433412c 100644 --- a/crates/engine/src/events.rs +++ b/crates/engine/src/events.rs @@ -127,12 +127,15 @@ pub fn topics(v: Option<&Value>) -> Result, String> { for t in list { let Some(s) = t.as_str() else { return Err("`topics` must be an array of strings".into()) }; match s { + // Each once: the list is scanned for every retained event under the log's lock, so + // a long list of repeats would multiply that work. + "comments" | "document" | "all" if out.iter().any(|t| t == s) => {} "comments" | "document" | "all" => out.push(s.to_string()), other => return Err(format!("unknown topic `{other}` (comments, document, all)")), } } - if out.is_empty() { - out.push("all".into()); + if out.is_empty() || out.iter().any(|t| t == "all") { + out = vec!["all".into()]; } Ok(out) } diff --git a/crates/engine/src/events_tests.rs b/crates/engine/src/events_tests.rs index 36b118a79..9cdef52ca 100644 --- a/crates/engine/src/events_tests.rs +++ b/crates/engine/src/events_tests.rs @@ -193,6 +193,10 @@ fn topics_parse_and_reject_unknown_names() { assert_eq!(events::topics(Some(&json!([]))).unwrap(), vec!["all".to_string()]); assert_eq!(events::topics(Some(&json!(["comments", "document"]))).unwrap(), vec!["comments".to_string(), "document".to_string()]); assert!(events::topics(Some(&json!(["nope"]))).is_err()); + // Repeats collapse (the list is scanned per event under the log's lock), and `all` is all. + let many: Vec<&str> = std::iter::repeat_n("comments", 10_000).chain(["document"]).collect(); + assert_eq!(events::topics(Some(&json!(many))).unwrap(), vec!["comments".to_string(), "document".to_string()]); + assert_eq!(events::topics(Some(&json!(["comments", "all", "document"]))).unwrap(), vec!["all".to_string()]); assert!(events::topics(Some(&json!("comments"))).is_err()); }