// Copyright 2025 The NATS Authors // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package server import ( "bytes" "time" ) // SDMMeta holds pending/proposed data for subject delete markers or message removals. type SDMMeta struct { totals map[string]uint64 pending map[uint64]SDMBySeq } // SDMBySeq holds data for a message with a specific sequence. type SDMBySeq struct { last bool // Whether the message for this sequence was the last for this subject. ts int64 // Last timestamp we proposed a removal/sdm. } func newSDMMeta() *SDMMeta { return &SDMMeta{ totals: make(map[string]uint64, 1), pending: make(map[uint64]SDMBySeq, 1), } } // isSubjectDeleteMarker returns whether the headers indicate this message is a subject delete marker. // Either it's a usual marker with JSMarkerReason, or it's a KV Purge marker as the KVOperation. func isSubjectDeleteMarker(hdr []byte) bool { return len(sliceHeader(JSMarkerReason, hdr)) != 0 || bytes.Equal(sliceHeader(KVOperation, hdr), KVOperationValuePurge) } // empty clears all data. func (sdm *SDMMeta) empty() { if sdm == nil { return } clear(sdm.totals) clear(sdm.pending) } // trackPending caches the given seq and subj and whether it's the last message for that subject. func (sdm *SDMMeta) trackPending(seq uint64, subj string, last bool) bool { if p, ok := sdm.pending[seq]; ok { return p.last } sdm.pending[seq] = SDMBySeq{last, time.Now().UnixNano()} sdm.totals[subj]++ return last } // removeSeqAndSubject clears the seq and subj from the cache. func (sdm *SDMMeta) removeSeqAndSubject(seq uint64, subj string) { if sdm == nil { return } if _, ok := sdm.pending[seq]; ok { delete(sdm.pending, seq) if msgs, ok := sdm.totals[subj]; ok { if msgs <= 1 { delete(sdm.totals, subj) } else { sdm.totals[subj] = msgs - 1 } } } }