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.
This commit is contained in:
+70
@@ -0,0 +1,70 @@
|
||||
// Copyright 2024 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 stree
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// For dumping out a text representation of a tree.
|
||||
func (t *SubjectTree[T]) Dump(w io.Writer) {
|
||||
t.dump(w, t.root, 0)
|
||||
fmt.Fprintln(w)
|
||||
}
|
||||
|
||||
// Will dump out a node.
|
||||
func (t *SubjectTree[T]) dump(w io.Writer, n node, depth int) {
|
||||
if n == nil {
|
||||
fmt.Fprintf(w, "EMPTY\n")
|
||||
return
|
||||
}
|
||||
if n.isLeaf() {
|
||||
leaf := n.(*leaf[T])
|
||||
fmt.Fprintf(w, "%s LEAF: Suffix: %q Value: %+v\n", dumpPre(depth), leaf.suffix, leaf.value)
|
||||
n = nil
|
||||
} else {
|
||||
// We are a node type here, grab meta portion.
|
||||
bn := n.base()
|
||||
fmt.Fprintf(w, "%s %s Prefix: %q\n", dumpPre(depth), n.kind(), bn.prefix)
|
||||
depth++
|
||||
n.iter(func(n node) bool {
|
||||
t.dump(w, n, depth)
|
||||
return true
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// For individual node/leaf dumps.
|
||||
func (n *leaf[T]) kind() string { return "LEAF" }
|
||||
func (n *node4) kind() string { return "NODE4" }
|
||||
func (n *node10) kind() string { return "NODE10" }
|
||||
func (n *node16) kind() string { return "NODE16" }
|
||||
func (n *node48) kind() string { return "NODE48" }
|
||||
func (n *node256) kind() string { return "NODE256" }
|
||||
|
||||
// Calculates the indendation, etc.
|
||||
func dumpPre(depth int) string {
|
||||
if depth == 0 {
|
||||
return "-- "
|
||||
} else {
|
||||
var b strings.Builder
|
||||
for i := 0; i < depth; i++ {
|
||||
b.WriteString(" ")
|
||||
}
|
||||
b.WriteString("|__ ")
|
||||
return b.String()
|
||||
}
|
||||
}
|
||||
+51
@@ -0,0 +1,51 @@
|
||||
// Copyright 2023-2024 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 stree
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
)
|
||||
|
||||
// Leaf node
|
||||
// Order of struct fields for best memory alignment (as per govet/fieldalignment)
|
||||
type leaf[T any] struct {
|
||||
value T
|
||||
// This could be the whole subject, but most likely just the suffix portion.
|
||||
// We will only store the suffix here and assume all prior prefix paths have
|
||||
// been checked once we arrive at this leafnode.
|
||||
suffix []byte
|
||||
}
|
||||
|
||||
func newLeaf[T any](suffix []byte, value T) *leaf[T] {
|
||||
return &leaf[T]{value, copyBytes(suffix)}
|
||||
}
|
||||
|
||||
func (n *leaf[T]) isLeaf() bool { return true }
|
||||
func (n *leaf[T]) base() *meta { return nil }
|
||||
func (n *leaf[T]) match(subject []byte) bool { return bytes.Equal(subject, n.suffix) }
|
||||
func (n *leaf[T]) setSuffix(suffix []byte) { n.suffix = copyBytes(suffix) }
|
||||
func (n *leaf[T]) isFull() bool { return true }
|
||||
func (n *leaf[T]) matchParts(parts [][]byte) ([][]byte, bool) { return matchParts(parts, n.suffix) }
|
||||
func (n *leaf[T]) iter(f func(node) bool) {}
|
||||
func (n *leaf[T]) children() []node { return nil }
|
||||
func (n *leaf[T]) numChildren() uint16 { return 0 }
|
||||
func (n *leaf[T]) path() []byte { return n.suffix }
|
||||
|
||||
// Not applicable to leafs and should not be called, so panic if we do.
|
||||
func (n *leaf[T]) setPrefix(pre []byte) { panic("setPrefix called on leaf") }
|
||||
func (n *leaf[T]) addChild(_ byte, _ node) { panic("addChild called on leaf") }
|
||||
func (n *leaf[T]) findChild(_ byte) *node { panic("findChild called on leaf") }
|
||||
func (n *leaf[T]) grow() node { panic("grow called on leaf") }
|
||||
func (n *leaf[T]) deleteChild(_ byte) { panic("deleteChild called on leaf") }
|
||||
func (n *leaf[T]) shrink() node { panic("shrink called on leaf") }
|
||||
+53
@@ -0,0 +1,53 @@
|
||||
// Copyright 2023-2024 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 stree
|
||||
|
||||
// Internal node interface.
|
||||
type node interface {
|
||||
isLeaf() bool
|
||||
base() *meta
|
||||
setPrefix(pre []byte)
|
||||
addChild(c byte, n node)
|
||||
findChild(c byte) *node
|
||||
deleteChild(c byte)
|
||||
isFull() bool
|
||||
grow() node
|
||||
shrink() node
|
||||
matchParts(parts [][]byte) ([][]byte, bool)
|
||||
kind() string
|
||||
iter(f func(node) bool)
|
||||
children() []node
|
||||
numChildren() uint16
|
||||
path() []byte
|
||||
}
|
||||
|
||||
type meta struct {
|
||||
prefix []byte
|
||||
size uint16
|
||||
}
|
||||
|
||||
func (n *meta) isLeaf() bool { return false }
|
||||
func (n *meta) base() *meta { return n }
|
||||
|
||||
func (n *meta) setPrefix(pre []byte) {
|
||||
n.prefix = append([]byte(nil), pre...)
|
||||
}
|
||||
|
||||
func (n *meta) numChildren() uint16 { return n.size }
|
||||
func (n *meta) path() []byte { return n.prefix }
|
||||
|
||||
// Will match parts against our prefix.
|
||||
func (n *meta) matchParts(parts [][]byte) ([][]byte, bool) {
|
||||
return matchParts(parts, n.prefix)
|
||||
}
|
||||
+106
@@ -0,0 +1,106 @@
|
||||
// Copyright 2023-2024 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 stree
|
||||
|
||||
// Node with 10 children
|
||||
// This node size is for the particular case that a part of the subject is numeric
|
||||
// in nature, i.e. it only needs to satisfy the range 0-9 without wasting bytes
|
||||
// Order of struct fields for best memory alignment (as per govet/fieldalignment)
|
||||
type node10 struct {
|
||||
child [10]node
|
||||
meta
|
||||
key [10]byte
|
||||
}
|
||||
|
||||
func newNode10(prefix []byte) *node10 {
|
||||
nn := &node10{}
|
||||
nn.setPrefix(prefix)
|
||||
return nn
|
||||
}
|
||||
|
||||
// Currently we do not keep node10 sorted or use bitfields for traversal so just add to the end.
|
||||
// TODO(dlc) - We should revisit here with more detailed benchmarks.
|
||||
func (n *node10) addChild(c byte, nn node) {
|
||||
if n.size >= 10 {
|
||||
panic("node10 full!")
|
||||
}
|
||||
n.key[n.size] = c
|
||||
n.child[n.size] = nn
|
||||
n.size++
|
||||
}
|
||||
|
||||
func (n *node10) findChild(c byte) *node {
|
||||
for i := uint16(0); i < n.size; i++ {
|
||||
if n.key[i] == c {
|
||||
return &n.child[i]
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (n *node10) isFull() bool { return n.size >= 10 }
|
||||
|
||||
func (n *node10) grow() node {
|
||||
nn := newNode16(n.prefix)
|
||||
for i := 0; i < 10; i++ {
|
||||
nn.addChild(n.key[i], n.child[i])
|
||||
}
|
||||
return nn
|
||||
}
|
||||
|
||||
// Deletes a child from the node.
|
||||
func (n *node10) deleteChild(c byte) {
|
||||
for i, last := uint16(0), n.size-1; i < n.size; i++ {
|
||||
if n.key[i] == c {
|
||||
// Unsorted so just swap in last one here, else nil if last.
|
||||
if i < last {
|
||||
n.key[i] = n.key[last]
|
||||
n.child[i] = n.child[last]
|
||||
n.key[last] = 0
|
||||
n.child[last] = nil
|
||||
} else {
|
||||
n.key[i] = 0
|
||||
n.child[i] = nil
|
||||
}
|
||||
n.size--
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Shrink if needed and return new node, otherwise return nil.
|
||||
func (n *node10) shrink() node {
|
||||
if n.size > 4 {
|
||||
return nil
|
||||
}
|
||||
nn := newNode4(nil)
|
||||
for i := uint16(0); i < n.size; i++ {
|
||||
nn.addChild(n.key[i], n.child[i])
|
||||
}
|
||||
return nn
|
||||
}
|
||||
|
||||
// Iterate over all children calling func f.
|
||||
func (n *node10) iter(f func(node) bool) {
|
||||
for i := uint16(0); i < n.size; i++ {
|
||||
if !f(n.child[i]) {
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Return our children as a slice.
|
||||
func (n *node10) children() []node {
|
||||
return n.child[:n.size]
|
||||
}
|
||||
+104
@@ -0,0 +1,104 @@
|
||||
// Copyright 2023-2024 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 stree
|
||||
|
||||
// Node with 16 children
|
||||
// Order of struct fields for best memory alignment (as per govet/fieldalignment)
|
||||
type node16 struct {
|
||||
child [16]node
|
||||
meta
|
||||
key [16]byte
|
||||
}
|
||||
|
||||
func newNode16(prefix []byte) *node16 {
|
||||
nn := &node16{}
|
||||
nn.setPrefix(prefix)
|
||||
return nn
|
||||
}
|
||||
|
||||
// Currently we do not keep node16 sorted or use bitfields for traversal so just add to the end.
|
||||
// TODO(dlc) - We should revisit here with more detailed benchmarks.
|
||||
func (n *node16) addChild(c byte, nn node) {
|
||||
if n.size >= 16 {
|
||||
panic("node16 full!")
|
||||
}
|
||||
n.key[n.size] = c
|
||||
n.child[n.size] = nn
|
||||
n.size++
|
||||
}
|
||||
|
||||
func (n *node16) findChild(c byte) *node {
|
||||
for i := uint16(0); i < n.size; i++ {
|
||||
if n.key[i] == c {
|
||||
return &n.child[i]
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (n *node16) isFull() bool { return n.size >= 16 }
|
||||
|
||||
func (n *node16) grow() node {
|
||||
nn := newNode48(n.prefix)
|
||||
for i := 0; i < 16; i++ {
|
||||
nn.addChild(n.key[i], n.child[i])
|
||||
}
|
||||
return nn
|
||||
}
|
||||
|
||||
// Deletes a child from the node.
|
||||
func (n *node16) deleteChild(c byte) {
|
||||
for i, last := uint16(0), n.size-1; i < n.size; i++ {
|
||||
if n.key[i] == c {
|
||||
// Unsorted so just swap in last one here, else nil if last.
|
||||
if i < last {
|
||||
n.key[i] = n.key[last]
|
||||
n.child[i] = n.child[last]
|
||||
n.key[last] = 0
|
||||
n.child[last] = nil
|
||||
} else {
|
||||
n.key[i] = 0
|
||||
n.child[i] = nil
|
||||
}
|
||||
n.size--
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Shrink if needed and return new node, otherwise return nil.
|
||||
func (n *node16) shrink() node {
|
||||
if n.size > 10 {
|
||||
return nil
|
||||
}
|
||||
nn := newNode10(nil)
|
||||
for i := uint16(0); i < n.size; i++ {
|
||||
nn.addChild(n.key[i], n.child[i])
|
||||
}
|
||||
return nn
|
||||
}
|
||||
|
||||
// Iterate over all children calling func f.
|
||||
func (n *node16) iter(f func(node) bool) {
|
||||
for i := uint16(0); i < n.size; i++ {
|
||||
if !f(n.child[i]) {
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Return our children as a slice.
|
||||
func (n *node16) children() []node {
|
||||
return n.child[:n.size]
|
||||
}
|
||||
+80
@@ -0,0 +1,80 @@
|
||||
// Copyright 2023-2024 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 stree
|
||||
|
||||
// Node with 256 children
|
||||
// Order of struct fields for best memory alignment (as per govet/fieldalignment)
|
||||
type node256 struct {
|
||||
child [256]node
|
||||
meta
|
||||
}
|
||||
|
||||
func newNode256(prefix []byte) *node256 {
|
||||
nn := &node256{}
|
||||
nn.setPrefix(prefix)
|
||||
return nn
|
||||
}
|
||||
|
||||
func (n *node256) addChild(c byte, nn node) {
|
||||
n.child[c] = nn
|
||||
n.size++
|
||||
}
|
||||
|
||||
func (n *node256) findChild(c byte) *node {
|
||||
if n.child[c] != nil {
|
||||
return &n.child[c]
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (n *node256) isFull() bool { return false }
|
||||
func (n *node256) grow() node { panic("grow can not be called on node256") }
|
||||
|
||||
// Deletes a child from the node.
|
||||
func (n *node256) deleteChild(c byte) {
|
||||
if n.child[c] != nil {
|
||||
n.child[c] = nil
|
||||
n.size--
|
||||
}
|
||||
}
|
||||
|
||||
// Shrink if needed and return new node, otherwise return nil.
|
||||
func (n *node256) shrink() node {
|
||||
if n.size > 48 {
|
||||
return nil
|
||||
}
|
||||
nn := newNode48(nil)
|
||||
for c, child := range n.child {
|
||||
if child != nil {
|
||||
nn.addChild(byte(c), n.child[c])
|
||||
}
|
||||
}
|
||||
return nn
|
||||
}
|
||||
|
||||
// Iterate over all children calling func f.
|
||||
func (n *node256) iter(f func(node) bool) {
|
||||
for i := 0; i < 256; i++ {
|
||||
if n.child[i] != nil {
|
||||
if !f(n.child[i]) {
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Return our children as a slice.
|
||||
func (n *node256) children() []node {
|
||||
return n.child[:256]
|
||||
}
|
||||
+99
@@ -0,0 +1,99 @@
|
||||
// Copyright 2023-2024 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 stree
|
||||
|
||||
// Node with 4 children
|
||||
// Order of struct fields for best memory alignment (as per govet/fieldalignment)
|
||||
type node4 struct {
|
||||
child [4]node
|
||||
meta
|
||||
key [4]byte
|
||||
}
|
||||
|
||||
func newNode4(prefix []byte) *node4 {
|
||||
nn := &node4{}
|
||||
nn.setPrefix(prefix)
|
||||
return nn
|
||||
}
|
||||
|
||||
// Currently we do not need to keep sorted for traversal so just add to the end.
|
||||
func (n *node4) addChild(c byte, nn node) {
|
||||
if n.size >= 4 {
|
||||
panic("node4 full!")
|
||||
}
|
||||
n.key[n.size] = c
|
||||
n.child[n.size] = nn
|
||||
n.size++
|
||||
}
|
||||
|
||||
func (n *node4) findChild(c byte) *node {
|
||||
for i := uint16(0); i < n.size; i++ {
|
||||
if n.key[i] == c {
|
||||
return &n.child[i]
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (n *node4) isFull() bool { return n.size >= 4 }
|
||||
|
||||
func (n *node4) grow() node {
|
||||
nn := newNode10(n.prefix)
|
||||
for i := 0; i < 4; i++ {
|
||||
nn.addChild(n.key[i], n.child[i])
|
||||
}
|
||||
return nn
|
||||
}
|
||||
|
||||
// Deletes a child from the node.
|
||||
func (n *node4) deleteChild(c byte) {
|
||||
for i, last := uint16(0), n.size-1; i < n.size; i++ {
|
||||
if n.key[i] == c {
|
||||
// Unsorted so just swap in last one here, else nil if last.
|
||||
if i < last {
|
||||
n.key[i] = n.key[last]
|
||||
n.child[i] = n.child[last]
|
||||
n.key[last] = 0
|
||||
n.child[last] = nil
|
||||
} else {
|
||||
n.key[i] = 0
|
||||
n.child[i] = nil
|
||||
}
|
||||
n.size--
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Shrink if needed and return new node, otherwise return nil.
|
||||
func (n *node4) shrink() node {
|
||||
if n.size == 1 {
|
||||
return n.child[0]
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Iterate over all children calling func f.
|
||||
func (n *node4) iter(f func(node) bool) {
|
||||
for i := uint16(0); i < n.size; i++ {
|
||||
if !f(n.child[i]) {
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Return our children as a slice.
|
||||
func (n *node4) children() []node {
|
||||
return n.child[:n.size]
|
||||
}
|
||||
+110
@@ -0,0 +1,110 @@
|
||||
// Copyright 2023-2024 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 stree
|
||||
|
||||
// Node with 48 children
|
||||
// Memory saving vs node256 comes from the fact that the child array is 16 bytes
|
||||
// per `node` entry, so node256's 256*16=4096 vs node48's 256+(48*16)=1024
|
||||
// Note that `key` is effectively 1-indexed, as 0 means no entry, so offset by 1
|
||||
// Order of struct fields for best memory alignment (as per govet/fieldalignment)
|
||||
type node48 struct {
|
||||
child [48]node
|
||||
meta
|
||||
key [256]byte
|
||||
}
|
||||
|
||||
func newNode48(prefix []byte) *node48 {
|
||||
nn := &node48{}
|
||||
nn.setPrefix(prefix)
|
||||
return nn
|
||||
}
|
||||
|
||||
func (n *node48) addChild(c byte, nn node) {
|
||||
if n.size >= 48 {
|
||||
panic("node48 full!")
|
||||
}
|
||||
n.child[n.size] = nn
|
||||
n.key[c] = byte(n.size + 1) // 1-indexed
|
||||
n.size++
|
||||
}
|
||||
|
||||
func (n *node48) findChild(c byte) *node {
|
||||
i := n.key[c]
|
||||
if i == 0 {
|
||||
return nil
|
||||
}
|
||||
return &n.child[i-1]
|
||||
}
|
||||
|
||||
func (n *node48) isFull() bool { return n.size >= 48 }
|
||||
|
||||
func (n *node48) grow() node {
|
||||
nn := newNode256(n.prefix)
|
||||
for c := 0; c < len(n.key); c++ {
|
||||
if i := n.key[byte(c)]; i > 0 {
|
||||
nn.addChild(byte(c), n.child[i-1])
|
||||
}
|
||||
}
|
||||
return nn
|
||||
}
|
||||
|
||||
// Deletes a child from the node.
|
||||
func (n *node48) deleteChild(c byte) {
|
||||
i := n.key[c]
|
||||
if i == 0 {
|
||||
return
|
||||
}
|
||||
i-- // Adjust for 1-indexing
|
||||
last := byte(n.size - 1)
|
||||
if i < last {
|
||||
n.child[i] = n.child[last]
|
||||
for ic := 0; ic < len(n.key); ic++ {
|
||||
if n.key[byte(ic)] == last+1 {
|
||||
n.key[byte(ic)] = i + 1
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
n.child[last] = nil
|
||||
n.key[c] = 0
|
||||
n.size--
|
||||
}
|
||||
|
||||
// Shrink if needed and return new node, otherwise return nil.
|
||||
func (n *node48) shrink() node {
|
||||
if n.size > 16 {
|
||||
return nil
|
||||
}
|
||||
nn := newNode16(nil)
|
||||
for c := 0; c < len(n.key); c++ {
|
||||
if i := n.key[byte(c)]; i > 0 {
|
||||
nn.addChild(byte(c), n.child[i-1])
|
||||
}
|
||||
}
|
||||
return nn
|
||||
}
|
||||
|
||||
// Iterate over all children calling func f.
|
||||
func (n *node48) iter(f func(node) bool) {
|
||||
for _, c := range n.child {
|
||||
if c != nil && !f(c) {
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Return our children as a slice.
|
||||
func (n *node48) children() []node {
|
||||
return n.child[:n.size]
|
||||
}
|
||||
+147
@@ -0,0 +1,147 @@
|
||||
// Copyright 2023-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 stree
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
)
|
||||
|
||||
// genParts will break a filter subject up into parts.
|
||||
// We need to break this up into chunks based on wildcards, either pwc '*' or fwc '>'.
|
||||
// We do not care about other tokens per se, just parts that are separated by wildcards with an optional end fwc.
|
||||
func genParts(filter []byte, parts [][]byte) [][]byte {
|
||||
var start int
|
||||
for i, e := 0, len(filter)-1; i < len(filter); i++ {
|
||||
if filter[i] == tsep {
|
||||
// See if next token is pwc. Either internal or end pwc.
|
||||
if i < e && filter[i+1] == pwc && (i+2 <= e && filter[i+2] == tsep || i+1 == e) {
|
||||
if i > start {
|
||||
parts = append(parts, filter[start:i+1])
|
||||
}
|
||||
parts = append(parts, filter[i+1:i+2])
|
||||
i++ // Skip pwc
|
||||
if i+2 <= e {
|
||||
i++ // Skip next tsep from next part too.
|
||||
}
|
||||
start = i + 1
|
||||
} else if i < e && filter[i+1] == fwc && i+1 == e {
|
||||
if i > start {
|
||||
parts = append(parts, filter[start:i+1])
|
||||
}
|
||||
parts = append(parts, filter[i+1:i+2])
|
||||
i++ // Skip fwc
|
||||
start = i + 1
|
||||
}
|
||||
} else if filter[i] == pwc || filter[i] == fwc {
|
||||
// Wildcard must be at the start or preceded by tsep.
|
||||
if prev := i - 1; prev >= 0 && filter[prev] != tsep {
|
||||
continue
|
||||
}
|
||||
// Wildcard must be at the end or followed by tsep.
|
||||
if next := i + 1; next == e || next < e && filter[next] != tsep {
|
||||
continue
|
||||
}
|
||||
// Full wildcard must be terminal.
|
||||
if filter[i] == fwc && i < e {
|
||||
break
|
||||
}
|
||||
// We start with a pwc or fwc.
|
||||
parts = append(parts, filter[i:i+1])
|
||||
if i+1 <= e {
|
||||
i++ // Skip next tsep from next part too.
|
||||
}
|
||||
start = i + 1
|
||||
}
|
||||
}
|
||||
if start < len(filter) {
|
||||
// Check to see if we need to eat a leading tsep.
|
||||
if filter[start] == tsep {
|
||||
start++
|
||||
}
|
||||
parts = append(parts, filter[start:])
|
||||
}
|
||||
return parts
|
||||
}
|
||||
|
||||
// Match our parts against a fragment, which could be prefix for nodes or a suffix for leafs.
|
||||
func matchParts(parts [][]byte, frag []byte) ([][]byte, bool) {
|
||||
lf := len(frag)
|
||||
if lf == 0 {
|
||||
return parts, true
|
||||
}
|
||||
|
||||
var si int
|
||||
lpi := len(parts) - 1
|
||||
|
||||
for i, part := range parts {
|
||||
if si >= lf {
|
||||
return parts[i:], true
|
||||
}
|
||||
lp := len(part)
|
||||
// Check for pwc or fwc place holders.
|
||||
if lp == 1 {
|
||||
if part[0] == pwc {
|
||||
index := bytes.IndexByte(frag[si:], tsep)
|
||||
// We are trying to match pwc and did not find our tsep.
|
||||
// Will need to move to next node from caller.
|
||||
if index < 0 {
|
||||
if i == lpi {
|
||||
return nil, true
|
||||
}
|
||||
return parts[i:], true
|
||||
}
|
||||
si += index + 1
|
||||
continue
|
||||
} else if part[0] == fwc {
|
||||
// If we are here we should be good.
|
||||
return nil, true
|
||||
}
|
||||
}
|
||||
end := min(si+lp, lf)
|
||||
// If part is bigger then the remaining fragment, adjust to a portion on the part.
|
||||
if si+lp > end {
|
||||
// Frag is smaller then part itself.
|
||||
part = part[:end-si]
|
||||
}
|
||||
if !bytes.Equal(part, frag[si:end]) {
|
||||
return parts, false
|
||||
}
|
||||
// If we still have a portion of the fragment left, update and continue.
|
||||
if end < lf {
|
||||
si = end
|
||||
continue
|
||||
}
|
||||
// If we matched a partial, do not move past current part
|
||||
// but update the part to what was consumed. This allows upper layers to continue.
|
||||
if end < si+lp {
|
||||
if end >= lf {
|
||||
// Create a copy before modifying. Reuse slice capacity available at the
|
||||
// end of the parts slice, since this saves us additional allocations.
|
||||
lp := len(parts)
|
||||
parts = append(parts[lp:], parts[:lp]...)
|
||||
parts[i] = parts[i][lf-si:]
|
||||
} else {
|
||||
i++
|
||||
}
|
||||
return parts[i:], true
|
||||
}
|
||||
if i == lpi {
|
||||
return nil, true
|
||||
}
|
||||
// If we are here we are not the last part which means we have a wildcard
|
||||
// gap, so we need to match anything up to next tsep.
|
||||
si += len(part)
|
||||
}
|
||||
return parts, false
|
||||
}
|
||||
+510
@@ -0,0 +1,510 @@
|
||||
// Copyright 2023-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 stree
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"slices"
|
||||
"unsafe"
|
||||
|
||||
"github.com/nats-io/nats-server/v2/server/gsl"
|
||||
)
|
||||
|
||||
// SubjectTree is an adaptive radix trie (ART) for storing subject information on literal subjects.
|
||||
// Will use dynamic nodes, path compression and lazy expansion.
|
||||
// The reason this exists is to not only save some memory in our filestore but to greatly optimize matching
|
||||
// a wildcard subject to certain members, e.g. consumer NumPending calculations.
|
||||
type SubjectTree[T any] struct {
|
||||
root node
|
||||
size int
|
||||
}
|
||||
|
||||
// NewSubjectTree creates a new SubjectTree with values T.
|
||||
func NewSubjectTree[T any]() *SubjectTree[T] {
|
||||
return &SubjectTree[T]{}
|
||||
}
|
||||
|
||||
// Size returns the number of elements stored.
|
||||
func (t *SubjectTree[T]) Size() int {
|
||||
if t == nil {
|
||||
return 0
|
||||
}
|
||||
return t.size
|
||||
}
|
||||
|
||||
// Will empty out the tree, or if tree is nil create a new one.
|
||||
func (t *SubjectTree[T]) Empty() *SubjectTree[T] {
|
||||
if t == nil {
|
||||
return NewSubjectTree[T]()
|
||||
}
|
||||
t.root, t.size = nil, 0
|
||||
return t
|
||||
}
|
||||
|
||||
// Insert a value into the tree. Will return if the value was updated and if so the old value.
|
||||
func (t *SubjectTree[T]) Insert(subject []byte, value T) (*T, bool) {
|
||||
if t == nil {
|
||||
return nil, false
|
||||
}
|
||||
|
||||
// Make sure we never insert anything with a noPivot byte.
|
||||
if bytes.IndexByte(subject, noPivot) >= 0 {
|
||||
return nil, false
|
||||
}
|
||||
|
||||
old, updated := t.insert(&t.root, subject, value, 0)
|
||||
if !updated {
|
||||
t.size++
|
||||
}
|
||||
return old, updated
|
||||
}
|
||||
|
||||
// Find will find the value and return it or false if it was not found.
|
||||
func (t *SubjectTree[T]) Find(subject []byte) (*T, bool) {
|
||||
if t == nil {
|
||||
return nil, false
|
||||
}
|
||||
|
||||
var si int
|
||||
for n := t.root; n != nil; {
|
||||
if n.isLeaf() {
|
||||
if ln := n.(*leaf[T]); ln.match(subject[si:]) {
|
||||
return &ln.value, true
|
||||
}
|
||||
return nil, false
|
||||
}
|
||||
// We are a node type here, grab meta portion.
|
||||
if bn := n.base(); len(bn.prefix) > 0 {
|
||||
end := min(si+len(bn.prefix), len(subject))
|
||||
if !bytes.Equal(subject[si:end], bn.prefix) {
|
||||
return nil, false
|
||||
}
|
||||
// Increment our subject index.
|
||||
si += len(bn.prefix)
|
||||
}
|
||||
if an := n.findChild(pivot(subject, si)); an != nil {
|
||||
n = *an
|
||||
} else {
|
||||
return nil, false
|
||||
}
|
||||
}
|
||||
return nil, false
|
||||
}
|
||||
|
||||
// Delete will delete the item and return its value, or not found if it did not exist.
|
||||
func (t *SubjectTree[T]) Delete(subject []byte) (*T, bool) {
|
||||
if t == nil {
|
||||
return nil, false
|
||||
}
|
||||
|
||||
val, deleted := t.delete(&t.root, subject, 0)
|
||||
if deleted {
|
||||
t.size--
|
||||
}
|
||||
return val, deleted
|
||||
}
|
||||
|
||||
// Match will match against a subject that can have wildcards and invoke the callback func for each matched value.
|
||||
func (t *SubjectTree[T]) Match(filter []byte, cb func(subject []byte, val *T)) {
|
||||
if t == nil || t.root == nil || len(filter) == 0 || cb == nil {
|
||||
return
|
||||
}
|
||||
// We need to break this up into chunks based on wildcards, either pwc '*' or fwc '>'.
|
||||
var raw [16][]byte
|
||||
parts := genParts(filter, raw[:0])
|
||||
var _pre [256]byte
|
||||
t.match(t.root, parts, _pre[:0], cb)
|
||||
}
|
||||
|
||||
// IterOrdered will walk all entries in the SubjectTree lexicographically. The callback can return false to terminate the walk.
|
||||
func (t *SubjectTree[T]) IterOrdered(cb func(subject []byte, val *T) bool) {
|
||||
if t == nil || t.root == nil {
|
||||
return
|
||||
}
|
||||
var _pre [256]byte
|
||||
t.iter(t.root, _pre[:0], true, cb)
|
||||
}
|
||||
|
||||
// IterFast will walk all entries in the SubjectTree with no guarantees of ordering. The callback can return false to terminate the walk.
|
||||
func (t *SubjectTree[T]) IterFast(cb func(subject []byte, val *T) bool) {
|
||||
if t == nil || t.root == nil {
|
||||
return
|
||||
}
|
||||
var _pre [256]byte
|
||||
t.iter(t.root, _pre[:0], false, cb)
|
||||
}
|
||||
|
||||
// Internal methods
|
||||
|
||||
// Internal call to insert that can be recursive.
|
||||
func (t *SubjectTree[T]) insert(np *node, subject []byte, value T, si int) (*T, bool) {
|
||||
n := *np
|
||||
if n == nil {
|
||||
*np = newLeaf(subject, value)
|
||||
return nil, false
|
||||
}
|
||||
if n.isLeaf() {
|
||||
ln := n.(*leaf[T])
|
||||
if ln.match(subject[si:]) {
|
||||
// Replace with new value.
|
||||
old := ln.value
|
||||
ln.value = value
|
||||
return &old, true
|
||||
}
|
||||
// Here we need to split this leaf.
|
||||
cpi := commonPrefixLen(ln.suffix, subject[si:])
|
||||
nn := newNode4(subject[si : si+cpi])
|
||||
ln.setSuffix(ln.suffix[cpi:])
|
||||
si += cpi
|
||||
// Make sure we have different pivot, normally this will be the case unless we have overflowing prefixes.
|
||||
if p := pivot(ln.suffix, 0); cpi > 0 && si < len(subject) && p == subject[si] {
|
||||
// We need to split the original leaf. Recursively call into insert.
|
||||
t.insert(np, subject, value, si)
|
||||
// Now add the update version of *np as a child to the new node4.
|
||||
nn.addChild(p, *np)
|
||||
} else {
|
||||
// Can just add this new leaf as a sibling.
|
||||
nl := newLeaf(subject[si:], value)
|
||||
nn.addChild(pivot(nl.suffix, 0), nl)
|
||||
// Add back original.
|
||||
nn.addChild(pivot(ln.suffix, 0), ln)
|
||||
}
|
||||
*np = nn
|
||||
return nil, false
|
||||
}
|
||||
|
||||
// Non-leaf nodes.
|
||||
bn := n.base()
|
||||
if len(bn.prefix) > 0 {
|
||||
cpi := commonPrefixLen(bn.prefix, subject[si:])
|
||||
if pli := len(bn.prefix); cpi >= pli {
|
||||
// Move past this node. We look for an existing child node to recurse into.
|
||||
// If one does not exist we can create a new leaf node.
|
||||
si += pli
|
||||
if nn := n.findChild(pivot(subject, si)); nn != nil {
|
||||
return t.insert(nn, subject, value, si)
|
||||
}
|
||||
if n.isFull() {
|
||||
n = n.grow()
|
||||
*np = n
|
||||
}
|
||||
n.addChild(pivot(subject, si), newLeaf(subject[si:], value))
|
||||
return nil, false
|
||||
} else {
|
||||
// We did not match the prefix completely here.
|
||||
// Calculate new prefix for this node.
|
||||
prefix := subject[si : si+cpi]
|
||||
si += len(prefix)
|
||||
// We will insert a new node4 and attach our current node below after adjusting prefix.
|
||||
nn := newNode4(prefix)
|
||||
// Shift the prefix for our original node.
|
||||
n.setPrefix(bn.prefix[cpi:])
|
||||
nn.addChild(pivot(bn.prefix[:], 0), n)
|
||||
// Add in our new leaf.
|
||||
nn.addChild(pivot(subject[si:], 0), newLeaf(subject[si:], value))
|
||||
// Update our node reference.
|
||||
*np = nn
|
||||
}
|
||||
} else {
|
||||
if nn := n.findChild(pivot(subject, si)); nn != nil {
|
||||
return t.insert(nn, subject, value, si)
|
||||
}
|
||||
// No prefix and no matched child, so add in new leafnode as needed.
|
||||
if n.isFull() {
|
||||
n = n.grow()
|
||||
*np = n
|
||||
}
|
||||
n.addChild(pivot(subject, si), newLeaf(subject[si:], value))
|
||||
}
|
||||
|
||||
return nil, false
|
||||
}
|
||||
|
||||
// internal function to recursively find the leaf to delete. Will do compaction if the item is found and removed.
|
||||
func (t *SubjectTree[T]) delete(np *node, subject []byte, si int) (*T, bool) {
|
||||
if t == nil || np == nil || *np == nil || len(subject) == 0 {
|
||||
return nil, false
|
||||
}
|
||||
n := *np
|
||||
if n.isLeaf() {
|
||||
ln := n.(*leaf[T])
|
||||
if ln.match(subject[si:]) {
|
||||
*np = nil
|
||||
return &ln.value, true
|
||||
}
|
||||
return nil, false
|
||||
}
|
||||
// Not a leaf node.
|
||||
if bn := n.base(); len(bn.prefix) > 0 {
|
||||
// subject could be shorter and would panic on bad index into subject slice.
|
||||
if len(subject) < si+len(bn.prefix) {
|
||||
return nil, false
|
||||
}
|
||||
if !bytes.Equal(subject[si:si+len(bn.prefix)], bn.prefix) {
|
||||
return nil, false
|
||||
}
|
||||
// Increment our subject index.
|
||||
si += len(bn.prefix)
|
||||
}
|
||||
p := pivot(subject, si)
|
||||
nna := n.findChild(p)
|
||||
if nna == nil {
|
||||
return nil, false
|
||||
}
|
||||
nn := *nna
|
||||
if nn.isLeaf() {
|
||||
ln := nn.(*leaf[T])
|
||||
if ln.match(subject[si:]) {
|
||||
n.deleteChild(p)
|
||||
|
||||
if sn := n.shrink(); sn != nil {
|
||||
bn := n.base()
|
||||
// Make sure to set cap so we force an append to copy below.
|
||||
pre := bn.prefix[:len(bn.prefix):len(bn.prefix)]
|
||||
// Need to fix up prefixes/suffixes.
|
||||
if sn.isLeaf() {
|
||||
ln := sn.(*leaf[T])
|
||||
// Make sure to set cap so we force an append to copy.
|
||||
ln.suffix = append(pre, ln.suffix...)
|
||||
} else {
|
||||
// We are a node here, we need to add in the old prefix.
|
||||
if len(pre) > 0 {
|
||||
bsn := sn.base()
|
||||
sn.setPrefix(append(pre, bsn.prefix...))
|
||||
}
|
||||
}
|
||||
*np = sn
|
||||
}
|
||||
|
||||
return &ln.value, true
|
||||
}
|
||||
return nil, false
|
||||
}
|
||||
return t.delete(nna, subject, si)
|
||||
}
|
||||
|
||||
// Internal function which can be called recursively to match all leaf nodes to a given filter subject which
|
||||
// once here has been decomposed to parts. These parts only care about wildcards, both pwc and fwc.
|
||||
func (t *SubjectTree[T]) match(n node, parts [][]byte, pre []byte, cb func(subject []byte, val *T)) {
|
||||
// Capture if we are sitting on a terminal fwc.
|
||||
var hasFWC bool
|
||||
if lp := len(parts); lp > 0 && len(parts[lp-1]) > 0 && parts[lp-1][0] == fwc {
|
||||
hasFWC = true
|
||||
}
|
||||
|
||||
for n != nil {
|
||||
nparts, matched := n.matchParts(parts)
|
||||
// Check if we did not match.
|
||||
if !matched {
|
||||
return
|
||||
}
|
||||
// We have matched here. If we are a leaf and have exhausted all parts or he have a FWC fire callback.
|
||||
if n.isLeaf() {
|
||||
if len(nparts) == 0 || (hasFWC && len(nparts) == 1) {
|
||||
ln := n.(*leaf[T])
|
||||
cb(append(pre, ln.suffix...), &ln.value)
|
||||
}
|
||||
return
|
||||
}
|
||||
// We have normal nodes here.
|
||||
// We need to append our prefix
|
||||
bn := n.base()
|
||||
if len(bn.prefix) > 0 {
|
||||
// Note that this append may reallocate, but it doesn't modify "pre" at the "match" callsite.
|
||||
pre = append(pre, bn.prefix...)
|
||||
}
|
||||
|
||||
// Check our remaining parts.
|
||||
if len(nparts) == 0 && !hasFWC {
|
||||
// We are a node with no parts left and we are not looking at a fwc.
|
||||
// We could have a leafnode with no suffix which would be a match.
|
||||
// We could also have a terminal pwc. Check for those here.
|
||||
var hasTermPWC bool
|
||||
if lp := len(parts); lp > 0 && len(parts[lp-1]) == 1 && parts[lp-1][0] == pwc {
|
||||
// If we are sitting on a terminal pwc, put the pwc back and continue.
|
||||
nparts = parts[len(parts)-1:]
|
||||
hasTermPWC = true
|
||||
}
|
||||
for _, cn := range n.children() {
|
||||
if cn == nil {
|
||||
continue
|
||||
}
|
||||
if cn.isLeaf() {
|
||||
ln := cn.(*leaf[T])
|
||||
if len(ln.suffix) == 0 {
|
||||
cb(append(pre, ln.suffix...), &ln.value)
|
||||
} else if hasTermPWC && bytes.IndexByte(ln.suffix, tsep) < 0 {
|
||||
cb(append(pre, ln.suffix...), &ln.value)
|
||||
}
|
||||
} else if hasTermPWC {
|
||||
// We have terminal pwc so call into match again with the child node.
|
||||
t.match(cn, nparts, pre, cb)
|
||||
}
|
||||
}
|
||||
// Return regardless.
|
||||
return
|
||||
}
|
||||
// If we are sitting on a terminal fwc, put back and continue.
|
||||
if hasFWC && len(nparts) == 0 {
|
||||
nparts = parts[len(parts)-1:]
|
||||
}
|
||||
|
||||
// Here we are a node type with a partial match.
|
||||
// Check if the first part is a wildcard.
|
||||
fp := nparts[0]
|
||||
p := pivot(fp, 0)
|
||||
// Check if we have a pwc/fwc part here. This will cause us to iterate.
|
||||
if len(fp) == 1 && (p == pwc || p == fwc) {
|
||||
// We need to iterate over all children here for the current node
|
||||
// to see if we match further down.
|
||||
for _, cn := range n.children() {
|
||||
if cn != nil {
|
||||
t.match(cn, nparts, pre, cb)
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
// Here we have normal traversal, so find the next child.
|
||||
nn := n.findChild(p)
|
||||
if nn == nil {
|
||||
return
|
||||
}
|
||||
n, parts = *nn, nparts
|
||||
}
|
||||
}
|
||||
|
||||
// Internal iter function to walk nodes in lexicographical order.
|
||||
func (t *SubjectTree[T]) iter(n node, pre []byte, ordered bool, cb func(subject []byte, val *T) bool) bool {
|
||||
if n.isLeaf() {
|
||||
ln := n.(*leaf[T])
|
||||
return cb(append(pre, ln.suffix...), &ln.value)
|
||||
}
|
||||
// We are normal node here.
|
||||
bn := n.base()
|
||||
// Note that this append may reallocate, but it doesn't modify "pre" at the "iter" callsite.
|
||||
pre = append(pre, bn.prefix...)
|
||||
// Not everything requires lexicographical sorting, so support a fast path for iterating in
|
||||
// whatever order the stree has things stored instead.
|
||||
if !ordered {
|
||||
for _, cn := range n.children() {
|
||||
if cn == nil {
|
||||
continue
|
||||
}
|
||||
if !t.iter(cn, pre, false, cb) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
// Collect nodes since unsorted.
|
||||
var _nodes [256]node
|
||||
nodes := _nodes[:0]
|
||||
for _, cn := range n.children() {
|
||||
if cn != nil {
|
||||
nodes = append(nodes, cn)
|
||||
}
|
||||
}
|
||||
// Now sort.
|
||||
slices.SortStableFunc(nodes, func(a, b node) int { return bytes.Compare(a.path(), b.path()) })
|
||||
// Now walk the nodes in order and call into next iter.
|
||||
for i := range nodes {
|
||||
if !t.iter(nodes[i], pre, true, cb) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
// LazyIntersect iterates the smaller of the two provided subject trees and
|
||||
// looks for matching entries in the other. It is lazy in that it does not
|
||||
// aggressively optimize against repeated walks, but is considerably faster
|
||||
// in most cases than intersecting against a potentially large sublist.
|
||||
func LazyIntersect[TL, TR any](tl *SubjectTree[TL], tr *SubjectTree[TR], cb func([]byte, *TL, *TR)) {
|
||||
if tl == nil || tr == nil || tl.root == nil || tr.root == nil {
|
||||
return
|
||||
}
|
||||
// Iterate over the smaller tree to reduce the number of rounds.
|
||||
if tl.Size() <= tr.Size() {
|
||||
tl.IterFast(func(key []byte, v1 *TL) bool {
|
||||
if v2, ok := tr.Find(key); ok {
|
||||
cb(key, v1, v2)
|
||||
}
|
||||
return true
|
||||
})
|
||||
} else {
|
||||
tr.IterFast(func(key []byte, v2 *TR) bool {
|
||||
if v1, ok := tl.Find(key); ok {
|
||||
cb(key, v1, v2)
|
||||
}
|
||||
return true
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// IntersectGSL will match all items in the given subject tree that
|
||||
// have interest expressed in the given sublist. The callback will only be called
|
||||
// once for each subject, regardless of overlapping subscriptions in the sublist.
|
||||
func IntersectGSL[T any, SL comparable](t *SubjectTree[T], sl *gsl.GenericSublist[SL], cb func(subject []byte, val *T)) {
|
||||
if t == nil || t.root == nil || sl == nil {
|
||||
return
|
||||
}
|
||||
var _pre [256]byte
|
||||
_intersectGSL(t.root, _pre[:0], sl, cb)
|
||||
}
|
||||
|
||||
func _intersectGSL[T any, SL comparable](n node, pre []byte, sl *gsl.GenericSublist[SL], cb func(subject []byte, val *T)) {
|
||||
if n.isLeaf() {
|
||||
ln := n.(*leaf[T])
|
||||
subj := append(pre, ln.suffix...)
|
||||
if sl.HasInterest(bytesToString(subj)) {
|
||||
cb(subj, &ln.value)
|
||||
}
|
||||
return
|
||||
}
|
||||
bn := n.base()
|
||||
pre = append(pre, bn.prefix...)
|
||||
for _, cn := range n.children() {
|
||||
if cn == nil {
|
||||
continue
|
||||
}
|
||||
subj := append(pre, cn.path()...)
|
||||
if !hasInterestForTokens(sl, subj, len(pre)) {
|
||||
continue
|
||||
}
|
||||
_intersectGSL(cn, pre, sl, cb)
|
||||
}
|
||||
}
|
||||
|
||||
// The subject tree can return partial tokens so we need to check starting interest
|
||||
// only from whole tokens when we encounter a tsep.
|
||||
func hasInterestForTokens[SL comparable](sl *gsl.GenericSublist[SL], subj []byte, since int) bool {
|
||||
for i := since; i < len(subj); i++ {
|
||||
if subj[i] == tsep {
|
||||
if !sl.HasInterestStartingIn(bytesToString(subj[:i])) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
// Note this will avoid a copy of the data used for the string, but it will also reference the existing slice's data pointer.
|
||||
// So this should be used sparingly when we know the encompassing byte slice's lifetime is the same.
|
||||
func bytesToString(b []byte) string {
|
||||
if len(b) == 0 {
|
||||
return ""
|
||||
}
|
||||
p := unsafe.SliceData(b)
|
||||
return unsafe.String(p, len(b))
|
||||
}
|
||||
+57
@@ -0,0 +1,57 @@
|
||||
// Copyright 2023-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 stree
|
||||
|
||||
// For subject matching.
|
||||
const (
|
||||
pwc = '*'
|
||||
fwc = '>'
|
||||
tsep = '.'
|
||||
)
|
||||
|
||||
// Determine index of common prefix. No match at all is 0, etc.
|
||||
func commonPrefixLen(s1, s2 []byte) int {
|
||||
limit := min(len(s1), len(s2))
|
||||
var i int
|
||||
for ; i < limit; i++ {
|
||||
if s1[i] != s2[i] {
|
||||
break
|
||||
}
|
||||
}
|
||||
return i
|
||||
}
|
||||
|
||||
// Helper to copy bytes.
|
||||
func copyBytes(src []byte) []byte {
|
||||
if len(src) == 0 {
|
||||
return nil
|
||||
}
|
||||
dst := make([]byte, len(src))
|
||||
copy(dst, src)
|
||||
return dst
|
||||
}
|
||||
|
||||
type position interface{ int | uint16 }
|
||||
|
||||
// No pivot available.
|
||||
const noPivot = byte(127)
|
||||
|
||||
// Can return 127 (DEL) if we have all the subject as prefixes.
|
||||
// We used to use 0, but when that was in the subject would cause infinite recursion in some situations.
|
||||
func pivot[N position](subject []byte, pos N) byte {
|
||||
if int(pos) >= len(subject) {
|
||||
return noPivot
|
||||
}
|
||||
return subject[pos]
|
||||
}
|
||||
Reference in New Issue
Block a user