Repository: tikv/tikv
File: components/raftstore/src/store/entry_storage.rs (Rust)

Code before:
    cache: VecDeque<(u64, u64)>, // (term, start_index)
    capacity: usize,
}

impl Default for TermCache {
    fn default() -> Self {
        TermCache {
            cache: Default::default(),
            capacity: 8, /* 8 is enough for the recent updated terms.
                          * Small capacity will make the extra memory
                          * cost negligible. */
        }
    }
}

impl TermCache {
    fn append(&mut self, index: u64, term: u64) {
        // Update the previous entry if it's in the same term.
        if let Some(first_term) = self.cache.front().map(|(first_term, _)| *first_term) {
            if first_term > term {
                // Still has enough space for caching this older term.
                if self.cache.len() < self.capacity {
                    self.cache.push_front((term, index));
                }
                return;
            }
            let pos = (term - first_term) as usize;
            if pos < self.cache.len() {
                let (t, idx) = self.cache[pos];
                assert_eq!(t, term);
                // Only updates if the inserted index is smaller than
                // the current start_index of this target term.
                if idx > index {
                    self.cache[pos].1 = index;
                }
                return;
            } else if self.cache.back().unwrap().0 + 1 < term {
                // Received a hopping term from other peers. It means that this
                // node might be partitioned from the majority. We need to clear the
                // cache to avoid returning stale terms.
                self.cache.clear();
            }
        }
        // New term.
        self.cache.push_back((term, index));
        if self.cache.len() > self.capacity {
            self.cache.pop_front();
        }
    }

    /// Push entries to the left of the cache.

Review comment (performance):
The calculation `(term - first_term) as usize` assumes terms are consecutive, but this may not always be true in Raft. If there's a gap between terms, this could lead to out-of-bounds access in the subsequent `self.cache[pos]` operation.
```suggestion
            }
            if term < first_term || term > self.cache.back().unwrap().0 {
                // Term is outside the range of cached terms. Clear the cache to avoid
                // stale or invalid data.
                self.cache.clear();
                self.cache.push_back((term, index));
                if self.cache.len() > self.capacity {
                    self.cache.pop_front();
                }
                return;
            }
```

Code after:
struct TermCache {
    cache: VecDeque<(u64, u64)>, // (term, start_index)
    capacity: usize,
}

impl Default for TermCache {
    fn default() -> Self {
        TermCache {
            cache: Default::default(),
            capacity: 8, /* 8 is enough for the recent updated terms.
                          * Small capacity will make the extra memory
                          * cost negligible. */
        }
    }
}

impl TermCache {
    fn append(&mut self, index: u64, term: u64) {
        // Check if we can update an existing entry or insert into the cache
        if let Some(first_term) = self.cache.front().map(|(first_term, _)| *first_term) {
            match first_term.cmp(&(term + 1)) {
                Greater => {
                    // Skip terms with gaps, as they represent stale/unfresh information.
                    return;
                }
                Equal => {
                    // Insert older term at the front if there's available space.
                    if self.cache.len() < self.capacity {
                        self.cache.push_front((term, index));
                    }
                    return;
                }
                Less => {
                    // Calculate position of the term in the cache (terms are stored sequentially)
                    let pos = (term - first_term) as usize;
                    if pos < self.cache.len() {
                        let (cached_term, cached_start_idx) = self.cache[pos];
                        assert_eq!(cached_term, term);

                        // Update start index only if the new index is smaller (extends the term
                        // range)
                        if cached_start_idx > index {
                            self.cache[pos].1 = index;
                        }
                        return;
                    } else if self.cache.back().unwrap().0 + 1 < term {
                        // Detected a term jump indicating potential network partition.
                        // Clear the cache to prevent serving stale term information.
                        self.cache.clear();
                    }
                }