A snapshot's subscriber counts are bounded above (#342)
Store::snapshot clamped a count only from below, and LISTING sums each feed's counts across instances, cast to BIGINT. Two instances sending i64::MAX for a feed overflowed it (Postgres: bigint out of range; SQLite: integer overflow), and list() and popular() failed for every feed: the public pages 503, /v1/feeds and /v1/popular 500, until an admin dropped the instance. Counts are now clamped to a million. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -13,6 +13,9 @@ pub const ITEMS: usize = 20;
|
||||
/// How long a feed's failure goes unexplained: a site down for an hour is not called gone (ipx#143).
|
||||
const FLAG_AFTER_SECS: i64 = 24 * 3600;
|
||||
|
||||
/// The most subscribers one instance may say a feed has.
|
||||
pub const MAX_SUBSCRIBERS: i64 = 1_000_000;
|
||||
|
||||
pub struct Store {
|
||||
db: DatabaseConnection,
|
||||
}
|
||||
@@ -221,7 +224,9 @@ impl Store {
|
||||
continue;
|
||||
}
|
||||
// The same feed twice in one snapshot counts once, at its larger number.
|
||||
let n = e.subscribers.max(0);
|
||||
// Bounded above as well: two instances' i64::MAX summed past BIGINT, and every listing
|
||||
// and page failed until the instance was dropped (#342). No server has a million.
|
||||
let n = e.subscribers.clamp(0, MAX_SUBSCRIBERS);
|
||||
let path = e.category.as_deref().map(str::trim).filter(|c| !c.is_empty()).map(ipx_feed::category_path);
|
||||
let slot = keep.entry(id).or_insert((url, n, path.clone()));
|
||||
slot.1 = slot.1.max(n);
|
||||
@@ -548,6 +553,17 @@ pub(crate) mod tests {
|
||||
assert!(normalise("not a url").is_none());
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn no_count_an_instance_sends_breaks_the_listing() {
|
||||
let s = Store::scratch().await;
|
||||
s.snapshot("a", &[e("https://x.test/a.xml", i64::MAX)]).await.unwrap();
|
||||
s.snapshot("b", &[e("https://x.test/a.xml", i64::MAX)]).await.unwrap();
|
||||
fetch_all(&s).await;
|
||||
assert_eq!(s.list().await.unwrap()[0].subscribers, 2 * MAX_SUBSCRIBERS, "summed, not overflowed (#342)");
|
||||
assert_eq!(s.popular(10).await.unwrap()[0].subscribers, 2 * MAX_SUBSCRIBERS);
|
||||
assert!(s.instances().await.is_ok());
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn snapshots_replace_what_an_instance_sent_and_add_up_across_instances() {
|
||||
let s = Store::scratch().await;
|
||||
|
||||
Reference in New Issue
Block a user