Files
mesh-controller/vendor/github.com/nats-io/nats-server/v2/server/memstore.go
T
jochen be92762969 Give every test a bus of its own, at the release the mesh runs (hq ADR 0237)
The live tests reached one shared bus and assert, read and remove the mesh's own objects by
their fixed names, so packages run in parallel deleted what each other read and the suite
passed only one package at a time; a red suite read as noise. internal/testbus starts a server
per test, linked in at the nats-server release go.mod pins, and a test holds that pin to the
catalogue's bus image and to the facts snapshot's bus when there is one, so the tests never run
a bus the mesh does not. The waiter test read a timing (the most connections held at one look)
and now reads the state it means (the fewest held across the wait). make check runs the packages
in parallel under the race detector, with a timeout.
2026-10-06 21:17:02 +02:00

2543 lines
64 KiB
Go

// Copyright 2019-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 (
crand "crypto/rand"
"encoding/binary"
"fmt"
"math"
"slices"
"sync"
"time"
"github.com/nats-io/nats-server/v2/server/ats"
"github.com/nats-io/nats-server/v2/server/avl"
"github.com/nats-io/nats-server/v2/server/gsl"
"github.com/nats-io/nats-server/v2/server/stree"
"github.com/nats-io/nats-server/v2/server/thw"
)
// TODO(dlc) - This is a fairly simplistic approach but should do for now.
type memStore struct {
mu sync.RWMutex
cfg StreamConfig
state StreamState
msgs map[uint64]*StoreMsg
fss *stree.SubjectTree[SimpleState]
dmap avl.SequenceSet
maxp int64
scb StorageUpdateHandler
rmcb StorageRemoveMsgHandler
sdmcb SubjectDeleteMarkerUpdateHandler
ageChk *time.Timer // Timer to expire messages.
ageChkRun bool // Whether message expiration is currently running.
ageChkTime int64 // When the message expiration is scheduled to run.
consumers int
receivedAny bool
ttls *thw.HashWheel
sdm *SDMMeta
}
func newMemStore(cfg *StreamConfig) (*memStore, error) {
if cfg == nil {
return nil, fmt.Errorf("config required")
}
if cfg.Storage != MemoryStorage {
return nil, fmt.Errorf("memStore requires memory storage type in config")
}
ms := &memStore{
msgs: make(map[uint64]*StoreMsg),
fss: stree.NewSubjectTree[SimpleState](),
maxp: cfg.MaxMsgsPer,
cfg: *cfg,
}
// Only create a THW if we're going to allow TTLs.
if cfg.AllowMsgTTL {
ms.ttls = thw.NewHashWheel()
}
if cfg.FirstSeq > 0 {
if _, err := ms.purge(cfg.FirstSeq); err != nil {
return nil, err
}
}
// Register with access time service.
ats.Register()
return ms, nil
}
func (ms *memStore) UpdateConfig(cfg *StreamConfig) error {
if cfg == nil {
return fmt.Errorf("config required")
}
if cfg.Storage != MemoryStorage {
return fmt.Errorf("memStore requires memory storage type in config")
}
ms.mu.Lock()
ms.cfg = *cfg
// Create or delete the THW if needed.
if cfg.AllowMsgTTL && ms.ttls == nil {
ms.recoverTTLState()
} else if !cfg.AllowMsgTTL && ms.ttls != nil {
ms.ttls = nil
}
// Limits checks and enforcement.
ms.enforceMsgLimit()
ms.enforceBytesLimit()
// Do age timers.
if ms.ageChk == nil && ms.cfg.MaxAge != 0 {
ms.startAgeChk()
}
if ms.ageChk != nil && ms.cfg.MaxAge == 0 {
ms.ageChk.Stop()
ms.ageChk = nil
ms.ageChkTime = 0
}
// Make sure to update MaxMsgsPer
if cfg.MaxMsgsPer < -1 {
cfg.MaxMsgsPer = -1
}
maxp := ms.maxp
ms.maxp = cfg.MaxMsgsPer
// If the value is smaller, or was unset before, we need to enforce that.
if ms.maxp > 0 && (maxp == 0 || ms.maxp < maxp) {
lm := uint64(ms.maxp)
ms.fss.IterFast(func(subj []byte, ss *SimpleState) bool {
if ss.Msgs > lm {
ms.enforcePerSubjectLimit(bytesToString(subj), ss)
}
return true
})
}
ms.mu.Unlock()
if cfg.MaxAge != 0 || cfg.AllowMsgTTL {
ms.expireMsgs()
}
return nil
}
// Lock should be held.
func (ms *memStore) recoverTTLState() {
ms.ttls = thw.NewHashWheel()
if ms.state.Msgs == 0 {
return
}
var (
seq uint64
smv StoreMsg
sm *StoreMsg
)
defer ms.resetAgeChk(0)
for sm, seq, _ = ms.loadNextMsgLocked(fwcs, true, 0, &smv); sm != nil; sm, seq, _ = ms.loadNextMsgLocked(fwcs, true, seq+1, &smv) {
if len(sm.hdr) == 0 {
continue
}
if ttl, _ := getMessageTTL(sm.hdr); ttl > 0 {
expires := time.Duration(sm.ts) + (time.Second * time.Duration(ttl))
ms.ttls.Add(seq, int64(expires))
}
}
}
// Stores a raw message with expected sequence number and timestamp.
// Lock should be held.
func (ms *memStore) storeRawMsg(subj string, hdr, msg []byte, seq uint64, ts, ttl int64) error {
if ms.msgs == nil {
return ErrStoreClosed
}
// Tracking by subject.
var ss *SimpleState
var asl bool
if len(subj) > 0 {
var ok bool
if ss, ok = ms.fss.Find(stringToBytes(subj)); ok {
asl = ms.maxp > 0 && ss.Msgs >= uint64(ms.maxp)
}
}
// Check if we are discarding new messages when we reach the limit.
if ms.cfg.Discard == DiscardNew {
if asl && ms.cfg.DiscardNewPer {
return ErrMaxMsgsPerSubject
}
// If we are discard new and limits policy and clustered, we do the enforcement
// above and should not disqualify the message here since it could cause replicas to drift.
if ms.cfg.Retention == LimitsPolicy || ms.cfg.Replicas == 1 {
if ms.cfg.MaxMsgs > 0 && ms.state.Msgs >= uint64(ms.cfg.MaxMsgs) {
// If we are tracking max messages per subject and are at the limit we will replace, so this is ok.
if !asl {
return ErrMaxMsgs
}
}
if ms.cfg.MaxBytes > 0 && ms.state.Bytes+memStoreMsgSize(subj, hdr, msg) >= uint64(ms.cfg.MaxBytes) {
if !asl {
return ErrMaxBytes
}
// If we are here we are at a subject maximum, need to determine if dropping last message gives us enough room.
if ss.firstNeedsUpdate || ss.lastNeedsUpdate {
ms.recalculateForSubj(subj, ss)
}
sm, ok := ms.msgs[ss.First]
if !ok || memStoreMsgSize(sm.subj, sm.hdr, sm.msg) < memStoreMsgSize(subj, hdr, msg) {
return ErrMaxBytes
}
}
}
}
if seq != ms.state.LastSeq+1 {
if seq > 0 {
return ErrSequenceMismatch
}
seq = ms.state.LastSeq + 1
}
// Adjust first if needed.
now := time.Unix(0, ts).UTC()
if ms.state.Msgs == 0 {
ms.state.FirstSeq = seq
ms.state.FirstTime = now
}
// Make copies
// TODO(dlc) - Maybe be smarter here.
if len(msg) > 0 {
msg = copyBytes(msg)
}
if len(hdr) > 0 {
hdr = copyBytes(hdr)
}
// FIXME(dlc) - Could pool at this level?
sm := &StoreMsg{subj, nil, nil, make([]byte, 0, len(hdr)+len(msg)), seq, ts}
sm.buf = append(sm.buf, hdr...)
sm.buf = append(sm.buf, msg...)
if len(hdr) > 0 {
sm.hdr = sm.buf[:len(hdr)]
}
sm.msg = sm.buf[len(hdr):]
ms.msgs[seq] = sm
ms.state.Msgs++
ms.state.Bytes += memStoreMsgSize(subj, hdr, msg)
ms.state.LastSeq = seq
ms.state.LastTime = now
// Track per subject.
if len(subj) > 0 {
if ss != nil {
ss.Msgs++
ss.Last = seq
ss.lastNeedsUpdate = false
// Check per subject limits.
if ms.maxp > 0 && ss.Msgs > uint64(ms.maxp) {
ms.enforcePerSubjectLimit(subj, ss)
}
} else {
ms.fss.Insert([]byte(subj), SimpleState{Msgs: 1, First: seq, Last: seq})
}
}
// Limits checks and enforcement.
ms.enforceMsgLimit()
ms.enforceBytesLimit()
// Per-message TTL.
if ms.ttls != nil && ttl > 0 {
expires := time.Duration(ts) + (time.Second * time.Duration(ttl))
ms.ttls.Add(seq, int64(expires))
}
// Check if we have and need the age expiration timer running.
switch {
case ms.ttls != nil && ttl > 0:
ms.resetAgeChk(0)
case ms.ageChk == nil && (ms.cfg.MaxAge > 0 || ms.ttls != nil):
ms.startAgeChk()
}
return nil
}
// StoreRawMsg stores a raw message with expected sequence number and timestamp.
func (ms *memStore) StoreRawMsg(subj string, hdr, msg []byte, seq uint64, ts, ttl int64) error {
ms.mu.Lock()
err := ms.storeRawMsg(subj, hdr, msg, seq, ts, ttl)
cb := ms.scb
// Check if first message timestamp requires expiry
// sooner than initial replica expiry timer set to MaxAge when initializing.
if !ms.receivedAny && ms.cfg.MaxAge != 0 && ts > 0 {
ms.receivedAny = true
// Calculate duration when the next expireMsgs should be called.
ms.resetAgeChk(int64(time.Millisecond) * 50)
}
ms.mu.Unlock()
if err == nil && cb != nil {
cb(1, int64(memStoreMsgSize(subj, hdr, msg)), seq, subj)
}
return err
}
// Store stores a message.
func (ms *memStore) StoreMsg(subj string, hdr, msg []byte, ttl int64) (uint64, int64, error) {
ms.mu.Lock()
seq, ts := ms.state.LastSeq+1, time.Now().UnixNano()
err := ms.storeRawMsg(subj, hdr, msg, seq, ts, ttl)
cb := ms.scb
ms.mu.Unlock()
if err != nil {
seq, ts = 0, 0
} else if cb != nil {
cb(1, int64(memStoreMsgSize(subj, hdr, msg)), seq, subj)
}
return seq, ts, err
}
// SkipMsg will use the next sequence number but not store anything.
func (ms *memStore) SkipMsg(seq uint64) (uint64, error) {
// Grab time.
now := time.Unix(0, ats.AccessTime()).UTC()
ms.mu.Lock()
defer ms.mu.Unlock()
// Check sequence matches our last sequence.
if seq != ms.state.LastSeq+1 {
if seq > 0 {
return 0, ErrSequenceMismatch
}
seq = ms.state.LastSeq + 1
}
ms.state.LastSeq = seq
ms.state.LastTime = now
if ms.state.Msgs == 0 {
ms.state.FirstSeq = seq + 1
ms.state.FirstTime = time.Time{}
} else {
ms.dmap.Insert(seq)
}
return seq, nil
}
// Skip multiple msgs.
func (ms *memStore) SkipMsgs(seq uint64, num uint64) error {
// Grab time.
now := time.Unix(0, ats.AccessTime()).UTC()
ms.mu.Lock()
defer ms.mu.Unlock()
// Check sequence matches our last sequence.
if seq != ms.state.LastSeq+1 {
if seq > 0 {
return ErrSequenceMismatch
}
seq = ms.state.LastSeq + 1
}
lseq := seq + num - 1
ms.state.LastSeq = lseq
ms.state.LastTime = now
if ms.state.Msgs == 0 {
ms.state.FirstSeq, ms.state.FirstTime = lseq+1, time.Time{}
} else {
for ; seq <= lseq; seq++ {
ms.dmap.Insert(seq)
}
}
return nil
}
// RegisterStorageUpdates registers a callback for updates to storage changes.
// It will present number of messages and bytes as a signed integer and an
// optional sequence number of the message if a single.
func (ms *memStore) RegisterStorageUpdates(cb StorageUpdateHandler) {
ms.mu.Lock()
ms.scb = cb
ms.mu.Unlock()
}
// RegisterStorageRemoveMsg registers a callback to remove messages.
// Replicated streams should propose removals, R1 can remove inline.
func (ms *memStore) RegisterStorageRemoveMsg(cb StorageRemoveMsgHandler) {
ms.mu.Lock()
ms.rmcb = cb
ms.mu.Unlock()
}
// RegisterSubjectDeleteMarkerUpdates registers a callback for updates to new subject delete markers.
func (ms *memStore) RegisterSubjectDeleteMarkerUpdates(cb SubjectDeleteMarkerUpdateHandler) {
ms.mu.Lock()
ms.sdmcb = cb
ms.mu.Unlock()
}
// GetSeqFromTime looks for the first sequence number that has the message
// with >= timestamp.
func (ms *memStore) GetSeqFromTime(t time.Time) uint64 {
ts := t.UnixNano()
ms.mu.RLock()
defer ms.mu.RUnlock()
if len(ms.msgs) == 0 {
return ms.state.LastSeq + 1
}
if ts <= ms.msgs[ms.state.FirstSeq].ts {
return ms.state.FirstSeq
}
// LastSeq is not guaranteed to be present since last does not go backwards.
var lmsg *StoreMsg
for lseq := ms.state.LastSeq; lseq > ms.state.FirstSeq; lseq-- {
if lmsg = ms.msgs[lseq]; lmsg != nil {
break
}
}
if lmsg == nil {
return ms.state.LastSeq + 1
}
last := lmsg.ts
if ts == last {
return lmsg.seq
}
if ts > last {
return ms.state.LastSeq + 1
}
var (
cts int64
cseq uint64
off uint64
)
// Using a binary search, but need to be aware of interior deletes.
fseq := ms.state.FirstSeq
lseq := ms.state.LastSeq
seq := lseq + 1
loop:
for fseq <= lseq {
mid := fseq + (lseq-fseq)/2
off = 0
// Potentially skip over gaps. We keep the original middle but keep track of a
// potential delete range with an offset.
for {
msg := ms.msgs[mid+off]
if msg == nil {
off++
if mid+off <= lseq {
continue
} else {
// Continue search to the left. Purposely ignore the skipped deletes here.
lseq = mid - 1
continue loop
}
}
cts = msg.ts
cseq = msg.seq
break
}
if cts >= ts {
seq = cseq
if mid == fseq {
break
}
// Continue search to the left.
lseq = mid - 1
} else {
// Continue search to the right (potentially skipping over interior deletes).
fseq = mid + off + 1
}
}
return seq
}
// FilteredState will return the SimpleState associated with the filtered subject and a proposed starting sequence.
func (ms *memStore) FilteredState(sseq uint64, subj string) SimpleState {
// This needs to be a write lock, as filteredStateLocked can
// mutate the per-subject state.
ms.mu.Lock()
defer ms.mu.Unlock()
return ms.filteredStateLocked(sseq, subj, false)
}
func (ms *memStore) filteredStateLocked(sseq uint64, filter string, lastPerSubject bool) SimpleState {
if sseq < ms.state.FirstSeq {
sseq = ms.state.FirstSeq
}
// If past the end no results.
if sseq > ms.state.LastSeq {
return SimpleState{}
}
if filter == _EMPTY_ {
filter = fwcs
}
isAll := filter == fwcs
// First check if we can optimize this part.
// This means we want all and the starting sequence was before this block.
if isAll && sseq <= ms.state.FirstSeq {
total := ms.state.Msgs
if lastPerSubject {
total = uint64(ms.fss.Size())
}
return SimpleState{
Msgs: total,
First: ms.state.FirstSeq,
Last: ms.state.LastSeq,
}
}
_tsa, _fsa := [32]string{}, [32]string{}
tsa, fsa := _tsa[:0], _fsa[:0]
wc := subjectHasWildcard(filter)
if wc {
fsa = tokenizeSubjectIntoSlice(fsa[:0], filter)
}
// 1. See if we match any subs from fss.
// 2. If we match and the sseq is past ss.Last then we can use meta only.
// 3. If we match we need to do a partial, break and clear any totals and do a full scan like num pending.
isMatch := func(subj string) bool {
if isAll {
return true
}
if !wc {
return subj == filter
}
tsa = tokenizeSubjectIntoSlice(tsa[:0], subj)
return isSubsetMatchTokenized(tsa, fsa)
}
var ss SimpleState
update := func(fss *SimpleState) {
msgs, first, last := fss.Msgs, fss.First, fss.Last
if lastPerSubject {
msgs, first = 1, last
}
ss.Msgs += msgs
if ss.First == 0 || first < ss.First {
ss.First = first
}
if last > ss.Last {
ss.Last = last
}
}
var havePartial bool
var totalSkipped uint64
// We will track start and end sequences as we go.
ms.fss.Match(stringToBytes(filter), func(subj []byte, fss *SimpleState) {
if fss.firstNeedsUpdate || fss.lastNeedsUpdate {
ms.recalculateForSubj(bytesToString(subj), fss)
}
if sseq <= fss.First {
update(fss)
} else if sseq <= fss.Last {
// We matched but it is a partial.
havePartial = true
// Don't break here, we will update to keep tracking last.
update(fss)
} else {
totalSkipped += fss.Msgs
}
})
// If we did not encounter any partials we can return here.
if !havePartial {
return ss
}
// If we are here we need to scan the msgs.
// Capture first and last sequences for scan and then clear what we had.
first, last := ss.First, ss.Last
// To track if we decide to exclude we need to calculate first.
var needScanFirst bool
if first < sseq {
first = sseq
needScanFirst = true
}
// Now we want to check if it is better to scan inclusive and recalculate that way
// or leave and scan exclusive and adjust our totals.
// ss.Last is always correct here.
toScan, toExclude := last-first, first-ms.state.FirstSeq+ms.state.LastSeq-ss.Last
var seen map[string]bool
if lastPerSubject {
seen = make(map[string]bool)
}
if toScan < toExclude {
ss.Msgs, ss.First = 0, 0
update := func(sm *StoreMsg) {
ss.Msgs++
if ss.First == 0 {
ss.First = sm.seq
}
if seen != nil {
seen[sm.subj] = true
}
}
// Check if easier to just scan msgs vs the sequence range.
// This can happen with lots of interior deletes.
if last-first > uint64(len(ms.msgs)) {
for _, sm := range ms.msgs {
if sm.seq >= first && sm.seq <= last && !seen[sm.subj] && isMatch(sm.subj) {
update(sm)
}
}
} else {
for seq := first; seq <= last; seq++ {
if sm, ok := ms.msgs[seq]; ok && !seen[sm.subj] && isMatch(sm.subj) {
update(sm)
}
}
}
} else {
// We will adjust from the totals above by scanning what we need to exclude.
ss.First = first
ss.Msgs += totalSkipped
var adjust uint64
var tss *SimpleState
update := func(sm *StoreMsg) {
if lastPerSubject {
tss, _ = ms.fss.Find(stringToBytes(sm.subj))
}
// If we are last per subject, make sure to only adjust if all messages are before our first.
if tss == nil || tss.Last < first {
adjust++
}
if seen != nil {
seen[sm.subj] = true
}
}
// Check if easier to just scan msgs vs the sequence range.
if first-ms.state.FirstSeq > uint64(len(ms.msgs)) {
for _, sm := range ms.msgs {
if sm.seq < first && !seen[sm.subj] && isMatch(sm.subj) {
update(sm)
}
}
} else {
for seq := ms.state.FirstSeq; seq < first; seq++ {
if sm, ok := ms.msgs[seq]; ok && !seen[sm.subj] && isMatch(sm.subj) {
update(sm)
}
}
}
// Now do range at end.
for seq := last + 1; seq < ms.state.LastSeq; seq++ {
if sm, ok := ms.msgs[seq]; ok && !seen[sm.subj] && isMatch(sm.subj) {
adjust++
if seen != nil {
seen[sm.subj] = true
}
}
}
ss.Msgs -= adjust
if needScanFirst {
// Check if easier to just scan msgs vs the sequence range.
// Since we will need to scan all of the msgs vs below where we break on the first match,
// we will only do so if a few orders of magnitude lower.
if last-first > 100*uint64(len(ms.msgs)) {
low := ms.state.LastSeq
for _, sm := range ms.msgs {
if sm.seq >= first && sm.seq < last && isMatch(sm.subj) {
if sm.seq < low {
low = sm.seq
}
}
}
if low < ms.state.LastSeq {
ss.First = low
}
} else {
for seq := first; seq < last; seq++ {
if sm, ok := ms.msgs[seq]; ok && isMatch(sm.subj) {
ss.First = seq
break
}
}
}
}
}
return ss
}
// SubjectsState returns a map of SimpleState for all matching subjects.
func (ms *memStore) SubjectsState(subject string) map[string]SimpleState {
// This needs to be a write lock, as we can mutate the per-subject state.
ms.mu.Lock()
defer ms.mu.Unlock()
if ms.fss.Size() == 0 {
return nil
}
if subject == _EMPTY_ {
subject = fwcs
}
fss := make(map[string]SimpleState)
ms.fss.Match(stringToBytes(subject), func(subj []byte, ss *SimpleState) {
subjs := string(subj)
if ss.firstNeedsUpdate || ss.lastNeedsUpdate {
ms.recalculateForSubj(subjs, ss)
}
oss := fss[subjs]
if oss.First == 0 { // New
fss[subjs] = *ss
} else {
// Merge here.
oss.Last, oss.Msgs = ss.Last, oss.Msgs+ss.Msgs
fss[subjs] = oss
}
})
return fss
}
// AllLastSeqs will return a sorted list of last sequences for all subjects.
func (ms *memStore) AllLastSeqs() ([]uint64, error) {
ms.mu.RLock()
defer ms.mu.RUnlock()
return ms.allLastSeqsLocked()
}
// allLastSeqsLocked will return a sorted list of last sequences for all
// subjects, but won't take the lock to do it, to avoid the issue of compounding
// read locks causing a deadlock with a write lock.
func (ms *memStore) allLastSeqsLocked() ([]uint64, error) {
if len(ms.msgs) == 0 {
return nil, nil
}
seqs := make([]uint64, 0, ms.fss.Size())
ms.fss.IterFast(func(subj []byte, ss *SimpleState) bool {
seqs = append(seqs, ss.Last)
return true
})
slices.Sort(seqs)
return seqs, nil
}
// Helper to determine if the filter(s) represent all the subjects.
// Most clients send in subjects even if they match the stream's ingest subjects.
// Lock should be held.
func (ms *memStore) filterIsAll(filters []string) bool {
if len(filters) != len(ms.cfg.Subjects) {
return false
}
// Sort so we can compare.
slices.Sort(filters)
for i, subj := range filters {
if !subjectIsSubsetMatch(ms.cfg.Subjects[i], subj) {
return false
}
}
return true
}
// MultiLastSeqs will return a sorted list of sequences that match all subjects presented in filters.
// We will not exceed the maxSeq, which if 0 becomes the store's last sequence.
func (ms *memStore) MultiLastSeqs(filters []string, maxSeq uint64, maxAllowed int) ([]uint64, error) {
ms.mu.RLock()
defer ms.mu.RUnlock()
if len(ms.msgs) == 0 {
return nil, nil
}
// See if we can short circuit if we think they are asking for all last sequences and have no maxSeq or maxAllowed set.
if maxSeq == 0 && maxAllowed <= 0 && ms.filterIsAll(filters) {
return ms.allLastSeqsLocked()
}
// Implied last sequence.
if maxSeq == 0 {
maxSeq = ms.state.LastSeq
}
seqs := make([]uint64, 0, 64)
seen := make(map[uint64]struct{})
addIfNotDupe := func(seq uint64) {
if _, ok := seen[seq]; !ok {
seqs = append(seqs, seq)
seen[seq] = struct{}{}
}
}
for _, filter := range filters {
ms.fss.Match(stringToBytes(filter), func(subj []byte, ss *SimpleState) {
if ss.Last <= maxSeq {
addIfNotDupe(ss.Last)
} else if ss.Msgs > 1 {
// The last is greater than maxSeq.
s := bytesToString(subj)
for seq := maxSeq; seq > 0; seq-- {
if sm, ok := ms.msgs[seq]; ok && sm.subj == s {
addIfNotDupe(seq)
break
}
}
}
})
// If maxAllowed was sepcified check that we will not exceed that.
if maxAllowed > 0 && len(seqs) > maxAllowed {
return nil, ErrTooManyResults
}
}
slices.Sort(seqs)
return seqs, nil
}
// SubjectsTotals return message totals per subject.
func (ms *memStore) SubjectsTotals(filterSubject string) map[string]uint64 {
ms.mu.RLock()
defer ms.mu.RUnlock()
return ms.subjectsTotalsLocked(filterSubject)
}
// Lock should be held.
func (ms *memStore) subjectsTotalsLocked(filterSubject string) map[string]uint64 {
if ms.fss.Size() == 0 {
return nil
}
_tsa, _fsa := [32]string{}, [32]string{}
tsa, fsa := _tsa[:0], _fsa[:0]
fsa = tokenizeSubjectIntoSlice(fsa[:0], filterSubject)
isAll := filterSubject == _EMPTY_ || filterSubject == fwcs
fst := make(map[string]uint64)
ms.fss.Match(stringToBytes(filterSubject), func(subj []byte, ss *SimpleState) {
subjs := string(subj)
if isAll {
fst[subjs] = ss.Msgs
} else {
if tsa = tokenizeSubjectIntoSlice(tsa[:0], subjs); isSubsetMatchTokenized(tsa, fsa) {
fst[subjs] = ss.Msgs
}
}
})
return fst
}
// NumPending will return the number of pending messages matching the filter subject starting at sequence.
func (ms *memStore) NumPending(sseq uint64, filter string, lastPerSubject bool) (total, validThrough uint64) {
// This needs to be a write lock, as filteredStateLocked can mutate the per-subject state.
ms.mu.Lock()
defer ms.mu.Unlock()
ss := ms.filteredStateLocked(sseq, filter, lastPerSubject)
return ss.Msgs, ms.state.LastSeq
}
// NumPending will return the number of pending messages matching any subject in the sublist starting at sequence.
func (ms *memStore) NumPendingMulti(sseq uint64, sl *gsl.SimpleSublist, lastPerSubject bool) (total, validThrough uint64) {
if sl == nil {
return ms.NumPending(sseq, fwcs, lastPerSubject)
}
// This needs to be a write lock, as we can mutate the per-subject state.
ms.mu.Lock()
defer ms.mu.Unlock()
var ss SimpleState
if sseq < ms.state.FirstSeq {
sseq = ms.state.FirstSeq
}
// If past the end no results.
if sseq > ms.state.LastSeq {
return 0, ms.state.LastSeq
}
update := func(fss *SimpleState) {
msgs, first, last := fss.Msgs, fss.First, fss.Last
if lastPerSubject {
msgs, first = 1, last
}
ss.Msgs += msgs
if ss.First == 0 || first < ss.First {
ss.First = first
}
if last > ss.Last {
ss.Last = last
}
}
var havePartial bool
var totalSkipped uint64
// We will track start and end sequences as we go.
stree.IntersectGSL[SimpleState](ms.fss, sl, func(subj []byte, fss *SimpleState) {
if fss.firstNeedsUpdate || fss.lastNeedsUpdate {
ms.recalculateForSubj(bytesToString(subj), fss)
}
if sseq <= fss.First {
update(fss)
} else if sseq <= fss.Last {
// We matched but it is a partial.
havePartial = true
// Don't break here, we will update to keep tracking last.
update(fss)
} else {
totalSkipped += fss.Msgs
}
})
// If we did not encounter any partials we can return here.
if !havePartial {
return ss.Msgs, ms.state.LastSeq
}
// If we are here we need to scan the msgs.
// Capture first and last sequences for scan and then clear what we had.
first, last := ss.First, ss.Last
// To track if we decide to exclude we need to calculate first.
if first < sseq {
first = sseq
}
// Now we want to check if it is better to scan inclusive and recalculate that way
// or leave and scan exclusive and adjust our totals.
// ss.Last is always correct here.
toScan, toExclude := last-first, first-ms.state.FirstSeq+ms.state.LastSeq-ss.Last
var seen map[string]bool
if lastPerSubject {
seen = make(map[string]bool)
}
if toScan < toExclude {
ss.Msgs, ss.First = 0, 0
update := func(sm *StoreMsg) {
ss.Msgs++
if ss.First == 0 {
ss.First = sm.seq
}
if seen != nil {
seen[sm.subj] = true
}
}
// Check if easier to just scan msgs vs the sequence range.
// This can happen with lots of interior deletes.
if last-first > uint64(len(ms.msgs)) {
for _, sm := range ms.msgs {
if sm.seq >= first && sm.seq <= last && !seen[sm.subj] && sl.HasInterest(sm.subj) {
update(sm)
}
}
} else {
for seq := first; seq <= last; seq++ {
if sm, ok := ms.msgs[seq]; ok && !seen[sm.subj] && sl.HasInterest(sm.subj) {
update(sm)
}
}
}
} else {
// We will adjust from the totals above by scanning what we need to exclude.
ss.First = first
ss.Msgs += totalSkipped
var adjust uint64
var tss *SimpleState
update := func(sm *StoreMsg) {
if lastPerSubject {
tss, _ = ms.fss.Find(stringToBytes(sm.subj))
}
// If we are last per subject, make sure to only adjust if all messages are before our first.
if tss == nil || tss.Last < first {
adjust++
}
if seen != nil {
seen[sm.subj] = true
}
}
// Check if easier to just scan msgs vs the sequence range.
if first-ms.state.FirstSeq > uint64(len(ms.msgs)) {
for _, sm := range ms.msgs {
if sm.seq < first && !seen[sm.subj] && sl.HasInterest(sm.subj) {
update(sm)
}
}
} else {
for seq := ms.state.FirstSeq; seq < first; seq++ {
if sm, ok := ms.msgs[seq]; ok && !seen[sm.subj] && sl.HasInterest(sm.subj) {
update(sm)
}
}
}
// Now do range at end.
for seq := last + 1; seq < ms.state.LastSeq; seq++ {
if sm, ok := ms.msgs[seq]; ok && !seen[sm.subj] && sl.HasInterest(sm.subj) {
adjust++
if seen != nil {
seen[sm.subj] = true
}
}
}
ss.Msgs -= adjust
}
return ss.Msgs, ms.state.LastSeq
}
// Will check the msg limit for this tracked subject.
// Lock should be held.
func (ms *memStore) enforcePerSubjectLimit(subj string, ss *SimpleState) {
if ms.maxp <= 0 {
return
}
for nmsgs := ss.Msgs; nmsgs > uint64(ms.maxp); nmsgs = ss.Msgs {
if ss.firstNeedsUpdate || ss.lastNeedsUpdate {
ms.recalculateForSubj(subj, ss)
}
if !ms.removeMsg(ss.First, false) {
break
}
}
}
// Will check the msg limit and drop firstSeq msg if needed.
// Lock should be held.
func (ms *memStore) enforceMsgLimit() {
if ms.cfg.Discard != DiscardOld {
return
}
if ms.cfg.MaxMsgs <= 0 || ms.state.Msgs <= uint64(ms.cfg.MaxMsgs) {
return
}
for nmsgs := ms.state.Msgs; nmsgs > uint64(ms.cfg.MaxMsgs); nmsgs = ms.state.Msgs {
ms.deleteFirstMsgOrPanic()
}
}
// Will check the bytes limit and drop msgs if needed.
// Lock should be held.
func (ms *memStore) enforceBytesLimit() {
if ms.cfg.Discard != DiscardOld {
return
}
if ms.cfg.MaxBytes <= 0 || ms.state.Bytes <= uint64(ms.cfg.MaxBytes) {
return
}
for bs := ms.state.Bytes; bs > uint64(ms.cfg.MaxBytes); bs = ms.state.Bytes {
ms.deleteFirstMsgOrPanic()
}
}
// Will start the age check timer.
// Lock should be held.
func (ms *memStore) startAgeChk() {
if ms.ageChk != nil {
return
}
if ms.cfg.MaxAge != 0 || ms.ttls != nil {
ms.ageChk = time.AfterFunc(ms.cfg.MaxAge, ms.expireMsgs)
}
}
// Lock should be held.
func (ms *memStore) resetAgeChk(delta int64) {
// If we're already expiring messages, it will make sure to reset.
// Don't trigger again, as that could result in many expire goroutines.
if ms.ageChkRun {
return
}
var next int64 = math.MaxInt64
if ms.ttls != nil {
next = ms.ttls.GetNextExpiration(next)
}
// If there's no MaxAge and there's nothing waiting to be expired then
// don't bother continuing. The next storeRawMsg() will wake us up if
// needs be.
if ms.cfg.MaxAge <= 0 && next == math.MaxInt64 {
clearTimer(&ms.ageChk)
return
}
// Check to see if we should be firing sooner than MaxAge for an expiring TTL.
fireIn := ms.cfg.MaxAge
// If delta for next-to-expire message is unset, but we still have messages to remove.
// Assume messages are removed through proposals, and we need to speed up subsequent age check.
if delta == 0 && ms.state.Msgs > 0 {
if until := 2 * time.Second; until < fireIn {
fireIn = until
}
}
if next < math.MaxInt64 {
// Looks like there's a next expiration, use it either if there's no
// MaxAge set or if it looks to be sooner than MaxAge is.
if until := time.Until(time.Unix(0, next)); fireIn == 0 || until < fireIn {
fireIn = until
}
}
// If not then look at the delta provided (usually gap to next age expiry).
if delta > 0 {
if fireIn == 0 || time.Duration(delta) < fireIn {
fireIn = time.Duration(delta)
}
}
// Make sure we aren't firing too often either way, otherwise we can
// negatively impact stream ingest performance.
if fireIn < 250*time.Millisecond {
fireIn = 250 * time.Millisecond
}
// If we want to kick the timer to run later than what was assigned before, don't reset it.
// Otherwise, we could get in a situation where the timer is continuously reset, and it never runs.
expires := ats.AccessTime() + fireIn.Nanoseconds()
if ms.ageChkTime > 0 && expires > ms.ageChkTime {
return
}
ms.ageChkTime = expires
if ms.ageChk != nil {
ms.ageChk.Reset(fireIn)
} else {
ms.ageChk = time.AfterFunc(fireIn, ms.expireMsgs)
}
}
// Lock should be held.
func (ms *memStore) cancelAgeChk() {
if ms.ageChk != nil {
ms.ageChk.Stop()
ms.ageChk = nil
ms.ageChkTime = 0
}
}
// Will expire msgs that are too old.
func (ms *memStore) expireMsgs() {
var smv StoreMsg
var sm *StoreMsg
ms.mu.Lock()
maxAge := int64(ms.cfg.MaxAge)
minAge := time.Now().UnixNano() - maxAge
rmcb := ms.rmcb
sdmcb := ms.sdmcb
sdmTTL := int64(ms.cfg.SubjectDeleteMarkerTTL.Seconds())
sdmEnabled := sdmTTL > 0
// If SDM is enabled, but handlers aren't set up yet. Try again later.
if sdmEnabled && (rmcb == nil || sdmcb == nil) {
ms.resetAgeChk(0)
ms.mu.Unlock()
return
}
ms.ageChkRun = true
ms.mu.Unlock()
if maxAge > 0 {
var seq uint64
for sm, seq, _ = ms.LoadNextMsg(fwcs, true, 0, &smv); sm != nil && sm.ts <= minAge; sm, seq, _ = ms.LoadNextMsg(fwcs, true, seq+1, &smv) {
if len(sm.hdr) > 0 {
if ttl, err := getMessageTTL(sm.hdr); err == nil && ttl < 0 {
// The message has a negative TTL, therefore it must "never expire".
minAge = time.Now().UnixNano() - maxAge
continue
}
}
if sdmEnabled {
if last, ok := ms.shouldProcessSdm(seq, sm.subj); ok {
sdm := last && !isSubjectDeleteMarker(sm.hdr)
ms.handleRemovalOrSdm(seq, sm.subj, sdm, sdmTTL)
}
} else {
ms.mu.Lock()
ms.removeMsg(seq, false)
ms.mu.Unlock()
}
// Recalculate in case we are expiring a bunch.
minAge = time.Now().UnixNano() - maxAge
}
}
ms.mu.Lock()
defer ms.mu.Unlock()
// TODO: Not great that we're holding the lock here, but the timed hash wheel isn't thread-safe.
nextTTL := int64(math.MaxInt64)
var rmSeqs []thw.HashWheelEntry
if ms.ttls != nil {
ms.ttls.ExpireTasks(func(seq uint64, ts int64) bool {
rmSeqs = append(rmSeqs, thw.HashWheelEntry{Seq: seq, Expires: ts})
// We might need to remove messages out of band, those can fail, and we can be shutdown halfway
// through so don't remove from THW just yet.
return false
})
if maxAge > 0 {
// Only check if we're expiring something in the next MaxAge interval, saves us a bit
// of work if MaxAge will beat us to the next expiry anyway.
nextTTL = ms.ttls.GetNextExpiration(time.Now().Add(time.Duration(maxAge)).UnixNano())
} else {
nextTTL = ms.ttls.GetNextExpiration(math.MaxInt64)
}
}
// Remove messages collected by THW.
if !sdmEnabled {
for _, rm := range rmSeqs {
ms.removeMsg(rm.Seq, false)
}
} else {
// THW is unordered, so must sort by sequence and must not be holding the lock.
ms.mu.Unlock()
slices.SortFunc(rmSeqs, func(a, b thw.HashWheelEntry) int {
if a.Seq == b.Seq {
return 0
} else if a.Seq < b.Seq {
return -1
} else {
return 1
}
})
for _, rm := range rmSeqs {
// Need to grab subject for the specified sequence if for SDM, and check
// if the message hasn't been removed in the meantime.
// We need to grab the message and check if we should process SDM while holding the lock,
// otherwise we can race if a deletion of this message is in progress.
ms.mu.Lock()
sm, _ = ms.loadMsgLocked(rm.Seq, &smv, false)
if sm == nil {
ms.ttls.Remove(rm.Seq, rm.Expires)
ms.mu.Unlock()
continue
}
last, ok := ms.shouldProcessSdmLocked(rm.Seq, sm.subj)
ms.mu.Unlock()
if ok {
sdm := last && !isSubjectDeleteMarker(sm.hdr)
ms.handleRemovalOrSdm(rm.Seq, sm.subj, sdm, sdmTTL)
}
}
ms.mu.Lock()
}
// Only cancel if no message left, not on potential lookup error that would result in sm == nil.
ms.ageChkRun, ms.ageChkTime = false, 0
if ms.state.Msgs == 0 && nextTTL == math.MaxInt64 {
ms.cancelAgeChk()
} else {
if sm == nil {
ms.resetAgeChk(0)
} else {
ms.resetAgeChk(sm.ts - minAge)
}
}
}
func (ms *memStore) shouldProcessSdm(seq uint64, subj string) (bool, bool) {
ms.mu.Lock()
defer ms.mu.Unlock()
return ms.shouldProcessSdmLocked(seq, subj)
}
// Lock should be held.
func (ms *memStore) shouldProcessSdmLocked(seq uint64, subj string) (bool, bool) {
if ms.sdm == nil {
ms.sdm = newSDMMeta()
}
if p, ok := ms.sdm.pending[seq]; ok {
// Don't allow more proposals for the same sequence if we already did recently.
if time.Since(time.Unix(0, p.ts)) < 2*time.Second {
return p.last, false
}
// If we're about to use the cached value, and we knew it was last before,
// quickly check that we don't have more remaining messages for the subject now.
// Which means we are not the last anymore and must reset to not remove later data.
if p.last {
msgs := ms.subjectsTotalsLocked(subj)[subj]
numPending := ms.sdm.totals[subj]
if remaining := msgs - numPending; remaining > 0 {
p.last = false
}
}
ms.sdm.pending[seq] = SDMBySeq{p.last, time.Now().UnixNano()}
return p.last, true
}
msgs := ms.subjectsTotalsLocked(subj)[subj]
if msgs == 0 {
return false, true
}
numPending := ms.sdm.totals[subj]
remaining := msgs - numPending
return ms.sdm.trackPending(seq, subj, remaining == 1), true
}
func (ms *memStore) handleRemovalOrSdm(seq uint64, subj string, sdm bool, sdmTTL int64) {
if sdm {
var _hdr [128]byte
hdr := fmt.Appendf(
_hdr[:0],
"NATS/1.0\r\n%s: %s\r\n%s: %s\r\n%s: %s\r\n\r\n",
JSMarkerReason, JSMarkerReasonMaxAge,
JSMessageTTL, time.Duration(sdmTTL)*time.Second,
JSMsgRollup, JSMsgRollupSubject,
)
msg := &inMsg{
subj: subj,
hdr: hdr,
}
ms.sdmcb(msg)
} else {
ms.rmcb(seq)
}
}
// PurgeEx will remove messages based on subject filters, sequence and number of messages to keep.
// Will return the number of purged messages.
func (ms *memStore) PurgeEx(subject string, sequence, keep uint64) (purged uint64, err error) {
if subject == _EMPTY_ || subject == fwcs {
if keep == 0 && sequence == 0 {
return ms.purge(0)
}
if sequence > 1 {
return ms.compact(sequence)
} else if keep > 0 {
ms.mu.RLock()
msgs, lseq := ms.state.Msgs, ms.state.LastSeq
ms.mu.RUnlock()
if keep >= msgs {
return 0, nil
}
return ms.compact(lseq - keep + 1)
}
return 0, nil
}
eq := compareFn(subject)
if ss := ms.FilteredState(1, subject); ss.Msgs > 0 {
if keep > 0 {
if keep >= ss.Msgs {
return 0, nil
}
ss.Msgs -= keep
}
last := ss.Last
if sequence > 1 {
last = sequence - 1
}
ms.mu.Lock()
for seq := ss.First; seq <= last; seq++ {
if sm, ok := ms.msgs[seq]; ok && eq(sm.subj, subject) {
if ok := ms.removeMsg(sm.seq, false); ok {
purged++
if purged >= ss.Msgs {
break
}
}
}
}
ms.mu.Unlock()
}
return purged, nil
}
// Purge will remove all messages from this store.
// Will return the number of purged messages.
func (ms *memStore) Purge() (uint64, error) {
return ms.purge(0)
}
func (ms *memStore) purge(fseq uint64) (uint64, error) {
ms.mu.Lock()
purged := uint64(len(ms.msgs))
cb := ms.scb
bytes := int64(ms.state.Bytes)
if fseq == 0 {
fseq = ms.state.LastSeq + 1
} else if fseq < ms.state.LastSeq {
ms.mu.Unlock()
return 0, fmt.Errorf("partial purges not supported on memory store")
}
ms.state.FirstSeq = fseq
ms.state.LastSeq = fseq - 1
ms.state.FirstTime = time.Time{}
ms.state.Bytes = 0
ms.state.Msgs = 0
if ms.msgs != nil {
ms.msgs = make(map[uint64]*StoreMsg)
}
ms.fss = stree.NewSubjectTree[SimpleState]()
ms.dmap.Empty()
ms.sdm.empty()
ms.mu.Unlock()
if cb != nil {
cb(-int64(purged), -bytes, 0, _EMPTY_)
}
return purged, nil
}
// Compact will remove all messages from this store up to
// but not including the seq parameter.
// Will return the number of purged messages.
func (ms *memStore) Compact(seq uint64) (uint64, error) {
return ms.compact(seq)
}
func (ms *memStore) compact(seq uint64) (uint64, error) {
if seq == 0 {
return ms.Purge()
}
var purged, bytes uint64
ms.mu.Lock()
// Short-circuit if the store was already compacted past this point.
if ms.state.FirstSeq > seq {
ms.mu.Unlock()
return purged, nil
}
cb := ms.scb
if seq <= ms.state.LastSeq {
fseq := ms.state.FirstSeq
// Determine new first sequence.
for ; seq <= ms.state.LastSeq; seq++ {
if sm, ok := ms.msgs[seq]; ok {
ms.state.FirstSeq = seq
ms.state.FirstTime = time.Unix(0, sm.ts).UTC()
break
}
}
for seq := seq - 1; seq >= fseq; seq-- {
if sm := ms.msgs[seq]; sm != nil {
bytes += memStoreMsgSize(sm.subj, sm.hdr, sm.msg)
purged++
ms.removeSeqPerSubject(sm.subj, seq)
// Must delete message after updating per-subject info, to be consistent with file store.
delete(ms.msgs, seq)
} else if !ms.dmap.IsEmpty() {
ms.dmap.Delete(seq)
}
}
if purged > ms.state.Msgs {
purged = ms.state.Msgs
}
ms.state.Msgs -= purged
if bytes > ms.state.Bytes {
bytes = ms.state.Bytes
}
ms.state.Bytes -= bytes
} else {
// We are compacting past the end of our range. Do purge and set sequences correctly
// such that the next message placed will have seq.
purged = uint64(len(ms.msgs))
bytes = ms.state.Bytes
ms.state.Bytes = 0
ms.state.Msgs = 0
ms.state.FirstSeq = seq
ms.state.FirstTime = time.Time{}
ms.state.LastSeq = seq - 1
// Reset msgs, fss and dmap.
ms.msgs = make(map[uint64]*StoreMsg)
ms.fss = stree.NewSubjectTree[SimpleState]()
ms.dmap.Empty()
ms.sdm.empty()
}
ms.mu.Unlock()
if cb != nil {
cb(-int64(purged), -int64(bytes), 0, _EMPTY_)
}
return purged, nil
}
// Will completely reset our store.
func (ms *memStore) reset() error {
ms.mu.Lock()
var purged, bytes uint64
cb := ms.scb
if cb != nil {
for _, sm := range ms.msgs {
purged++
bytes += memStoreMsgSize(sm.subj, sm.hdr, sm.msg)
}
}
// Reset
ms.state.FirstSeq = 0
ms.state.FirstTime = time.Time{}
ms.state.LastSeq = 0
ms.state.LastTime = time.Now().UTC()
// Update msgs and bytes.
ms.state.Msgs = 0
ms.state.Bytes = 0
// Reset msgs, fss and dmap.
ms.msgs = make(map[uint64]*StoreMsg)
ms.fss = stree.NewSubjectTree[SimpleState]()
ms.dmap.Empty()
ms.sdm.empty()
ms.mu.Unlock()
if cb != nil {
cb(-int64(purged), -int64(bytes), 0, _EMPTY_)
}
return nil
}
// Truncate will truncate a stream store up to seq. Sequence needs to be valid.
func (ms *memStore) Truncate(seq uint64) error {
// Check for request to reset.
if seq == 0 {
return ms.reset()
}
var purged, bytes uint64
ms.mu.Lock()
lsm, ok := ms.msgs[seq]
lastTime := ms.state.LastTime
if ok && lsm != nil {
lastTime = time.Unix(0, lsm.ts).UTC()
}
for i := ms.state.LastSeq; i > seq; i-- {
if sm := ms.msgs[i]; sm != nil {
purged++
bytes += memStoreMsgSize(sm.subj, sm.hdr, sm.msg)
ms.removeSeqPerSubject(sm.subj, i)
// Must delete message after updating per-subject info, to be consistent with file store.
delete(ms.msgs, i)
} else if !ms.dmap.IsEmpty() {
ms.dmap.Delete(i)
}
}
// Reset last.
ms.state.LastSeq = seq
ms.state.LastTime = lastTime
// Update msgs and bytes.
if purged > ms.state.Msgs {
purged = ms.state.Msgs
}
ms.state.Msgs -= purged
if bytes > ms.state.Bytes {
bytes = ms.state.Bytes
}
ms.state.Bytes -= bytes
cb := ms.scb
ms.mu.Unlock()
if cb != nil {
cb(-int64(purged), -int64(bytes), 0, _EMPTY_)
}
return nil
}
func (ms *memStore) deleteFirstMsgOrPanic() {
if !ms.deleteFirstMsg() {
panic("jetstream memstore has inconsistent state, can't find first seq msg")
}
}
func (ms *memStore) deleteFirstMsg() bool {
return ms.removeMsg(ms.state.FirstSeq, false)
}
// SubjectForSeq will return what the subject is for this sequence if found.
func (ms *memStore) SubjectForSeq(seq uint64) (string, error) {
ms.mu.RLock()
defer ms.mu.RUnlock()
if seq < ms.state.FirstSeq {
return _EMPTY_, ErrStoreMsgNotFound
}
if sm, ok := ms.msgs[seq]; ok {
return sm.subj, nil
}
return _EMPTY_, ErrStoreMsgNotFound
}
// LoadMsg will lookup the message by sequence number and return it if found.
func (ms *memStore) LoadMsg(seq uint64, smp *StoreMsg) (*StoreMsg, error) {
return ms.loadMsgLocked(seq, smp, true)
}
// loadMsgLocked will lookup the message by sequence number and return it if found.
func (ms *memStore) loadMsgLocked(seq uint64, smp *StoreMsg, needMSLock bool) (*StoreMsg, error) {
if needMSLock {
ms.mu.RLock()
}
sm, ok := ms.msgs[seq]
last := ms.state.LastSeq
if needMSLock {
ms.mu.RUnlock()
}
if !ok || sm == nil {
var err = ErrStoreEOF
if seq <= last {
err = ErrStoreMsgNotFound
}
return nil, err
}
if smp == nil {
smp = new(StoreMsg)
}
sm.copy(smp)
return smp, nil
}
// LoadLastMsg will return the last message we have that matches a given subject.
// The subject can be a wildcard.
func (ms *memStore) LoadLastMsg(subject string, smp *StoreMsg) (*StoreMsg, error) {
var sm *StoreMsg
var ok bool
// This needs to be a write lock, as filteredStateLocked can
// mutate the per-subject state.
ms.mu.Lock()
defer ms.mu.Unlock()
if subject == _EMPTY_ || subject == fwcs {
sm, ok = ms.msgs[ms.state.LastSeq]
} else if subjectIsLiteral(subject) {
var ss *SimpleState
if ss, ok = ms.fss.Find(stringToBytes(subject)); ok && ss.Msgs > 0 {
sm, ok = ms.msgs[ss.Last]
}
} else if ss := ms.filteredStateLocked(1, subject, true); ss.Msgs > 0 {
sm, ok = ms.msgs[ss.Last]
}
if !ok || sm == nil {
return nil, ErrStoreMsgNotFound
}
if smp == nil {
smp = new(StoreMsg)
}
sm.copy(smp)
return smp, nil
}
// LoadNextMsgMulti will find the next message matching any entry in the sublist.
func (ms *memStore) LoadNextMsgMulti(sl *gsl.SimpleSublist, start uint64, smp *StoreMsg) (sm *StoreMsg, skip uint64, err error) {
// TODO(dlc) - for now simple linear walk to get started.
ms.mu.RLock()
defer ms.mu.RUnlock()
if start < ms.state.FirstSeq {
start = ms.state.FirstSeq
}
// If past the end no results.
if start > ms.state.LastSeq || ms.state.Msgs == 0 {
return nil, ms.state.LastSeq, ErrStoreEOF
}
// Initial setup.
fseq, lseq := start, ms.state.LastSeq
for nseq := fseq; nseq <= lseq; nseq++ {
sm, ok := ms.msgs[nseq]
if !ok {
continue
}
if sl.HasInterest(sm.subj) {
if smp == nil {
smp = new(StoreMsg)
}
sm.copy(smp)
return smp, nseq, nil
}
}
return nil, ms.state.LastSeq, ErrStoreEOF
}
// LoadNextMsg will find the next message matching the filter subject starting at the start sequence.
// The filter subject can be a wildcard.
func (ms *memStore) LoadNextMsg(filter string, wc bool, start uint64, smp *StoreMsg) (*StoreMsg, uint64, error) {
ms.mu.Lock()
defer ms.mu.Unlock()
return ms.loadNextMsgLocked(filter, wc, start, smp)
}
// Lock should be held.
func (ms *memStore) loadNextMsgLocked(filter string, wc bool, start uint64, smp *StoreMsg) (*StoreMsg, uint64, error) {
if start < ms.state.FirstSeq {
start = ms.state.FirstSeq
}
// If past the end no results.
if start > ms.state.LastSeq || ms.state.Msgs == 0 {
return nil, ms.state.LastSeq, ErrStoreEOF
}
if filter == _EMPTY_ {
filter = fwcs
}
isAll := filter == fwcs
// Skip scan of ms.fss if number of messages in the block are less than
// 1/2 the number of subjects in ms.fss. Or we have a wc and lots of fss entries.
const linearScanMaxFSS = 256
doLinearScan := isAll || 2*int(ms.state.LastSeq-start) < ms.fss.Size() || (wc && ms.fss.Size() > linearScanMaxFSS)
// Initial setup.
fseq, lseq := start, ms.state.LastSeq
if !doLinearScan {
subs := []string{filter}
if wc || isAll {
subs = subs[:0]
ms.fss.Match(stringToBytes(filter), func(subj []byte, val *SimpleState) {
subs = append(subs, string(subj))
})
}
fseq, lseq = ms.state.LastSeq, uint64(0)
for _, subj := range subs {
ss, ok := ms.fss.Find(stringToBytes(subj))
if !ok {
continue
}
if ss.firstNeedsUpdate || ss.lastNeedsUpdate {
ms.recalculateForSubj(subj, ss)
}
if ss.First < fseq {
fseq = ss.First
}
if ss.Last > lseq {
lseq = ss.Last
}
}
if fseq < start {
fseq = start
}
}
eq := subjectsEqual
if wc {
eq = subjectIsSubsetMatch
}
for nseq := fseq; nseq <= lseq; nseq++ {
if sm, ok := ms.msgs[nseq]; ok && (isAll || eq(sm.subj, filter)) {
if smp == nil {
smp = new(StoreMsg)
}
sm.copy(smp)
return smp, nseq, nil
}
}
return nil, ms.state.LastSeq, ErrStoreEOF
}
// Will load the previous message matching the filter subject, starting at the start sequence and walking backwards.
func (ms *memStore) LoadPrevMsg(filter string, wc bool, start uint64, smp *StoreMsg) (sm *StoreMsg, skip uint64, err error) {
ms.mu.RLock()
defer ms.mu.RUnlock()
if ms.msgs == nil {
return nil, 0, ErrStoreClosed
}
if ms.state.Msgs == 0 || start < ms.state.FirstSeq {
return nil, ms.state.FirstSeq, ErrStoreEOF
}
if start > ms.state.LastSeq {
start = ms.state.LastSeq
}
if filter == _EMPTY_ {
filter = fwcs
wc = true
}
isAll := filter == fwcs
eq := subjectsEqual
if wc {
eq = matchLiteral
}
for seq := start; seq >= ms.state.FirstSeq; seq-- {
if sm, ok := ms.msgs[seq]; ok && (isAll || eq(sm.subj, filter)) {
if smp == nil {
smp = new(StoreMsg)
}
sm.copy(smp)
return smp, seq, nil
}
}
return nil, ms.state.FirstSeq, ErrStoreEOF
}
// LoadPrevMsgMulti will find the previous message matching any entry in the sublist.
func (ms *memStore) LoadPrevMsgMulti(sl *gsl.SimpleSublist, start uint64, smp *StoreMsg) (sm *StoreMsg, skip uint64, err error) {
// TODO(dlc) - for now simple linear walk to get started.
ms.mu.RLock()
defer ms.mu.RUnlock()
if start > ms.state.LastSeq {
start = ms.state.LastSeq
}
// If past the start no results.
if start < ms.state.FirstSeq || ms.state.Msgs == 0 {
return nil, ms.state.FirstSeq, ErrStoreEOF
}
// Initial setup.
fseq, lseq := start, ms.state.FirstSeq
for nseq := fseq; nseq >= lseq; nseq-- {
sm, ok := ms.msgs[nseq]
if !ok {
continue
}
if sl.HasInterest(sm.subj) {
if smp == nil {
smp = new(StoreMsg)
}
sm.copy(smp)
return smp, nseq, nil
}
}
return nil, ms.state.FirstSeq, ErrStoreEOF
}
// RemoveMsg will remove the message from this store.
// Will return the number of bytes removed.
func (ms *memStore) RemoveMsg(seq uint64) (bool, error) {
ms.mu.Lock()
removed := ms.removeMsg(seq, false)
ms.mu.Unlock()
return removed, nil
}
// EraseMsg will remove the message and rewrite its contents.
func (ms *memStore) EraseMsg(seq uint64) (bool, error) {
ms.mu.Lock()
removed := ms.removeMsg(seq, true)
ms.mu.Unlock()
return removed, nil
}
// Performs logic to update first sequence number.
// Lock should be held.
func (ms *memStore) updateFirstSeq(seq uint64) {
if seq != ms.state.FirstSeq {
// Interior delete.
return
}
var nsm *StoreMsg
var ok bool
for nseq := ms.state.FirstSeq + 1; nseq <= ms.state.LastSeq; nseq++ {
if nsm, ok = ms.msgs[nseq]; ok {
break
}
}
oldFirst := ms.state.FirstSeq
if nsm != nil {
ms.state.FirstSeq = nsm.seq
ms.state.FirstTime = time.Unix(0, nsm.ts).UTC()
} else {
// Like purge.
ms.state.FirstSeq = ms.state.LastSeq + 1
ms.state.FirstTime = time.Time{}
}
if oldFirst == ms.state.FirstSeq-1 {
ms.dmap.Delete(oldFirst)
} else {
for seq := oldFirst; seq < ms.state.FirstSeq; seq++ {
ms.dmap.Delete(seq)
}
}
}
// Remove a seq from the fss and select new first.
// Lock should be held.
func (ms *memStore) removeSeqPerSubject(subj string, seq uint64) {
ss, ok := ms.fss.Find(stringToBytes(subj))
if !ok {
return
}
ms.sdm.removeSeqAndSubject(seq, subj)
if ss.Msgs == 1 {
ms.fss.Delete(stringToBytes(subj))
return
}
ss.Msgs--
// Only one left
if ss.Msgs == 1 {
if !ss.lastNeedsUpdate && seq != ss.Last {
ss.First = ss.Last
ss.firstNeedsUpdate = false
return
}
if !ss.firstNeedsUpdate && seq != ss.First {
ss.Last = ss.First
ss.lastNeedsUpdate = false
return
}
}
// We can lazily calculate the first/last sequence when needed.
ss.firstNeedsUpdate = seq == ss.First || ss.firstNeedsUpdate
ss.lastNeedsUpdate = seq == ss.Last || ss.lastNeedsUpdate
}
// Will recalculate the first and/or last sequence for this subject.
// Lock should be held.
func (ms *memStore) recalculateForSubj(subj string, ss *SimpleState) {
if ss.firstNeedsUpdate {
tseq := ss.First + 1
if tseq < ms.state.FirstSeq {
tseq = ms.state.FirstSeq
}
for ; tseq <= ss.Last; tseq++ {
if sm := ms.msgs[tseq]; sm != nil && sm.subj == subj {
ss.First = tseq
ss.firstNeedsUpdate = false
if ss.Msgs == 1 {
ss.Last = tseq
ss.lastNeedsUpdate = false
return
}
break
}
}
}
if ss.lastNeedsUpdate {
tseq := ss.Last - 1
if tseq > ms.state.LastSeq {
tseq = ms.state.LastSeq
}
for ; tseq >= ss.First; tseq-- {
if sm := ms.msgs[tseq]; sm != nil && sm.subj == subj {
ss.Last = tseq
ss.lastNeedsUpdate = false
if ss.Msgs == 1 {
ss.First = tseq
ss.firstNeedsUpdate = false
}
return
}
}
}
}
// Removes the message referenced by seq.
// Lock should be held.
func (ms *memStore) removeMsg(seq uint64, secure bool) bool {
var ss uint64
sm, ok := ms.msgs[seq]
if !ok {
return false
}
ss = memStoreMsgSize(sm.subj, sm.hdr, sm.msg)
if ms.state.Msgs > 0 {
ms.state.Msgs--
if ss > ms.state.Bytes {
ss = ms.state.Bytes
}
ms.state.Bytes -= ss
}
ms.dmap.Insert(seq)
ms.updateFirstSeq(seq)
// Remove any per subject tracking.
ms.removeSeqPerSubject(sm.subj, seq)
if ms.ttls != nil {
if ttl, err := getMessageTTL(sm.hdr); err == nil {
expires := time.Duration(sm.ts) + (time.Second * time.Duration(ttl))
ms.ttls.Remove(seq, int64(expires))
}
}
if secure {
if len(sm.hdr) > 0 {
sm.hdr = make([]byte, len(sm.hdr))
crand.Read(sm.hdr)
}
if len(sm.msg) > 0 {
sm.msg = make([]byte, len(sm.msg))
crand.Read(sm.msg)
}
sm.seq, sm.ts = 0, 0
}
// Must delete message after updating per-subject info, to be consistent with file store.
delete(ms.msgs, seq)
if ms.scb != nil {
// We do not want to hold any locks here.
ms.mu.Unlock()
if ms.scb != nil {
delta := int64(ss)
ms.scb(-1, -delta, seq, sm.subj)
}
ms.mu.Lock()
}
return ok
}
// Type returns the type of the underlying store.
func (ms *memStore) Type() StorageType {
return MemoryStorage
}
// FastState will fill in state with only the following.
// Msgs, Bytes, First and Last Sequence and Time and NumDeleted.
func (ms *memStore) FastState(state *StreamState) {
ms.mu.RLock()
state.Msgs = ms.state.Msgs
state.Bytes = ms.state.Bytes
state.FirstSeq = ms.state.FirstSeq
state.FirstTime = ms.state.FirstTime
state.LastSeq = ms.state.LastSeq
state.LastTime = ms.state.LastTime
if state.LastSeq > state.FirstSeq {
state.NumDeleted = int((state.LastSeq - state.FirstSeq + 1) - state.Msgs)
if state.NumDeleted < 0 {
state.NumDeleted = 0
}
}
state.Consumers = ms.consumers
state.NumSubjects = ms.fss.Size()
ms.mu.RUnlock()
}
func (ms *memStore) State() StreamState {
ms.mu.RLock()
defer ms.mu.RUnlock()
state := ms.state
state.Consumers = ms.consumers
state.NumSubjects = ms.fss.Size()
state.Deleted = nil
// Calculate interior delete details.
if numDeleted := int((state.LastSeq - state.FirstSeq + 1) - state.Msgs); numDeleted > 0 {
state.Deleted = make([]uint64, 0, numDeleted)
fseq, lseq := state.FirstSeq, state.LastSeq
ms.dmap.Range(func(seq uint64) bool {
if seq < fseq || seq > lseq {
ms.dmap.Delete(seq)
} else {
state.Deleted = append(state.Deleted, seq)
}
return true
})
}
if len(state.Deleted) > 0 {
state.NumDeleted = len(state.Deleted)
}
return state
}
func (ms *memStore) Utilization() (total, reported uint64, err error) {
ms.mu.RLock()
defer ms.mu.RUnlock()
return ms.state.Bytes, ms.state.Bytes, nil
}
func memStoreMsgSize(subj string, hdr, msg []byte) uint64 {
return uint64(len(subj) + len(hdr) + len(msg) + 16) // 8*2 for seq + age
}
// Delete is same as Stop for memory store.
func (ms *memStore) Delete() error {
return ms.Stop()
}
func (ms *memStore) Stop() error {
ms.mu.Lock()
if ms.msgs == nil {
ms.mu.Unlock()
return nil
}
if ms.ageChk != nil {
ms.ageChk.Stop()
ms.ageChk = nil
ms.ageChkTime = 0
}
ms.msgs = nil
ms.mu.Unlock()
// These can't come back, so stop is same as Delete.
ms.Purge()
// Unregister from the access time service.
ats.Unregister()
return nil
}
func (ms *memStore) isClosed() bool {
ms.mu.RLock()
defer ms.mu.RUnlock()
return ms.msgs == nil
}
type consumerMemStore struct {
mu sync.Mutex
ms StreamStore
cfg ConsumerConfig
state ConsumerState
closed bool
}
func (ms *memStore) ConsumerStore(name string, cfg *ConsumerConfig) (ConsumerStore, error) {
if ms == nil {
return nil, fmt.Errorf("memstore is nil")
}
if ms.isClosed() {
return nil, ErrStoreClosed
}
if cfg == nil || name == _EMPTY_ {
return nil, fmt.Errorf("bad consumer config")
}
o := &consumerMemStore{ms: ms, cfg: *cfg}
ms.AddConsumer(o)
return o, nil
}
func (ms *memStore) AddConsumer(o ConsumerStore) error {
ms.mu.Lock()
ms.consumers++
ms.mu.Unlock()
return nil
}
func (ms *memStore) RemoveConsumer(o ConsumerStore) error {
ms.mu.Lock()
if ms.consumers > 0 {
ms.consumers--
}
ms.mu.Unlock()
return nil
}
func (ms *memStore) Snapshot(_ time.Duration, _, _ bool) (*SnapshotResult, error) {
return nil, fmt.Errorf("no impl")
}
// Binary encoded state snapshot, >= v2.10 server.
func (ms *memStore) EncodedStreamState(failed uint64) ([]byte, error) {
ms.mu.RLock()
defer ms.mu.RUnlock()
// Quick calculate num deleted.
numDeleted := int((ms.state.LastSeq - ms.state.FirstSeq + 1) - ms.state.Msgs)
if numDeleted < 0 {
numDeleted = 0
}
// Encoded is Msgs, Bytes, FirstSeq, LastSeq, Failed, NumDeleted and optional DeletedBlocks
var buf [1024]byte
buf[0], buf[1] = streamStateMagic, streamStateVersion
n := hdrLen
n += binary.PutUvarint(buf[n:], ms.state.Msgs)
n += binary.PutUvarint(buf[n:], ms.state.Bytes)
n += binary.PutUvarint(buf[n:], ms.state.FirstSeq)
n += binary.PutUvarint(buf[n:], ms.state.LastSeq)
n += binary.PutUvarint(buf[n:], failed)
n += binary.PutUvarint(buf[n:], uint64(numDeleted))
b := buf[0:n]
if numDeleted > 0 {
buf, err := ms.dmap.Encode(nil)
if err != nil {
return nil, err
}
b = append(b, buf...)
}
return b, nil
}
// SyncDeleted will make sure this stream has same deleted state as dbs.
func (ms *memStore) SyncDeleted(dbs DeleteBlocks) {
ms.mu.Lock()
defer ms.mu.Unlock()
// For now we share one dmap, so if we have one entry here check if states are the same.
// Note this will work for any DeleteBlock type, but we expect this to be a dmap too.
if len(dbs) == 1 {
min, max, num := ms.dmap.State()
if pmin, pmax, pnum := dbs[0].State(); pmin == min && pmax == max && pnum == num {
return
}
}
lseq := ms.state.LastSeq
for _, db := range dbs {
// Skip if beyond our current state.
if first, _, _ := db.State(); first > lseq {
continue
}
db.Range(func(seq uint64) bool {
ms.removeMsg(seq, false)
return true
})
}
}
func (o *consumerMemStore) Update(state *ConsumerState) error {
// Sanity checks.
if state.AckFloor.Consumer > state.Delivered.Consumer {
return fmt.Errorf("bad ack floor for consumer")
}
if state.AckFloor.Stream > state.Delivered.Stream {
return fmt.Errorf("bad ack floor for stream")
}
// Copy to our state.
var pending map[uint64]*Pending
var redelivered map[uint64]uint64
if len(state.Pending) > 0 {
pending = make(map[uint64]*Pending, len(state.Pending))
for seq, p := range state.Pending {
pending[seq] = &Pending{p.Sequence, p.Timestamp}
if seq <= state.AckFloor.Stream || seq > state.Delivered.Stream {
return fmt.Errorf("bad pending entry, sequence [%d] out of range", seq)
}
}
}
if len(state.Redelivered) > 0 {
redelivered = make(map[uint64]uint64, len(state.Redelivered))
for seq, dc := range state.Redelivered {
redelivered[seq] = dc
}
}
// Replace our state.
o.mu.Lock()
defer o.mu.Unlock()
// Check to see if this is an outdated update.
if state.Delivered.Consumer < o.state.Delivered.Consumer || state.AckFloor.Stream < o.state.AckFloor.Stream {
return fmt.Errorf("old update ignored")
}
o.state.Delivered = state.Delivered
o.state.AckFloor = state.AckFloor
o.state.Pending = pending
o.state.Redelivered = redelivered
return nil
}
// SetStarting sets our starting stream sequence.
func (o *consumerMemStore) SetStarting(sseq uint64) error {
o.mu.Lock()
o.state.Delivered.Stream = sseq
o.mu.Unlock()
return nil
}
// UpdateStarting updates our starting stream sequence.
func (o *consumerMemStore) UpdateStarting(sseq uint64) {
o.mu.Lock()
defer o.mu.Unlock()
if sseq > o.state.Delivered.Stream {
o.state.Delivered.Stream = sseq
// For AckNone just update delivered and ackfloor at the same time.
if o.cfg.AckPolicy == AckNone {
o.state.AckFloor.Stream = sseq
}
}
}
// HasState returns if this store has a recorded state.
func (o *consumerMemStore) HasState() bool {
o.mu.Lock()
defer o.mu.Unlock()
// We have a running state.
return o.state.Delivered.Consumer != 0 || o.state.Delivered.Stream != 0
}
func (o *consumerMemStore) UpdateDelivered(dseq, sseq, dc uint64, ts int64) error {
o.mu.Lock()
defer o.mu.Unlock()
if dc != 1 && o.cfg.AckPolicy == AckNone {
return ErrNoAckPolicy
}
// On restarts the old leader may get a replay from the raft logs that are old.
if dseq <= o.state.AckFloor.Consumer {
return nil
}
// See if we expect an ack for this.
if o.cfg.AckPolicy != AckNone {
// Need to create pending records here.
if o.state.Pending == nil {
o.state.Pending = make(map[uint64]*Pending)
}
var p *Pending
// Check for an update to a message already delivered.
if sseq <= o.state.Delivered.Stream {
if p = o.state.Pending[sseq]; p != nil {
// Do not update p.Sequence, that should be the original delivery sequence.
p.Timestamp = ts
}
} else {
// Add to pending.
o.state.Pending[sseq] = &Pending{dseq, ts}
}
// Update delivered as needed.
if dseq > o.state.Delivered.Consumer {
o.state.Delivered.Consumer = dseq
}
if sseq > o.state.Delivered.Stream {
o.state.Delivered.Stream = sseq
}
if dc > 1 {
if maxdc := uint64(o.cfg.MaxDeliver); maxdc > 0 && dc > maxdc {
// Make sure to remove from pending.
delete(o.state.Pending, sseq)
}
if o.state.Redelivered == nil {
o.state.Redelivered = make(map[uint64]uint64)
}
// Only update if greater than what we already have.
if o.state.Redelivered[sseq] < dc-1 {
o.state.Redelivered[sseq] = dc - 1
}
}
} else {
// For AckNone just update delivered and ackfloor at the same time.
if dseq > o.state.Delivered.Consumer {
o.state.Delivered.Consumer = dseq
o.state.AckFloor.Consumer = dseq
}
if sseq > o.state.Delivered.Stream {
o.state.Delivered.Stream = sseq
o.state.AckFloor.Stream = sseq
}
}
return nil
}
func (o *consumerMemStore) UpdateAcks(dseq, sseq uint64) error {
o.mu.Lock()
defer o.mu.Unlock()
if o.cfg.AckPolicy == AckNone {
return ErrNoAckPolicy
}
// On restarts the old leader may get a replay from the raft logs that are old.
if dseq <= o.state.AckFloor.Consumer {
return nil
}
if len(o.state.Pending) == 0 || o.state.Pending[sseq] == nil {
delete(o.state.Redelivered, sseq)
return ErrStoreMsgNotFound
}
// Check for AckAll here.
if o.cfg.AckPolicy == AckAll {
sgap := sseq - o.state.AckFloor.Stream
o.state.AckFloor.Consumer = dseq
o.state.AckFloor.Stream = sseq
if sgap > uint64(len(o.state.Pending)) {
for seq := range o.state.Pending {
if seq <= sseq {
delete(o.state.Pending, seq)
delete(o.state.Redelivered, seq)
}
}
} else {
for seq := sseq; seq > sseq-sgap && len(o.state.Pending) > 0; seq-- {
delete(o.state.Pending, seq)
delete(o.state.Redelivered, seq)
}
}
return nil
}
// AckExplicit
// First delete from our pending state.
if p, ok := o.state.Pending[sseq]; ok {
delete(o.state.Pending, sseq)
if dseq > p.Sequence && p.Sequence > 0 {
dseq = p.Sequence // Use the original.
}
}
if len(o.state.Pending) == 0 {
o.state.AckFloor.Consumer = o.state.Delivered.Consumer
o.state.AckFloor.Stream = o.state.Delivered.Stream
} else if dseq == o.state.AckFloor.Consumer+1 {
o.state.AckFloor.Consumer = dseq
o.state.AckFloor.Stream = sseq
if o.state.Delivered.Consumer > dseq {
for ss := sseq + 1; ss <= o.state.Delivered.Stream; ss++ {
if p, ok := o.state.Pending[ss]; ok {
if p.Sequence > 0 {
o.state.AckFloor.Consumer = p.Sequence - 1
o.state.AckFloor.Stream = ss - 1
}
break
}
}
}
}
// We do these regardless.
delete(o.state.Redelivered, sseq)
return nil
}
func (o *consumerMemStore) UpdateConfig(cfg *ConsumerConfig) error {
o.mu.Lock()
defer o.mu.Unlock()
// This is mostly unchecked here. We are assuming the upper layers have done sanity checking.
o.cfg = *cfg
return nil
}
func (o *consumerMemStore) Stop() error {
o.mu.Lock()
o.closed = true
ms := o.ms
o.mu.Unlock()
ms.RemoveConsumer(o)
return nil
}
func (o *consumerMemStore) Delete() error {
return o.Stop()
}
func (o *consumerMemStore) StreamDelete() error {
return o.Stop()
}
func (o *consumerMemStore) State() (*ConsumerState, error) {
return o.stateWithCopy(true)
}
// This will not copy pending or redelivered, so should only be done under the
// consumer owner's lock.
func (o *consumerMemStore) BorrowState() (*ConsumerState, error) {
return o.stateWithCopy(false)
}
func (o *consumerMemStore) stateWithCopy(doCopy bool) (*ConsumerState, error) {
o.mu.Lock()
defer o.mu.Unlock()
if o.closed {
return nil, ErrStoreClosed
}
state := &ConsumerState{}
state.Delivered = o.state.Delivered
state.AckFloor = o.state.AckFloor
if len(o.state.Pending) > 0 {
if doCopy {
state.Pending = o.copyPending()
} else {
state.Pending = o.state.Pending
}
}
if len(o.state.Redelivered) > 0 {
if doCopy {
state.Redelivered = o.copyRedelivered()
} else {
state.Redelivered = o.state.Redelivered
}
}
return state, nil
}
// EncodedState for this consumer store.
func (o *consumerMemStore) EncodedState() ([]byte, error) {
o.mu.Lock()
defer o.mu.Unlock()
if o.closed {
return nil, ErrStoreClosed
}
return encodeConsumerState(&o.state), nil
}
func (o *consumerMemStore) copyPending() map[uint64]*Pending {
pending := make(map[uint64]*Pending, len(o.state.Pending))
for seq, p := range o.state.Pending {
pending[seq] = &Pending{p.Sequence, p.Timestamp}
}
return pending
}
func (o *consumerMemStore) copyRedelivered() map[uint64]uint64 {
redelivered := make(map[uint64]uint64, len(o.state.Redelivered))
for seq, dc := range o.state.Redelivered {
redelivered[seq] = dc
}
return redelivered
}
// Type returns the type of the underlying store.
func (o *consumerMemStore) Type() StorageType { return MemoryStorage }
// Templates
type templateMemStore struct{}
func newTemplateMemStore() *templateMemStore {
return &templateMemStore{}
}
// No-ops for memstore.
func (ts *templateMemStore) Store(t *streamTemplate) error { return nil }
func (ts *templateMemStore) Delete(t *streamTemplate) error { return nil }