2543 lines
64 KiB
Go
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 }
|