Skip to content

Commit 55c5c7e

Browse files
Cherry-picks for v2.12.4-RC.3 (#7742)
Includes the following: - #7735 - #7736 - #7741 - #7737 - #7744 Signed-off-by: Neil Twigg <neil@nats.io>
2 parents 4faf8db + 6e1cea1 commit 55c5c7e

File tree

17 files changed

+738
-552
lines changed

17 files changed

+738
-552
lines changed

.github/workflows/release.yaml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -38,8 +38,8 @@ jobs:
3838

3939
- name: Install syft
4040
# Use commit hash here to avoid a re-tagging attack, as this is a third-party action
41-
# Commit a930d0ac434e3182448fe678398ba5713717112a = tag v0.21.0
42-
uses: anchore/sbom-action/download-syft@a930d0ac434e3182448fe678398ba5713717112a
41+
# Commit 0b82b0b1a22399a1c542d4d656f70cd903571b5c = tag v0.21.1
42+
uses: anchore/sbom-action/download-syft@0b82b0b1a22399a1c542d4d656f70cd903571b5c
4343
with:
4444
syft-version: "v1.27.1"
4545

.goreleaser.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ builds:
2121
env:
2222
# This is the toolchain version we use for releases. To override, set the env var, e.g.:
2323
# GORELEASER_TOOLCHAIN="go1.22.8" TARGET='linux_amd64' goreleaser build --snapshot --clean --single-target
24-
- GOTOOLCHAIN={{ envOrDefault "GORELEASER_TOOLCHAIN" "go1.25.5" }}
24+
- GOTOOLCHAIN={{ envOrDefault "GORELEASER_TOOLCHAIN" "go1.25.6" }}
2525
- GO111MODULE=on
2626
- CGO_ENABLED=0
2727
goos:

conf/parse.go

Lines changed: 46 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,9 @@ type parser struct {
6060

6161
// pedantic reports error when configuration is not correct.
6262
pedantic bool
63+
64+
// Tracks environment variable references, to avoid cycles
65+
envVarReferences map[string]bool
6366
}
6467

6568
// Parse will return a map of keys to any, although concrete types
@@ -180,16 +183,37 @@ func (t *token) Position() int {
180183
return t.item.pos
181184
}
182185

183-
func parse(data, fp string, pedantic bool) (p *parser, err error) {
184-
p = &parser{
185-
mapping: make(map[string]any),
186-
lx: lex(data),
187-
ctxs: make([]any, 0, 4),
188-
keys: make([]string, 0, 4),
189-
ikeys: make([]item, 0, 4),
190-
fp: filepath.Dir(fp),
191-
pedantic: pedantic,
186+
func newParser(data, fp string, pedantic bool) *parser {
187+
return &parser{
188+
mapping: make(map[string]any),
189+
lx: lex(data),
190+
ctxs: make([]any, 0, 4),
191+
keys: make([]string, 0, 4),
192+
ikeys: make([]item, 0, 4),
193+
fp: filepath.Dir(fp),
194+
pedantic: pedantic,
195+
envVarReferences: make(map[string]bool),
196+
}
197+
}
198+
199+
func parse(data, fp string, pedantic bool) (*parser, error) {
200+
p := newParser(data, fp, pedantic)
201+
if err := p.parse(fp); err != nil {
202+
return nil, err
192203
}
204+
return p, nil
205+
}
206+
207+
func parseEnv(data string, parent *parser) (*parser, error) {
208+
p := newParser(data, "", false)
209+
p.envVarReferences = parent.envVarReferences
210+
if err := p.parse(""); err != nil {
211+
return nil, err
212+
}
213+
return p, nil
214+
}
215+
216+
func (p *parser) parse(fp string) error {
193217
p.pushContext(p.mapping)
194218

195219
var prevItem item
@@ -199,16 +223,16 @@ func parse(data, fp string, pedantic bool) (p *parser, err error) {
199223
// Here we allow the final character to be a bracket '}'
200224
// in order to support JSON like configurations.
201225
if prevItem.typ == itemKey && prevItem.val != mapEndString {
202-
return nil, fmt.Errorf("config is invalid (%s:%d:%d)", fp, it.line, it.pos)
226+
return fmt.Errorf("config is invalid (%s:%d:%d)", fp, it.line, it.pos)
203227
}
204228
break
205229
}
206230
prevItem = it
207231
if err := p.processItem(it, fp); err != nil {
208-
return nil, err
232+
return err
209233
}
210234
}
211-
return p, nil
235+
return nil
212236
}
213237

214238
func (p *parser) next() item {
@@ -453,11 +477,18 @@ func (p *parser) lookupVariable(varReference string) (any, bool, error) {
453477
}
454478

455479
// If we are here, we have exhausted our context maps and still not found anything.
456-
// Parse from the environment.
480+
// Detect reference cycles
481+
if p.envVarReferences[varReference] {
482+
return nil, false, fmt.Errorf("variable reference cycle for '%s'", varReference)
483+
}
484+
p.envVarReferences[varReference] = true
485+
defer delete(p.envVarReferences, varReference)
486+
487+
// Parse from the environment
457488
if vStr, ok := os.LookupEnv(varReference); ok {
458489
// Everything we get here will be a string value, so we need to process as a parser would.
459-
if vmap, err := Parse(fmt.Sprintf("%s=%s", pkey, vStr)); err == nil {
460-
v, ok := vmap[pkey]
490+
if subp, err := parseEnv(fmt.Sprintf("%s=%s", pkey, vStr), p); err == nil {
491+
v, ok := subp.mapping[pkey]
461492
return v, ok, nil
462493
} else {
463494
return nil, false, err

go.mod

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,19 +2,19 @@ module github.com/nats-io/nats-server/v2
22

33
go 1.24.0
44

5-
toolchain go1.24.11
5+
toolchain go1.24.12
66

77
require (
88
github.com/antithesishq/antithesis-sdk-go v0.5.0-default-no-op
99
github.com/google/go-tpm v0.9.8
10-
github.com/klauspost/compress v1.18.2
10+
github.com/klauspost/compress v1.18.3
1111
github.com/nats-io/jwt/v2 v2.8.0
1212
github.com/nats-io/nats.go v1.48.0
1313
github.com/nats-io/nkeys v0.4.12
1414
github.com/nats-io/nuid v1.0.1
1515
go.uber.org/automaxprocs v1.6.0
16-
golang.org/x/crypto v0.46.0
17-
golang.org/x/sys v0.39.0
16+
golang.org/x/crypto v0.47.0
17+
golang.org/x/sys v0.40.0
1818
golang.org/x/time v0.14.0
1919
)
2020

go.sum

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,8 @@ github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c
44
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
55
github.com/google/go-tpm v0.9.8 h1:slArAR9Ft+1ybZu0lBwpSmpwhRXaa85hWtMinMyRAWo=
66
github.com/google/go-tpm v0.9.8/go.mod h1:h9jEsEECg7gtLis0upRBQU+GhYVH6jMjrFxI8u6bVUY=
7-
github.com/klauspost/compress v1.18.2 h1:iiPHWW0YrcFgpBYhsA6D1+fqHssJscY/Tm/y2Uqnapk=
8-
github.com/klauspost/compress v1.18.2/go.mod h1:R0h/fSBs8DE4ENlcrlib3PsXS61voFxhIs2DeRhCvJ4=
7+
github.com/klauspost/compress v1.18.3 h1:9PJRvfbmTabkOX8moIpXPbMMbYN60bWImDDU7L+/6zw=
8+
github.com/klauspost/compress v1.18.3/go.mod h1:R0h/fSBs8DE4ENlcrlib3PsXS61voFxhIs2DeRhCvJ4=
99
github.com/minio/highwayhash v1.0.4-0.20251030100505-070ab1a87a76 h1:KGuD/pM2JpL9FAYvBrnBBeENKZNh6eNtjqytV6TYjnk=
1010
github.com/minio/highwayhash v1.0.4-0.20251030100505-070ab1a87a76/go.mod h1:GGYsuwP/fPD6Y9hMiXuapVvlIUEhFhMTh0rxU3ik1LQ=
1111
github.com/nats-io/jwt/v2 v2.8.0 h1:K7uzyz50+yGZDO5o772eRE7atlcSEENpL7P+b74JV1g=
@@ -24,11 +24,11 @@ github.com/stretchr/testify v1.7.1 h1:5TQK59W5E3v0r2duFAb7P95B6hEeOyEnHRa8MjYSMT
2424
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
2525
go.uber.org/automaxprocs v1.6.0 h1:O3y2/QNTOdbF+e/dpXNNW7Rx2hZ4sTIPyybbxyNqTUs=
2626
go.uber.org/automaxprocs v1.6.0/go.mod h1:ifeIMSnPZuznNm6jmdzmU3/bfk01Fe2fotchwEFJ8r8=
27-
golang.org/x/crypto v0.46.0 h1:cKRW/pmt1pKAfetfu+RCEvjvZkA9RimPbh7bhFjGVBU=
28-
golang.org/x/crypto v0.46.0/go.mod h1:Evb/oLKmMraqjZ2iQTwDwvCtJkczlDuTmdJXoZVzqU0=
27+
golang.org/x/crypto v0.47.0 h1:V6e3FRj+n4dbpw86FJ8Fv7XVOql7TEwpHapKoMJ/GO8=
28+
golang.org/x/crypto v0.47.0/go.mod h1:ff3Y9VzzKbwSSEzWqJsJVBnWmRwRSHt/6Op5n9bQc4A=
2929
golang.org/x/sys v0.21.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
30-
golang.org/x/sys v0.39.0 h1:CvCKL8MeisomCi6qNZ+wbb0DN9E5AATixKsvNtMoMFk=
31-
golang.org/x/sys v0.39.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
30+
golang.org/x/sys v0.40.0 h1:DBZZqJ2Rkml6QMQsZywtnjnnGvHza6BTfYFWY9kjEWQ=
31+
golang.org/x/sys v0.40.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
3232
golang.org/x/time v0.14.0 h1:MRx4UaLrDotUKUdCIqzPC48t1Y9hANFKIRpNx+Te8PI=
3333
golang.org/x/time v0.14.0/go.mod h1:eL/Oa2bBBK0TkX57Fyni+NgnyQQN4LitPmob2Hjnqw4=
3434
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=

server/filestore.go

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -2663,7 +2663,7 @@ func (mb *msgBlock) firstMatchingMulti(sl *gsl.SimpleSublist, start uint64, sm *
26632663
if uint64(mb.fss.Size()) < lseq-start {
26642664
// If there are no subject matches then this is effectively no-op.
26652665
hseq := uint64(math.MaxUint64)
2666-
gsl.IntersectStree(mb.fss, sl, func(subj []byte, ss *SimpleState) {
2666+
stree.IntersectGSL(mb.fss, sl, func(subj []byte, ss *SimpleState) {
26672667
if ss.firstNeedsUpdate || ss.lastNeedsUpdate {
26682668
// mb is already loaded into the cache so should be fast-ish.
26692669
mb.recalculateForSubj(bytesToString(subj), ss)
@@ -2915,7 +2915,7 @@ func (mb *msgBlock) prevMatchingMulti(sl *gsl.SimpleSublist, start uint64, sm *S
29152915
if uint64(mb.fss.Size()) < start-lseq {
29162916
// If there are no subject matches then this is effectively no-op.
29172917
hseq := uint64(0)
2918-
gsl.IntersectStree(mb.fss, sl, func(subj []byte, ss *SimpleState) {
2918+
stree.IntersectGSL(mb.fss, sl, func(subj []byte, ss *SimpleState) {
29192919
if ss.firstNeedsUpdate || ss.lastNeedsUpdate {
29202920
// mb is already loaded into the cache so should be fast-ish.
29212921
mb.recalculateForSubj(bytesToString(subj), ss)
@@ -4012,7 +4012,7 @@ func (fs *fileStore) NumPendingMulti(sseq uint64, sl *gsl.SimpleSublist, lastPer
40124012
mb := fs.blks[seqStart]
40134013
bi := mb.index
40144014

4015-
gsl.IntersectStree(fs.psim, sl, func(subj []byte, psi *psi) {
4015+
stree.IntersectGSL(fs.psim, sl, func(subj []byte, psi *psi) {
40164016
// If the select blk start is greater than entry's last blk skip.
40174017
if bi > psi.lblk {
40184018
return
@@ -4111,7 +4111,7 @@ func (fs *fileStore) NumPendingMulti(sseq uint64, sl *gsl.SimpleSublist, lastPer
41114111
var t uint64
41124112
var havePartial bool
41134113
var updateLLTS bool
4114-
gsl.IntersectStree[SimpleState](mb.fss, sl, func(bsubj []byte, ss *SimpleState) {
4114+
stree.IntersectGSL[SimpleState](mb.fss, sl, func(bsubj []byte, ss *SimpleState) {
41154115
subj := bytesToString(bsubj)
41164116
if havePartial {
41174117
// If we already found a partial then don't do anything else.
@@ -4174,7 +4174,7 @@ func (fs *fileStore) NumPendingMulti(sseq uint64, sl *gsl.SimpleSublist, lastPer
41744174

41754175
// If we are here it's better to calculate totals from psim and adjust downward by scanning less blocks.
41764176
start := uint32(math.MaxUint32)
4177-
gsl.IntersectStree(fs.psim, sl, func(subj []byte, psi *psi) {
4177+
stree.IntersectGSL(fs.psim, sl, func(subj []byte, psi *psi) {
41784178
total += psi.total
41794179
// Keep track of start index for this subject.
41804180
if psi.fblk < start {
@@ -4225,7 +4225,7 @@ func (fs *fileStore) NumPendingMulti(sseq uint64, sl *gsl.SimpleSublist, lastPer
42254225
}
42264226
// Mark fss activity.
42274227
mb.lsts = ats.AccessTime()
4228-
gsl.IntersectStree(mb.fss, sl, func(bsubj []byte, ss *SimpleState) {
4228+
stree.IntersectGSL(mb.fss, sl, func(bsubj []byte, ss *SimpleState) {
42294229
adjust += ss.Msgs
42304230
})
42314231
}

server/gsl/gsl.go

Lines changed: 38 additions & 83 deletions
Original file line numberDiff line numberDiff line change
@@ -17,9 +17,6 @@ import (
1717
"errors"
1818
"strings"
1919
"sync"
20-
"unsafe"
21-
22-
"github.com/nats-io/nats-server/v2/server/stree"
2320
)
2421

2522
// Sublist is a routing mechanism to handle subject distribution and
@@ -372,6 +369,35 @@ func (s *GenericSublist[T]) Remove(subject string, value T) error {
372369
return s.remove(subject, value, true)
373370
}
374371

372+
// HasInterestStartingIn is a helper for subject tree intersection.
373+
func (s *GenericSublist[T]) HasInterestStartingIn(subj string) bool {
374+
s.RLock()
375+
defer s.RUnlock()
376+
var _tokens [64]string
377+
tokens := tokenizeSubjectIntoSlice(_tokens[:0], subj)
378+
return hasInterestStartingIn(s.root, tokens)
379+
}
380+
381+
func hasInterestStartingIn[T comparable](l *level[T], tokens []string) bool {
382+
if l == nil {
383+
return false
384+
}
385+
if len(tokens) == 0 {
386+
return true
387+
}
388+
token := tokens[0]
389+
if l.fwc != nil {
390+
return true
391+
}
392+
if pwc := l.pwc; pwc != nil {
393+
return hasInterestStartingIn(pwc.next, tokens[1:])
394+
}
395+
if n := l.nodes[token]; n != nil {
396+
return hasInterestStartingIn(n.next, tokens[1:])
397+
}
398+
return false
399+
}
400+
375401
// pruneNode is used to prune an empty node from the tree.
376402
func (l *level[T]) pruneNode(n *node[T], t string) {
377403
if n == nil {
@@ -465,86 +491,15 @@ func visitLevel[T comparable](l *level[T], depth int) int {
465491
return maxDepth
466492
}
467493

468-
// IntersectStree will match all items in the given subject tree that
469-
// have interest expressed in the given sublist. The callback will only be called
470-
// once for each subject, regardless of overlapping subscriptions in the sublist.
471-
func IntersectStree[T1 any, T2 comparable](st *stree.SubjectTree[T1], sl *GenericSublist[T2], cb func(subj []byte, entry *T1)) {
472-
var _subj [255]byte
473-
intersectStree(st, sl.root, _subj[:0], cb)
474-
}
475-
476-
func intersectStree[T1 any, T2 comparable](st *stree.SubjectTree[T1], r *level[T2], subj []byte, cb func(subj []byte, entry *T1)) {
477-
nsubj := subj
478-
if len(nsubj) > 0 {
479-
nsubj = append(subj, '.')
480-
}
481-
if r.fwc != nil {
482-
// We've reached a full wildcard, do a FWC match on the stree at this point
483-
// and don't keep iterating downward.
484-
nsubj := append(nsubj, '>')
485-
st.Match(nsubj, cb)
486-
return
487-
}
488-
if r.pwc != nil {
489-
// We've found a partial wildcard. We'll keep iterating downwards, but first
490-
// check whether there's interest at this level (without triggering dupes) and
491-
// match if so.
492-
var done bool
493-
nsubj := append(nsubj, '*')
494-
if len(r.pwc.subs) > 0 {
495-
st.Match(nsubj, cb)
496-
done = true
497-
}
498-
if r.pwc.next.numNodes() > 0 {
499-
intersectStree(st, r.pwc.next, nsubj, cb)
500-
}
501-
if done {
502-
return
503-
}
504-
}
505-
// Normal node with subject literals, keep iterating.
506-
for t, n := range r.nodes {
507-
if r.pwc != nil && r.pwc.next.numNodes() > 0 && n.next.numNodes() > 0 {
508-
// A wildcard at the next level will already visit these descendents
509-
// so skip so we don't callback the same subject more than once.
510-
continue
511-
}
512-
nsubj := append(nsubj, t...)
513-
if len(n.subs) > 0 {
514-
if subjectHasWildcard(bytesToString(nsubj)) {
515-
st.Match(nsubj, cb)
516-
} else {
517-
if e, ok := st.Find(nsubj); ok {
518-
cb(nsubj, e)
519-
}
520-
}
521-
}
522-
if n.next.numNodes() > 0 {
523-
intersectStree(st, n.next, nsubj, cb)
524-
}
525-
}
526-
}
527-
528-
// Determine if a subject has any wildcard tokens.
529-
func subjectHasWildcard(subject string) bool {
530-
// This one exits earlier then !subjectIsLiteral(subject)
531-
for i, c := range subject {
532-
if c == pwc || c == fwc {
533-
if (i == 0 || subject[i-1] == btsep) &&
534-
(i+1 == len(subject) || subject[i+1] == btsep) {
535-
return true
536-
}
494+
// use similar to append. meaning, the updated slice will be returned
495+
func tokenizeSubjectIntoSlice(tts []string, subject string) []string {
496+
start := 0
497+
for i := 0; i < len(subject); i++ {
498+
if subject[i] == btsep {
499+
tts = append(tts, subject[start:i])
500+
start = i + 1
537501
}
538502
}
539-
return false
540-
}
541-
542-
// Note this will avoid a copy of the data used for the string, but it will also reference the existing slice's data pointer.
543-
// So this should be used sparingly when we know the encompassing byte slice's lifetime is the same.
544-
func bytesToString(b []byte) string {
545-
if len(b) == 0 {
546-
return _EMPTY_
547-
}
548-
p := unsafe.SliceData(b)
549-
return unsafe.String(p, len(b))
503+
tts = append(tts, subject[start:])
504+
return tts
550505
}

0 commit comments

Comments
 (0)