fix(notify): report cursor gap when history is evicted instead of silently dropping events (#4446)

`LiveEventHistory::snapshot_since` resumed from the oldest retained event
whenever a consumer's cursor pointed before it, without signaling that the
in-between events had been evicted from the ring buffer. Cursor-based
consumers therefore assumed the returned batch was contiguous with their
cursor and silently lost events.

Add a `gap` flag to `LiveEventBatch`, set when the consumer's cursor is
older than the oldest retained sequence, so consumers can detect the loss
and trigger a full re-sync or alert. Add a regression test that fills and
evicts past the ring-buffer capacity and asserts a lagging cursor reports
`gap = true` (and that contiguous/caught-up cursors do not).

Refs rustfs/backlog#969
This commit is contained in:
Zhengchao An
2026-07-08 17:02:36 +08:00
committed by GitHub
parent fefa70b31e
commit 686b39bbef
2 changed files with 48 additions and 1 deletions
+9
View File
@@ -47,6 +47,12 @@ pub struct LiveEventBatch {
pub events: Vec<Arc<Event>>,
pub next_sequence: u64,
pub truncated: bool,
/// Set when the consumer's cursor is older than the oldest event still
/// retained in the ring buffer: events between the cursor and the oldest
/// retained sequence were evicted and can never be delivered. Consumers
/// must treat a `gap` as lost events (e.g. trigger a full re-sync/alert)
/// instead of assuming the returned batch is contiguous with their cursor.
pub gap: bool,
}
/// Notify the system of monitoring indicators
@@ -438,6 +444,7 @@ mod tests {
assert_eq!(batch.next_sequence, 2);
assert!(!batch.truncated);
assert!(!batch.gap);
assert_eq!(batch.events.len(), 1);
assert_eq!(batch.events[0].s3.object.key, "two");
}
@@ -452,6 +459,7 @@ mod tests {
assert_eq!(batch.next_sequence, 1);
assert!(batch.truncated);
assert!(!batch.gap);
assert_eq!(batch.events.len(), 1);
assert_eq!(batch.events[0].s3.object.key, "one");
}
@@ -473,5 +481,6 @@ mod tests {
assert_eq!(batch.events[0].s3.object.key, "object");
assert_eq!(batch.next_sequence, 1);
assert!(!batch.truncated);
assert!(!batch.gap);
}
}