build(deps): bump github.com/nats-io/nats-server/v2
Bumps [github.com/nats-io/nats-server/v2](https://github.com/nats-io/nats-server) from 2.10.12 to 2.10.14. - [Release notes](https://github.com/nats-io/nats-server/releases) - [Changelog](https://github.com/nats-io/nats-server/blob/main/.goreleaser.yml) - [Commits](https://github.com/nats-io/nats-server/compare/v2.10.12...v2.10.14) --- updated-dependencies: - dependency-name: github.com/nats-io/nats-server/v2 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] <support@github.com>
This commit is contained in:
committed by
Ralf Haferkamp
parent
38699ca1e1
commit
628b4bdbea
+1
-1
@@ -236,7 +236,7 @@ func (lx *lexer) peek() rune {
|
||||
// errorf stops all lexing by emitting an error and returning `nil`.
|
||||
// Note that any value that is a character is escaped if it's a special
|
||||
// character (new lines, tabs, etc.).
|
||||
func (lx *lexer) errorf(format string, values ...interface{}) stateFn {
|
||||
func (lx *lexer) errorf(format string, values ...any) stateFn {
|
||||
for i, value := range values {
|
||||
if v, ok := value.(rune); ok {
|
||||
values[i] = escapeSpecial(v)
|
||||
|
||||
+22
-22
@@ -36,14 +36,14 @@ import (
|
||||
)
|
||||
|
||||
type parser struct {
|
||||
mapping map[string]interface{}
|
||||
mapping map[string]any
|
||||
lx *lexer
|
||||
|
||||
// The current scoped context, can be array or map
|
||||
ctx interface{}
|
||||
ctx any
|
||||
|
||||
// stack of contexts, either map or array/slice stack
|
||||
ctxs []interface{}
|
||||
ctxs []any
|
||||
|
||||
// Keys stack
|
||||
keys []string
|
||||
@@ -58,10 +58,10 @@ type parser struct {
|
||||
pedantic bool
|
||||
}
|
||||
|
||||
// Parse will return a map of keys to interface{}, although concrete types
|
||||
// Parse will return a map of keys to any, although concrete types
|
||||
// underly them. The values supported are string, bool, int64, float64, DateTime.
|
||||
// Arrays and nested Maps are also supported.
|
||||
func Parse(data string) (map[string]interface{}, error) {
|
||||
func Parse(data string) (map[string]any, error) {
|
||||
p, err := parse(data, "", false)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@@ -70,7 +70,7 @@ func Parse(data string) (map[string]interface{}, error) {
|
||||
}
|
||||
|
||||
// ParseFile is a helper to open file, etc. and parse the contents.
|
||||
func ParseFile(fp string) (map[string]interface{}, error) {
|
||||
func ParseFile(fp string) (map[string]any, error) {
|
||||
data, err := os.ReadFile(fp)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error opening config file: %v", err)
|
||||
@@ -84,7 +84,7 @@ func ParseFile(fp string) (map[string]interface{}, error) {
|
||||
}
|
||||
|
||||
// ParseFileWithChecks is equivalent to ParseFile but runs in pedantic mode.
|
||||
func ParseFileWithChecks(fp string) (map[string]interface{}, error) {
|
||||
func ParseFileWithChecks(fp string) (map[string]any, error) {
|
||||
data, err := os.ReadFile(fp)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@@ -100,12 +100,12 @@ func ParseFileWithChecks(fp string) (map[string]interface{}, error) {
|
||||
|
||||
type token struct {
|
||||
item item
|
||||
value interface{}
|
||||
value any
|
||||
usedVariable bool
|
||||
sourceFile string
|
||||
}
|
||||
|
||||
func (t *token) Value() interface{} {
|
||||
func (t *token) Value() any {
|
||||
return t.value
|
||||
}
|
||||
|
||||
@@ -127,9 +127,9 @@ func (t *token) Position() int {
|
||||
|
||||
func parse(data, fp string, pedantic bool) (p *parser, err error) {
|
||||
p = &parser{
|
||||
mapping: make(map[string]interface{}),
|
||||
mapping: make(map[string]any),
|
||||
lx: lex(data),
|
||||
ctxs: make([]interface{}, 0, 4),
|
||||
ctxs: make([]any, 0, 4),
|
||||
keys: make([]string, 0, 4),
|
||||
ikeys: make([]item, 0, 4),
|
||||
fp: filepath.Dir(fp),
|
||||
@@ -160,12 +160,12 @@ func (p *parser) next() item {
|
||||
return p.lx.nextItem()
|
||||
}
|
||||
|
||||
func (p *parser) pushContext(ctx interface{}) {
|
||||
func (p *parser) pushContext(ctx any) {
|
||||
p.ctxs = append(p.ctxs, ctx)
|
||||
p.ctx = ctx
|
||||
}
|
||||
|
||||
func (p *parser) popContext() interface{} {
|
||||
func (p *parser) popContext() any {
|
||||
if len(p.ctxs) == 0 {
|
||||
panic("BUG in parser, context stack empty")
|
||||
}
|
||||
@@ -205,7 +205,7 @@ func (p *parser) popItemKey() item {
|
||||
}
|
||||
|
||||
func (p *parser) processItem(it item, fp string) error {
|
||||
setValue := func(it item, v interface{}) {
|
||||
setValue := func(it item, v any) {
|
||||
if p.pedantic {
|
||||
p.setValue(&token{it, v, false, fp})
|
||||
} else {
|
||||
@@ -226,7 +226,7 @@ func (p *parser) processItem(it item, fp string) error {
|
||||
p.pushItemKey(it)
|
||||
}
|
||||
case itemMapStart:
|
||||
newCtx := make(map[string]interface{})
|
||||
newCtx := make(map[string]any)
|
||||
p.pushContext(newCtx)
|
||||
case itemMapEnd:
|
||||
setValue(it, p.popContext())
|
||||
@@ -309,7 +309,7 @@ func (p *parser) processItem(it item, fp string) error {
|
||||
}
|
||||
setValue(it, dt)
|
||||
case itemArrayStart:
|
||||
var array = make([]interface{}, 0)
|
||||
var array = make([]any, 0)
|
||||
p.pushContext(array)
|
||||
case itemArrayEnd:
|
||||
array := p.ctx
|
||||
@@ -342,7 +342,7 @@ func (p *parser) processItem(it item, fp string) error {
|
||||
}
|
||||
case itemInclude:
|
||||
var (
|
||||
m map[string]interface{}
|
||||
m map[string]any
|
||||
err error
|
||||
)
|
||||
if p.pedantic {
|
||||
@@ -380,7 +380,7 @@ const bcryptPrefix = "2a$"
|
||||
// ignore array contexts and only process the map contexts..
|
||||
//
|
||||
// Returns true for ok if it finds something, similar to map.
|
||||
func (p *parser) lookupVariable(varReference string) (interface{}, bool, error) {
|
||||
func (p *parser) lookupVariable(varReference string) (any, bool, error) {
|
||||
// Do special check to see if it is a raw bcrypt string.
|
||||
if strings.HasPrefix(varReference, bcryptPrefix) {
|
||||
return "$" + varReference, true, nil
|
||||
@@ -390,7 +390,7 @@ func (p *parser) lookupVariable(varReference string) (interface{}, bool, error)
|
||||
for i := len(p.ctxs) - 1; i >= 0; i-- {
|
||||
ctx := p.ctxs[i]
|
||||
// Process if it is a map context
|
||||
if m, ok := ctx.(map[string]interface{}); ok {
|
||||
if m, ok := ctx.(map[string]any); ok {
|
||||
if v, ok := m[varReference]; ok {
|
||||
return v, ok, nil
|
||||
}
|
||||
@@ -411,17 +411,17 @@ func (p *parser) lookupVariable(varReference string) (interface{}, bool, error)
|
||||
return nil, false, nil
|
||||
}
|
||||
|
||||
func (p *parser) setValue(val interface{}) {
|
||||
func (p *parser) setValue(val any) {
|
||||
// Test to see if we are on an array or a map
|
||||
|
||||
// Array processing
|
||||
if ctx, ok := p.ctx.([]interface{}); ok {
|
||||
if ctx, ok := p.ctx.([]any); ok {
|
||||
p.ctx = append(ctx, val)
|
||||
p.ctxs[len(p.ctxs)-1] = p.ctx
|
||||
}
|
||||
|
||||
// Map processing
|
||||
if ctx, ok := p.ctx.(map[string]interface{}); ok {
|
||||
if ctx, ok := p.ctx.(map[string]any); ok {
|
||||
key := p.popKey()
|
||||
|
||||
if p.pedantic {
|
||||
|
||||
+1
-1
@@ -16,7 +16,7 @@ func Uint32() uint32
|
||||
//go:linkname Uint32n runtime.fastrandn
|
||||
func Uint32n(n uint32) uint32
|
||||
|
||||
// Uint32 returns a lock free uint64 value.
|
||||
// Uint64 returns a lock free uint64 value.
|
||||
func Uint64() uint64 {
|
||||
v := uint64(Uint32())
|
||||
return v<<32 | uint64(Uint32())
|
||||
|
||||
+7
-7
@@ -178,7 +178,7 @@ func (l *fileLogger) setMaxNumFiles(max int) {
|
||||
l.Unlock()
|
||||
}
|
||||
|
||||
func (l *fileLogger) logDirect(label, format string, v ...interface{}) int {
|
||||
func (l *fileLogger) logDirect(label, format string, v ...any) int {
|
||||
var entrya = [256]byte{}
|
||||
var entry = entrya[:0]
|
||||
if l.pid != "" {
|
||||
@@ -368,34 +368,34 @@ func setColoredLabelFormats(l *Logger) {
|
||||
}
|
||||
|
||||
// Noticef logs a notice statement
|
||||
func (l *Logger) Noticef(format string, v ...interface{}) {
|
||||
func (l *Logger) Noticef(format string, v ...any) {
|
||||
l.logger.Printf(l.infoLabel+format, v...)
|
||||
}
|
||||
|
||||
// Warnf logs a notice statement
|
||||
func (l *Logger) Warnf(format string, v ...interface{}) {
|
||||
func (l *Logger) Warnf(format string, v ...any) {
|
||||
l.logger.Printf(l.warnLabel+format, v...)
|
||||
}
|
||||
|
||||
// Errorf logs an error statement
|
||||
func (l *Logger) Errorf(format string, v ...interface{}) {
|
||||
func (l *Logger) Errorf(format string, v ...any) {
|
||||
l.logger.Printf(l.errorLabel+format, v...)
|
||||
}
|
||||
|
||||
// Fatalf logs a fatal error
|
||||
func (l *Logger) Fatalf(format string, v ...interface{}) {
|
||||
func (l *Logger) Fatalf(format string, v ...any) {
|
||||
l.logger.Fatalf(l.fatalLabel+format, v...)
|
||||
}
|
||||
|
||||
// Debugf logs a debug statement
|
||||
func (l *Logger) Debugf(format string, v ...interface{}) {
|
||||
func (l *Logger) Debugf(format string, v ...any) {
|
||||
if l.debug {
|
||||
l.logger.Printf(l.debugLabel+format, v...)
|
||||
}
|
||||
}
|
||||
|
||||
// Tracef logs a trace statement
|
||||
func (l *Logger) Tracef(format string, v ...interface{}) {
|
||||
func (l *Logger) Tracef(format string, v ...any) {
|
||||
if l.trace {
|
||||
l.logger.Printf(l.traceLabel+format, v...)
|
||||
}
|
||||
|
||||
+6
-6
@@ -99,34 +99,34 @@ func getNetworkAndAddr(fqn string) (network, addr string) {
|
||||
}
|
||||
|
||||
// Noticef logs a notice statement
|
||||
func (l *SysLogger) Noticef(format string, v ...interface{}) {
|
||||
func (l *SysLogger) Noticef(format string, v ...any) {
|
||||
l.writer.Notice(fmt.Sprintf(format, v...))
|
||||
}
|
||||
|
||||
// Warnf logs a warning statement
|
||||
func (l *SysLogger) Warnf(format string, v ...interface{}) {
|
||||
func (l *SysLogger) Warnf(format string, v ...any) {
|
||||
l.writer.Warning(fmt.Sprintf(format, v...))
|
||||
}
|
||||
|
||||
// Fatalf logs a fatal error
|
||||
func (l *SysLogger) Fatalf(format string, v ...interface{}) {
|
||||
func (l *SysLogger) Fatalf(format string, v ...any) {
|
||||
l.writer.Crit(fmt.Sprintf(format, v...))
|
||||
}
|
||||
|
||||
// Errorf logs an error statement
|
||||
func (l *SysLogger) Errorf(format string, v ...interface{}) {
|
||||
func (l *SysLogger) Errorf(format string, v ...any) {
|
||||
l.writer.Err(fmt.Sprintf(format, v...))
|
||||
}
|
||||
|
||||
// Debugf logs a debug statement
|
||||
func (l *SysLogger) Debugf(format string, v ...interface{}) {
|
||||
func (l *SysLogger) Debugf(format string, v ...any) {
|
||||
if l.debug {
|
||||
l.writer.Debug(fmt.Sprintf(format, v...))
|
||||
}
|
||||
}
|
||||
|
||||
// Tracef logs a trace statement
|
||||
func (l *SysLogger) Tracef(format string, v ...interface{}) {
|
||||
func (l *SysLogger) Tracef(format string, v ...any) {
|
||||
if l.trace {
|
||||
l.writer.Notice(fmt.Sprintf(format, v...))
|
||||
}
|
||||
|
||||
+7
-7
@@ -70,42 +70,42 @@ func NewRemoteSysLogger(fqn string, debug, trace bool) *SysLogger {
|
||||
}
|
||||
}
|
||||
|
||||
func formatMsg(tag, format string, v ...interface{}) string {
|
||||
func formatMsg(tag, format string, v ...any) string {
|
||||
orig := fmt.Sprintf(format, v...)
|
||||
return fmt.Sprintf("pid[%d][%s]: %s", os.Getpid(), tag, orig)
|
||||
}
|
||||
|
||||
// Noticef logs a notice statement
|
||||
func (l *SysLogger) Noticef(format string, v ...interface{}) {
|
||||
func (l *SysLogger) Noticef(format string, v ...any) {
|
||||
l.writer.Info(1, formatMsg("NOTICE", format, v...))
|
||||
}
|
||||
|
||||
// Warnf logs a warning statement
|
||||
func (l *SysLogger) Warnf(format string, v ...interface{}) {
|
||||
func (l *SysLogger) Warnf(format string, v ...any) {
|
||||
l.writer.Info(1, formatMsg("WARN", format, v...))
|
||||
}
|
||||
|
||||
// Fatalf logs a fatal error
|
||||
func (l *SysLogger) Fatalf(format string, v ...interface{}) {
|
||||
func (l *SysLogger) Fatalf(format string, v ...any) {
|
||||
msg := formatMsg("FATAL", format, v...)
|
||||
l.writer.Error(5, msg)
|
||||
panic(msg)
|
||||
}
|
||||
|
||||
// Errorf logs an error statement
|
||||
func (l *SysLogger) Errorf(format string, v ...interface{}) {
|
||||
func (l *SysLogger) Errorf(format string, v ...any) {
|
||||
l.writer.Error(2, formatMsg("ERROR", format, v...))
|
||||
}
|
||||
|
||||
// Debugf logs a debug statement
|
||||
func (l *SysLogger) Debugf(format string, v ...interface{}) {
|
||||
func (l *SysLogger) Debugf(format string, v ...any) {
|
||||
if l.debug {
|
||||
l.writer.Info(3, formatMsg("DEBUG", format, v...))
|
||||
}
|
||||
}
|
||||
|
||||
// Tracef logs a trace statement
|
||||
func (l *SysLogger) Tracef(format string, v ...interface{}) {
|
||||
func (l *SysLogger) Tracef(format string, v ...any) {
|
||||
if l.trace {
|
||||
l.writer.Info(4, formatMsg("TRACE", format, v...))
|
||||
}
|
||||
|
||||
+38
-34
@@ -859,24 +859,26 @@ func (a *Account) addClient(c *client) int {
|
||||
if a.clients != nil {
|
||||
a.clients[c] = struct{}{}
|
||||
}
|
||||
added := n != len(a.clients)
|
||||
if added {
|
||||
if c.kind != CLIENT && c.kind != LEAF {
|
||||
a.sysclients++
|
||||
} else if c.kind == LEAF {
|
||||
a.nleafs++
|
||||
}
|
||||
// If we did not add it, we are done
|
||||
if n == len(a.clients) {
|
||||
a.mu.Unlock()
|
||||
return n
|
||||
}
|
||||
if c.kind != CLIENT && c.kind != LEAF {
|
||||
a.sysclients++
|
||||
} else if c.kind == LEAF {
|
||||
a.nleafs++
|
||||
}
|
||||
a.mu.Unlock()
|
||||
|
||||
// If we added a new leaf use the list lock and add it to the list.
|
||||
if added && c.kind == LEAF {
|
||||
if c.kind == LEAF {
|
||||
a.lmu.Lock()
|
||||
a.lleafs = append(a.lleafs, c)
|
||||
a.lmu.Unlock()
|
||||
}
|
||||
|
||||
if c != nil && c.srv != nil && added {
|
||||
if c != nil && c.srv != nil {
|
||||
c.srv.accConnsUpdate(a)
|
||||
}
|
||||
|
||||
@@ -941,31 +943,33 @@ func (a *Account) removeClient(c *client) int {
|
||||
a.mu.Lock()
|
||||
n := len(a.clients)
|
||||
delete(a.clients, c)
|
||||
removed := n != len(a.clients)
|
||||
if removed {
|
||||
if c.kind != CLIENT && c.kind != LEAF {
|
||||
a.sysclients--
|
||||
} else if c.kind == LEAF {
|
||||
a.nleafs--
|
||||
// Need to do cluster accounting here.
|
||||
// Do cluster accounting if we are a hub.
|
||||
if c.isHubLeafNode() {
|
||||
cluster := c.remoteCluster()
|
||||
if count := a.leafClusters[cluster]; count > 1 {
|
||||
a.leafClusters[cluster]--
|
||||
} else if count == 1 {
|
||||
delete(a.leafClusters, cluster)
|
||||
}
|
||||
// If we did not actually remove it, we are done.
|
||||
if n == len(a.clients) {
|
||||
a.mu.Unlock()
|
||||
return n
|
||||
}
|
||||
if c.kind != CLIENT && c.kind != LEAF {
|
||||
a.sysclients--
|
||||
} else if c.kind == LEAF {
|
||||
a.nleafs--
|
||||
// Need to do cluster accounting here.
|
||||
// Do cluster accounting if we are a hub.
|
||||
if c.isHubLeafNode() {
|
||||
cluster := c.remoteCluster()
|
||||
if count := a.leafClusters[cluster]; count > 1 {
|
||||
a.leafClusters[cluster]--
|
||||
} else if count == 1 {
|
||||
delete(a.leafClusters, cluster)
|
||||
}
|
||||
}
|
||||
}
|
||||
a.mu.Unlock()
|
||||
|
||||
if removed && c.kind == LEAF {
|
||||
if c.kind == LEAF {
|
||||
a.removeLeafNode(c)
|
||||
}
|
||||
|
||||
if c != nil && c.srv != nil && removed {
|
||||
if c != nil && c.srv != nil {
|
||||
c.srv.accConnsUpdate(a)
|
||||
}
|
||||
|
||||
@@ -1117,7 +1121,7 @@ func (a *Account) TrackServiceExportWithSampling(service, results string, sampli
|
||||
}
|
||||
|
||||
// Now track down the imports and add in latency as needed to enable.
|
||||
s.accounts.Range(func(k, v interface{}) bool {
|
||||
s.accounts.Range(func(k, v any) bool {
|
||||
acc := v.(*Account)
|
||||
acc.mu.Lock()
|
||||
for _, im := range acc.imports.services {
|
||||
@@ -1158,7 +1162,7 @@ func (a *Account) UnTrackServiceExport(service string) {
|
||||
}
|
||||
|
||||
// Now track down the imports and clean them up.
|
||||
s.accounts.Range(func(k, v interface{}) bool {
|
||||
s.accounts.Range(func(k, v any) bool {
|
||||
acc := v.(*Account)
|
||||
acc.mu.Lock()
|
||||
for _, im := range acc.imports.services {
|
||||
@@ -3406,7 +3410,7 @@ func (s *Server) updateAccountClaimsWithRefresh(a *Account, ac *jwt.AccountClaim
|
||||
clients := map[*client]struct{}{}
|
||||
// We need to check all accounts that have an import claim from this account.
|
||||
awcsti := map[string]struct{}{}
|
||||
s.accounts.Range(func(k, v interface{}) bool {
|
||||
s.accounts.Range(func(k, v any) bool {
|
||||
acc := v.(*Account)
|
||||
// Move to the next if this account is actually account "a".
|
||||
if acc.Name == a.Name {
|
||||
@@ -3436,7 +3440,7 @@ func (s *Server) updateAccountClaimsWithRefresh(a *Account, ac *jwt.AccountClaim
|
||||
}
|
||||
// Now check if service exports have changed.
|
||||
if !a.checkServiceExportsEqual(old) || signersChanged || serviceTokenExpirationChanged {
|
||||
s.accounts.Range(func(k, v interface{}) bool {
|
||||
s.accounts.Range(func(k, v any) bool {
|
||||
acc := v.(*Account)
|
||||
// Move to the next if this account is actually account "a".
|
||||
if acc.Name == a.Name {
|
||||
@@ -3626,7 +3630,7 @@ func (s *Server) updateAccountClaimsWithRefresh(a *Account, ac *jwt.AccountClaim
|
||||
|
||||
if _, ok := s.incompleteAccExporterMap.Load(old.Name); ok && refreshImportingAccounts {
|
||||
s.incompleteAccExporterMap.Delete(old.Name)
|
||||
s.accounts.Range(func(key, value interface{}) bool {
|
||||
s.accounts.Range(func(key, value any) bool {
|
||||
acc := value.(*Account)
|
||||
acc.mu.RLock()
|
||||
incomplete := acc.incomplete
|
||||
@@ -3904,13 +3908,13 @@ func handleListRequest(store *DirJWTStore, s *Server, reply string) {
|
||||
} else {
|
||||
s.Debugf("list request responded with %d account ids", len(accIds))
|
||||
server := &ServerInfo{}
|
||||
response := map[string]interface{}{"server": server, "data": accIds}
|
||||
response := map[string]any{"server": server, "data": accIds}
|
||||
s.sendInternalMsgLocked(reply, _EMPTY_, server, response)
|
||||
}
|
||||
}
|
||||
|
||||
func handleDeleteRequest(store *DirJWTStore, s *Server, msg []byte, reply string) {
|
||||
var accIds []interface{}
|
||||
var accIds []any
|
||||
var subj, sysAccName string
|
||||
if sysAcc := s.SystemAccount(); sysAcc != nil {
|
||||
sysAccName = sysAcc.GetName()
|
||||
@@ -3927,7 +3931,7 @@ func handleDeleteRequest(store *DirJWTStore, s *Server, msg []byte, reply string
|
||||
err = fmt.Errorf("not trusted")
|
||||
} else if list, ok := gk.Data["accounts"]; !ok {
|
||||
err = fmt.Errorf("malformed request")
|
||||
} else if accIds, ok = list.([]interface{}); !ok {
|
||||
} else if accIds, ok = list.([]any); !ok {
|
||||
err = fmt.Errorf("malformed request")
|
||||
} else {
|
||||
for _, entry := range accIds {
|
||||
|
||||
+20
-3
@@ -15,6 +15,7 @@ package server
|
||||
|
||||
import (
|
||||
"crypto/sha256"
|
||||
"crypto/subtle"
|
||||
"crypto/tls"
|
||||
"crypto/x509/pkix"
|
||||
"encoding/asn1"
|
||||
@@ -83,9 +84,10 @@ func (u *User) clone() *User {
|
||||
}
|
||||
clone := &User{}
|
||||
*clone = *u
|
||||
// Account is not cloned because it is always by reference to an existing struct.
|
||||
clone.Permissions = u.Permissions.clone()
|
||||
|
||||
if len(u.AllowedConnectionTypes) > 0 {
|
||||
if u.AllowedConnectionTypes != nil {
|
||||
clone.AllowedConnectionTypes = make(map[string]struct{})
|
||||
for k, v := range u.AllowedConnectionTypes {
|
||||
clone.AllowedConnectionTypes[k] = v
|
||||
@@ -103,7 +105,16 @@ func (n *NkeyUser) clone() *NkeyUser {
|
||||
}
|
||||
clone := &NkeyUser{}
|
||||
*clone = *n
|
||||
// Account is not cloned because it is always by reference to an existing struct.
|
||||
clone.Permissions = n.Permissions.clone()
|
||||
|
||||
if n.AllowedConnectionTypes != nil {
|
||||
clone.AllowedConnectionTypes = make(map[string]struct{})
|
||||
for k, v := range n.AllowedConnectionTypes {
|
||||
clone.AllowedConnectionTypes[k] = v
|
||||
}
|
||||
}
|
||||
|
||||
return clone
|
||||
}
|
||||
|
||||
@@ -1418,8 +1429,14 @@ func comparePasswords(serverPassword, clientPassword string) bool {
|
||||
if err := bcrypt.CompareHashAndPassword([]byte(serverPassword), []byte(clientPassword)); err != nil {
|
||||
return false
|
||||
}
|
||||
} else if serverPassword != clientPassword {
|
||||
return false
|
||||
} else {
|
||||
// stringToBytes should be constant-time near enough compared to
|
||||
// turning a string into []byte normally.
|
||||
spass := stringToBytes(serverPassword)
|
||||
cpass := stringToBytes(clientPassword)
|
||||
if subtle.ConstantTimeCompare(spass, cpass) == 0 {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
+6
-6
@@ -121,11 +121,11 @@ func NewOCSPPeerConfig() *OCSPPeerConfig {
|
||||
|
||||
// Log is a neutral method of passing server loggers to plugins
|
||||
type Log struct {
|
||||
Debugf func(format string, v ...interface{})
|
||||
Noticef func(format string, v ...interface{})
|
||||
Warnf func(format string, v ...interface{})
|
||||
Errorf func(format string, v ...interface{})
|
||||
Tracef func(format string, v ...interface{})
|
||||
Debugf func(format string, v ...any)
|
||||
Noticef func(format string, v ...any)
|
||||
Warnf func(format string, v ...any)
|
||||
Errorf func(format string, v ...any)
|
||||
Tracef func(format string, v ...any)
|
||||
}
|
||||
|
||||
type CertInfo struct {
|
||||
@@ -145,7 +145,7 @@ For client, leaf spoke (remotes), and leaf hub connections, you may enable OCSP
|
||||
...
|
||||
# short form enables peer verify and takes option defaults
|
||||
ocsp_peer: true
|
||||
|
||||
|
||||
# long form includes settable options
|
||||
ocsp_peer {
|
||||
# Enable OCSP peer validation (default false)
|
||||
|
||||
+80
-15
@@ -1529,7 +1529,16 @@ func (c *client) flushOutbound() bool {
|
||||
return false
|
||||
}
|
||||
c.flags.set(flushOutbound)
|
||||
defer c.flags.clear(flushOutbound)
|
||||
defer func() {
|
||||
// Check flushAndClose() for explanation on why we do this.
|
||||
if c.isClosed() {
|
||||
for i := range c.out.wnb {
|
||||
nbPoolPut(c.out.wnb[i])
|
||||
}
|
||||
c.out.wnb = nil
|
||||
}
|
||||
c.flags.clear(flushOutbound)
|
||||
}()
|
||||
|
||||
// Check for nothing to do.
|
||||
if c.nc == nil || c.srv == nil || c.out.pb == 0 {
|
||||
@@ -1589,6 +1598,8 @@ func (c *client) flushOutbound() bool {
|
||||
}
|
||||
if err != nil {
|
||||
c.Errorf("Error compressing data: %v", err)
|
||||
// We need to grab the lock now before marking as closed and exiting
|
||||
c.mu.Lock()
|
||||
c.markConnAsClosed(WriteError)
|
||||
return false
|
||||
}
|
||||
@@ -1821,7 +1832,10 @@ func (c *client) markConnAsClosed(reason ClosedState) {
|
||||
// flushSignal will use server to queue the flush IO operation to a pool of flushers.
|
||||
// Lock must be held.
|
||||
func (c *client) flushSignal() {
|
||||
c.out.sg.Signal()
|
||||
// Check that sg is not nil, which will happen if the connection is closed.
|
||||
if c.out.sg != nil {
|
||||
c.out.sg.Signal()
|
||||
}
|
||||
}
|
||||
|
||||
// Traces a message.
|
||||
@@ -1849,7 +1863,7 @@ func (c *client) traceOutOp(op string, arg []byte) {
|
||||
}
|
||||
|
||||
func (c *client) traceOp(format, op string, arg []byte) {
|
||||
opa := []interface{}{}
|
||||
opa := []any{}
|
||||
if op != _EMPTY_ {
|
||||
opa = append(opa, op)
|
||||
}
|
||||
@@ -2065,7 +2079,8 @@ func (c *client) processConnect(arg []byte) error {
|
||||
}
|
||||
|
||||
// If no account designation.
|
||||
if c.acc == nil {
|
||||
// Do this only for CLIENT and LEAF connections.
|
||||
if c.acc == nil && (c.kind == CLIENT || c.kind == LEAF) {
|
||||
// By default register with the global account.
|
||||
c.registerWithAccount(srv.globalAccount())
|
||||
}
|
||||
@@ -2679,7 +2694,8 @@ func (c *client) processSubEx(subject, queue, bsid []byte, cb msgHandler, noForw
|
||||
sid := bytesToString(sub.sid)
|
||||
|
||||
// This check does not apply to SYSTEM or JETSTREAM or ACCOUNT clients (because they don't have a `nc`...)
|
||||
if c.isClosed() && (kind != SYSTEM && kind != JETSTREAM && kind != ACCOUNT) {
|
||||
// When a connection is closed though, we set c.subs to nil. So check for the map to not be nil.
|
||||
if (c.isClosed() && (kind != SYSTEM && kind != JETSTREAM && kind != ACCOUNT)) || (c.subs == nil) {
|
||||
c.mu.Unlock()
|
||||
return nil, ErrConnectionClosed
|
||||
}
|
||||
@@ -3649,7 +3665,7 @@ func (c *client) prunePubPermsCache() {
|
||||
}
|
||||
const maxPruneAtOnce = 1000
|
||||
r := 0
|
||||
c.perms.pcache.Range(func(k, _ interface{}) bool {
|
||||
c.perms.pcache.Range(func(k, _ any) bool {
|
||||
c.perms.pcache.Delete(k)
|
||||
if r++; (r > pruneSize && atomic.LoadInt32(&c.perms.pcsz) < int32(maxPermCacheSize)) ||
|
||||
(r > maxPruneAtOnce) {
|
||||
@@ -4008,6 +4024,34 @@ func removeHeaderIfPresent(hdr []byte, key string) []byte {
|
||||
return hdr
|
||||
}
|
||||
|
||||
func removeHeaderIfPrefixPresent(hdr []byte, prefix string) []byte {
|
||||
var index int
|
||||
for {
|
||||
if index >= len(hdr) {
|
||||
return hdr
|
||||
}
|
||||
|
||||
start := bytes.Index(hdr[index:], []byte(prefix))
|
||||
if start < 0 {
|
||||
return hdr
|
||||
}
|
||||
index += start
|
||||
if index < 1 || hdr[index-1] != '\n' {
|
||||
return hdr
|
||||
}
|
||||
|
||||
end := bytes.Index(hdr[index+len(prefix):], []byte(_CRLF_))
|
||||
if end < 0 {
|
||||
return hdr
|
||||
}
|
||||
|
||||
hdr = append(hdr[:index], hdr[index+end+len(prefix)+len(_CRLF_):]...)
|
||||
if len(hdr) <= len(emptyHdrLine) {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Generate a new header based on optional original header and key value.
|
||||
// More used in JetStream layers.
|
||||
func genHeader(hdr []byte, key, value string) []byte {
|
||||
@@ -4965,6 +5009,18 @@ func (c *client) flushAndClose(minimalFlush bool) {
|
||||
nbPoolPut(c.out.nb[i])
|
||||
}
|
||||
c.out.nb = nil
|
||||
// We can't touch c.out.wnb when a flushOutbound is in progress since it
|
||||
// is accessed outside the lock there. If in progress, the cleanup will be
|
||||
// done in flushOutbound when detecting that connection is closed.
|
||||
if !c.flags.isSet(flushOutbound) {
|
||||
for i := range c.out.wnb {
|
||||
nbPoolPut(c.out.wnb[i])
|
||||
}
|
||||
c.out.wnb = nil
|
||||
}
|
||||
// This seem to be important (from experimentation) for the GC to release
|
||||
// the connection.
|
||||
c.out.sg = nil
|
||||
|
||||
// Close the low level connection.
|
||||
if c.nc != nil {
|
||||
@@ -5143,6 +5199,9 @@ func (c *client) closeConnection(reason ClosedState) {
|
||||
if kind == CLIENT || kind == LEAF || kind == JETSTREAM {
|
||||
var _subs [32]*subscription
|
||||
subs = _subs[:0]
|
||||
// Do not set c.subs to nil or delete the sub from c.subs here because
|
||||
// it will be needed in saveClosedClient (which has been started as a
|
||||
// go routine in markConnAsClosed). Cleanup will be done there.
|
||||
for _, sub := range c.subs {
|
||||
// Auto-unsubscribe subscriptions must be unsubscribed forcibly.
|
||||
sub.max = 0
|
||||
@@ -5230,6 +5289,7 @@ func (c *client) reconnect() {
|
||||
gwName string
|
||||
gwIsOutbound bool
|
||||
gwCfg *gatewayCfg
|
||||
leafCfg *leafNodeCfg
|
||||
)
|
||||
|
||||
c.mu.Lock()
|
||||
@@ -5246,10 +5306,15 @@ func (c *client) reconnect() {
|
||||
retryImplicit = c.route.retry || (c.route.didSolicit && c.route.routeType == Implicit)
|
||||
}
|
||||
kind := c.kind
|
||||
if kind == GATEWAY {
|
||||
switch kind {
|
||||
case GATEWAY:
|
||||
gwName = c.gw.name
|
||||
gwIsOutbound = c.gw.outbound
|
||||
gwCfg = c.gw.cfg
|
||||
case LEAF:
|
||||
if c.isSolicitedLeafNode() {
|
||||
leafCfg = c.leaf.remote
|
||||
}
|
||||
}
|
||||
srv := c.srv
|
||||
c.mu.Unlock()
|
||||
@@ -5305,9 +5370,9 @@ func (c *client) reconnect() {
|
||||
} else {
|
||||
srv.Debugf("Gateway %q not in configuration, not attempting reconnect", gwName)
|
||||
}
|
||||
} else if c.isSolicitedLeafNode() {
|
||||
} else if leafCfg != nil {
|
||||
// Check if this is a solicited leaf node. Start up a reconnect.
|
||||
srv.startGoRoutine(func() { srv.reConnectToRemoteLeafNode(c.leaf.remote) })
|
||||
srv.startGoRoutine(func() { srv.reConnectToRemoteLeafNode(leafCfg) })
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5722,32 +5787,32 @@ func (c *client) Error(err error) {
|
||||
c.srv.Errors(c, err)
|
||||
}
|
||||
|
||||
func (c *client) Errorf(format string, v ...interface{}) {
|
||||
func (c *client) Errorf(format string, v ...any) {
|
||||
format = fmt.Sprintf("%s - %s", c, format)
|
||||
c.srv.Errorf(format, v...)
|
||||
}
|
||||
|
||||
func (c *client) Debugf(format string, v ...interface{}) {
|
||||
func (c *client) Debugf(format string, v ...any) {
|
||||
format = fmt.Sprintf("%s - %s", c, format)
|
||||
c.srv.Debugf(format, v...)
|
||||
}
|
||||
|
||||
func (c *client) Noticef(format string, v ...interface{}) {
|
||||
func (c *client) Noticef(format string, v ...any) {
|
||||
format = fmt.Sprintf("%s - %s", c, format)
|
||||
c.srv.Noticef(format, v...)
|
||||
}
|
||||
|
||||
func (c *client) Tracef(format string, v ...interface{}) {
|
||||
func (c *client) Tracef(format string, v ...any) {
|
||||
format = fmt.Sprintf("%s - %s", c, format)
|
||||
c.srv.Tracef(format, v...)
|
||||
}
|
||||
|
||||
func (c *client) Warnf(format string, v ...interface{}) {
|
||||
func (c *client) Warnf(format string, v ...any) {
|
||||
format = fmt.Sprintf("%s - %s", c, format)
|
||||
c.srv.Warnf(format, v...)
|
||||
}
|
||||
|
||||
func (c *client) RateLimitWarnf(format string, v ...interface{}) {
|
||||
func (c *client) RateLimitWarnf(format string, v ...any) {
|
||||
// Do the check before adding the client info to the format...
|
||||
statement := fmt.Sprintf(format, v...)
|
||||
if _, loaded := c.srv.rateLimitLogging.LoadOrStore(statement, time.Now()); loaded {
|
||||
|
||||
+1
-1
@@ -41,7 +41,7 @@ var (
|
||||
|
||||
const (
|
||||
// VERSION is the current version for the server.
|
||||
VERSION = "2.10.12"
|
||||
VERSION = "2.10.14"
|
||||
|
||||
// PROTO is the currently supported protocol.
|
||||
// 0 was the original
|
||||
|
||||
+237
-302
@@ -316,6 +316,7 @@ type consumer struct {
|
||||
stream string
|
||||
sseq uint64 // next stream sequence
|
||||
subjf subjectFilters // subject filters and their sequences
|
||||
filters *Sublist // When we have multiple filters we will use LoadNextMsgMulti and pass this in.
|
||||
dseq uint64 // delivered consumer sequence
|
||||
adflr uint64 // ack delivery floor
|
||||
asflr uint64 // ack store floor
|
||||
@@ -364,6 +365,7 @@ type consumer struct {
|
||||
created time.Time
|
||||
ldt time.Time
|
||||
lat time.Time
|
||||
lwqic time.Time
|
||||
closed bool
|
||||
|
||||
// Clustered.
|
||||
@@ -394,12 +396,8 @@ type consumer struct {
|
||||
// A single subject filter.
|
||||
type subjectFilter struct {
|
||||
subject string
|
||||
nextSeq uint64
|
||||
currentSeq uint64
|
||||
pmsg *jsPubMsg
|
||||
err error
|
||||
hasWildcard bool
|
||||
tokenizedSubject []string
|
||||
hasWildcard bool
|
||||
}
|
||||
|
||||
type subjectFilters []*subjectFilter
|
||||
@@ -503,7 +501,7 @@ func checkConsumerCfg(
|
||||
}
|
||||
|
||||
// Check if we have a BackOff defined that MaxDeliver is within range etc.
|
||||
if lbo := len(config.BackOff); lbo > 0 && config.MaxDeliver <= lbo {
|
||||
if lbo := len(config.BackOff); lbo > 0 && config.MaxDeliver != -1 && config.MaxDeliver <= lbo {
|
||||
return NewJSConsumerMaxDeliverBackoffError()
|
||||
}
|
||||
|
||||
@@ -600,13 +598,13 @@ func checkConsumerCfg(
|
||||
}
|
||||
subjectFilters := gatherSubjectFilters(config.FilterSubject, config.FilterSubjects)
|
||||
|
||||
// Check subject filters overlap.
|
||||
// Check subject filters do not overlap.
|
||||
for outer, subject := range subjectFilters {
|
||||
if !IsValidSubject(subject) {
|
||||
return NewJSStreamInvalidConfigError(ErrBadSubject)
|
||||
}
|
||||
for inner, ssubject := range subjectFilters {
|
||||
if inner != outer && subjectIsSubsetMatch(subject, ssubject) {
|
||||
if inner != outer && SubjectsCollide(subject, ssubject) {
|
||||
return NewJSConsumerOverlappingSubjectFiltersError()
|
||||
}
|
||||
}
|
||||
@@ -745,7 +743,6 @@ func (mset *stream) addConsumerWithAssignment(config *ConsumerConfig, oname stri
|
||||
if err := checkConsumerCfg(config, srvLim, &cfg, acc, &selectedLimits, isRecovering); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
sampleFreq := 0
|
||||
if config.SampleFrequency != _EMPTY_ {
|
||||
// Can't fail as checkConsumerCfg checks correct format
|
||||
@@ -785,7 +782,7 @@ func (mset *stream) addConsumerWithAssignment(config *ConsumerConfig, oname stri
|
||||
if action == ActionCreate && !reflect.DeepEqual(*config, eo.config()) {
|
||||
return nil, NewJSConsumerAlreadyExistsError()
|
||||
}
|
||||
// Check for overlapping subjects.
|
||||
// Check for overlapping subjects if we are a workqueue
|
||||
if mset.cfg.Retention == WorkQueuePolicy {
|
||||
subjects := gatherSubjectFilters(config.FilterSubject, config.FilterSubjects)
|
||||
if !mset.partitionUnique(cName, subjects) {
|
||||
@@ -941,8 +938,7 @@ func (mset *stream) addConsumerWithAssignment(config *ConsumerConfig, oname stri
|
||||
o.store = store
|
||||
}
|
||||
|
||||
subjects := gatherSubjectFilters(o.cfg.FilterSubject, o.cfg.FilterSubjects)
|
||||
for _, filter := range subjects {
|
||||
for _, filter := range gatherSubjectFilters(o.cfg.FilterSubject, o.cfg.FilterSubjects) {
|
||||
sub := &subjectFilter{
|
||||
subject: filter,
|
||||
hasWildcard: subjectHasWildcard(filter),
|
||||
@@ -951,6 +947,18 @@ func (mset *stream) addConsumerWithAssignment(config *ConsumerConfig, oname stri
|
||||
o.subjf = append(o.subjf, sub)
|
||||
}
|
||||
|
||||
// If we have multiple filter subjects, create a sublist which we will use
|
||||
// in calling store.LoadNextMsgMulti.
|
||||
if len(o.cfg.FilterSubjects) > 0 {
|
||||
o.filters = NewSublistWithCache()
|
||||
for _, filter := range o.cfg.FilterSubjects {
|
||||
o.filters.Insert(&subscription{subject: []byte(filter)})
|
||||
}
|
||||
} else {
|
||||
// Make sure this is nil otherwise.
|
||||
o.filters = nil
|
||||
}
|
||||
|
||||
if o.store != nil && o.store.HasState() {
|
||||
// Restore our saved state.
|
||||
o.mu.Lock()
|
||||
@@ -1354,6 +1362,10 @@ func (o *consumer) setLeader(isLeader bool) {
|
||||
} else if o.srv.gateway.enabled {
|
||||
stopAndClearTimer(&o.gwdtmr)
|
||||
}
|
||||
// If we were the leader make sure to drain queued up acks.
|
||||
if wasLeader {
|
||||
o.ackMsgs.drain()
|
||||
}
|
||||
o.mu.Unlock()
|
||||
|
||||
// Unregister as a leader with our parent stream.
|
||||
@@ -1798,7 +1810,7 @@ func (acc *Account) checkNewConsumerConfig(cfg, ncfg *ConsumerConfig) error {
|
||||
}
|
||||
|
||||
// Check if BackOff is defined, MaxDeliver is within range.
|
||||
if lbo := len(ncfg.BackOff); lbo > 0 && ncfg.MaxDeliver <= lbo {
|
||||
if lbo := len(ncfg.BackOff); lbo > 0 && ncfg.MaxDeliver != -1 && ncfg.MaxDeliver <= lbo {
|
||||
return NewJSConsumerMaxDeliverBackoffError()
|
||||
}
|
||||
|
||||
@@ -1877,16 +1889,6 @@ func (o *consumer) updateConfig(cfg *ConsumerConfig) error {
|
||||
hasWildcard: subjectHasWildcard(newFilter),
|
||||
tokenizedSubject: tokenizeSubjectIntoSlice(nil, newFilter),
|
||||
}
|
||||
// If given subject was present, we will retain its fields values
|
||||
// so `getNextMgs` can take advantage of already buffered `pmsgs`.
|
||||
for _, oldFilter := range o.subjf {
|
||||
if oldFilter.subject == newFilter {
|
||||
fs.currentSeq = oldFilter.currentSeq
|
||||
fs.nextSeq = oldFilter.nextSeq
|
||||
fs.pmsg = oldFilter.pmsg
|
||||
}
|
||||
continue
|
||||
}
|
||||
newSubjf = append(newSubjf, fs)
|
||||
}
|
||||
// Make sure we have correct signaling setup.
|
||||
@@ -1900,8 +1902,17 @@ func (o *consumer) updateConfig(cfg *ConsumerConfig) error {
|
||||
// If filters were removed, set `o.subjf` to nil.
|
||||
if len(newSubjf) == 0 {
|
||||
o.subjf = nil
|
||||
o.filters = nil
|
||||
} else {
|
||||
o.subjf = newSubjf
|
||||
if len(o.subjf) == 1 {
|
||||
o.filters = nil
|
||||
} else {
|
||||
o.filters = NewSublistWithCache()
|
||||
for _, filter := range o.subjf {
|
||||
o.filters.Insert(&subscription{subject: []byte(filter.subject)})
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2261,17 +2272,18 @@ func (o *consumer) releaseAnyPendingRequests(isAssigned bool) {
|
||||
if !isAssigned {
|
||||
hdr = []byte("NATS/1.0 409 Consumer Deleted\r\n\r\n")
|
||||
}
|
||||
|
||||
wq := o.waiting
|
||||
o.waiting = nil
|
||||
for i, rp := 0, wq.rp; i < wq.n; i++ {
|
||||
if wr := wq.reqs[rp]; wr != nil {
|
||||
if hdr != nil {
|
||||
o.outq.send(newJSPubMsg(wr.reply, _EMPTY_, _EMPTY_, hdr, nil, nil, 0))
|
||||
}
|
||||
wr.recycle()
|
||||
for wr := wq.head; wr != nil; {
|
||||
if hdr != nil {
|
||||
o.outq.send(newJSPubMsg(wr.reply, _EMPTY_, _EMPTY_, hdr, nil, nil, 0))
|
||||
}
|
||||
rp = (rp + 1) % cap(wq.reqs)
|
||||
next := wr.next
|
||||
wr.recycle()
|
||||
wr = next
|
||||
}
|
||||
// Nil out old queue.
|
||||
o.waiting = nil
|
||||
}
|
||||
|
||||
// Process a NAK.
|
||||
@@ -2410,7 +2422,7 @@ func (o *consumer) checkRedelivered(slseq uint64) {
|
||||
}
|
||||
var shouldUpdateState bool
|
||||
for sseq := range o.rdc {
|
||||
if sseq < o.asflr || (lseq > 0 && sseq > lseq) {
|
||||
if sseq <= o.asflr || (lseq > 0 && sseq > lseq) {
|
||||
delete(o.rdc, sseq)
|
||||
o.removeFromRedeliverQueue(sseq)
|
||||
shouldUpdateState = true
|
||||
@@ -2447,19 +2459,18 @@ func (o *consumer) applyState(state *ConsumerState) {
|
||||
return
|
||||
}
|
||||
|
||||
// If o.sseq is greater don't update. Don't go backwards on o.sseq.
|
||||
if o.sseq <= state.Delivered.Stream {
|
||||
// If o.sseq is greater don't update. Don't go backwards on o.sseq if leader.
|
||||
if !o.isLeader() || o.sseq <= state.Delivered.Stream {
|
||||
o.sseq = state.Delivered.Stream + 1
|
||||
}
|
||||
o.dseq = state.Delivered.Consumer + 1
|
||||
|
||||
o.adflr = state.AckFloor.Consumer
|
||||
o.asflr = state.AckFloor.Stream
|
||||
o.pending = state.Pending
|
||||
o.rdc = state.Redelivered
|
||||
|
||||
// Setup tracking timer if we have restored pending.
|
||||
if len(o.pending) > 0 {
|
||||
if o.isLeader() && len(o.pending) > 0 {
|
||||
// This is on startup or leader change. We want to check pending
|
||||
// sooner in case there are inconsistencies etc. Pick between 500ms - 1.5s
|
||||
delay := 500*time.Millisecond + time.Duration(rand.Int63n(1000))*time.Millisecond
|
||||
@@ -2695,6 +2706,12 @@ func (o *consumer) processAckMsg(sseq, dseq, dc uint64, doSample bool) {
|
||||
return
|
||||
}
|
||||
|
||||
mset := o.mset
|
||||
if mset == nil || mset.closed.Load() {
|
||||
o.mu.Unlock()
|
||||
return
|
||||
}
|
||||
|
||||
var sagap uint64
|
||||
var needSignal bool
|
||||
|
||||
@@ -2710,19 +2727,22 @@ func (o *consumer) processAckMsg(sseq, dseq, dc uint64, doSample bool) {
|
||||
delete(o.pending, sseq)
|
||||
// Use the original deliver sequence from our pending record.
|
||||
dseq = p.Sequence
|
||||
}
|
||||
if len(o.pending) == 0 {
|
||||
o.adflr, o.asflr = o.dseq-1, o.sseq-1
|
||||
} else if dseq == o.adflr+1 {
|
||||
o.adflr, o.asflr = dseq, sseq
|
||||
for ss := sseq + 1; ss < o.sseq; ss++ {
|
||||
if p, ok := o.pending[ss]; ok {
|
||||
if p.Sequence > 0 {
|
||||
o.adflr, o.asflr = p.Sequence-1, ss-1
|
||||
// Only move floors if we matched an existing pending.
|
||||
if dseq == o.adflr+1 {
|
||||
o.adflr, o.asflr = dseq, sseq
|
||||
for ss := sseq + 1; ss < o.sseq; ss++ {
|
||||
if p, ok := o.pending[ss]; ok {
|
||||
if p.Sequence > 0 {
|
||||
o.adflr, o.asflr = p.Sequence-1, ss-1
|
||||
}
|
||||
break
|
||||
}
|
||||
break
|
||||
}
|
||||
}
|
||||
// If nothing left set to current delivered.
|
||||
if len(o.pending) == 0 {
|
||||
o.adflr, o.asflr = o.dseq-1, o.sseq-1
|
||||
}
|
||||
}
|
||||
// We do these regardless.
|
||||
delete(o.rdc, sseq)
|
||||
@@ -2752,7 +2772,6 @@ func (o *consumer) processAckMsg(sseq, dseq, dc uint64, doSample bool) {
|
||||
// Update underlying store.
|
||||
o.updateAcks(dseq, sseq)
|
||||
|
||||
mset := o.mset
|
||||
clustered := o.node != nil
|
||||
|
||||
// In case retention changes for a stream, this ought to have been updated
|
||||
@@ -2918,12 +2937,13 @@ func nextReqFromMsg(msg []byte) (time.Time, int, int, bool, time.Duration, time.
|
||||
|
||||
// Represents a request that is on the internal waiting queue
|
||||
type waitingRequest struct {
|
||||
next *waitingRequest
|
||||
acc *Account
|
||||
interest string
|
||||
reply string
|
||||
n int // For batching
|
||||
d int
|
||||
b int // For max bytes tracking.
|
||||
d int // num delivered
|
||||
b int // For max bytes tracking
|
||||
expires time.Time
|
||||
received time.Time
|
||||
hb time.Duration
|
||||
@@ -2933,7 +2953,7 @@ type waitingRequest struct {
|
||||
|
||||
// sync.Pool for waiting requests.
|
||||
var wrPool = sync.Pool{
|
||||
New: func() interface{} {
|
||||
New: func() any {
|
||||
return new(waitingRequest)
|
||||
},
|
||||
}
|
||||
@@ -2950,21 +2970,22 @@ func (wr *waitingRequest) recycleIfDone() bool {
|
||||
// Force a recycle.
|
||||
func (wr *waitingRequest) recycle() {
|
||||
if wr != nil {
|
||||
wr.acc, wr.interest, wr.reply = nil, _EMPTY_, _EMPTY_
|
||||
wr.next, wr.acc, wr.interest, wr.reply = nil, nil, _EMPTY_, _EMPTY_
|
||||
wrPool.Put(wr)
|
||||
}
|
||||
}
|
||||
|
||||
// waiting queue for requests that are waiting for new messages to arrive.
|
||||
type waitQueue struct {
|
||||
rp, wp, n int
|
||||
last time.Time
|
||||
reqs []*waitingRequest
|
||||
n, max int
|
||||
last time.Time
|
||||
head *waitingRequest
|
||||
tail *waitingRequest
|
||||
}
|
||||
|
||||
// Create a new ring buffer with at most max items.
|
||||
func newWaitQueue(max int) *waitQueue {
|
||||
return &waitQueue{rp: -1, reqs: make([]*waitingRequest, max)}
|
||||
return &waitQueue{max: max}
|
||||
}
|
||||
|
||||
var (
|
||||
@@ -2980,14 +3001,16 @@ func (wq *waitQueue) add(wr *waitingRequest) error {
|
||||
if wq.isFull() {
|
||||
return errWaitQueueFull
|
||||
}
|
||||
wq.reqs[wq.wp] = wr
|
||||
// TODO(dlc) - Could make pow2 and get rid of mod.
|
||||
wq.wp = (wq.wp + 1) % cap(wq.reqs)
|
||||
|
||||
// Adjust read pointer if we were empty.
|
||||
if wq.rp < 0 {
|
||||
wq.rp = 0
|
||||
if wq.head == nil {
|
||||
wq.head = wr
|
||||
} else {
|
||||
wq.tail.next = wr
|
||||
}
|
||||
// Always set tail.
|
||||
wq.tail = wr
|
||||
// Make sure nil
|
||||
wr.next = nil
|
||||
|
||||
// Track last active via when we receive a request.
|
||||
wq.last = wr.received
|
||||
wq.n++
|
||||
@@ -2995,11 +3018,17 @@ func (wq *waitQueue) add(wr *waitingRequest) error {
|
||||
}
|
||||
|
||||
func (wq *waitQueue) isFull() bool {
|
||||
return wq.n == cap(wq.reqs)
|
||||
if wq == nil {
|
||||
return false
|
||||
}
|
||||
return wq.n == wq.max
|
||||
}
|
||||
|
||||
func (wq *waitQueue) isEmpty() bool {
|
||||
return wq.len() == 0
|
||||
if wq == nil {
|
||||
return true
|
||||
}
|
||||
return wq.n == 0
|
||||
}
|
||||
|
||||
func (wq *waitQueue) len() int {
|
||||
@@ -3014,11 +3043,7 @@ func (wq *waitQueue) peek() *waitingRequest {
|
||||
if wq == nil {
|
||||
return nil
|
||||
}
|
||||
var wr *waitingRequest
|
||||
if wq.rp >= 0 {
|
||||
wr = wq.reqs[wq.rp]
|
||||
}
|
||||
return wr
|
||||
return wq.head
|
||||
}
|
||||
|
||||
// pop will return the next request and move the read cursor.
|
||||
@@ -3028,7 +3053,6 @@ func (wq *waitQueue) pop() *waitingRequest {
|
||||
if wr != nil {
|
||||
wr.d++
|
||||
wr.n--
|
||||
|
||||
// Always remove current now on a pop, and move to end if still valid.
|
||||
// If we were the only one don't need to remove since this can be a no-op.
|
||||
if wr.n > 0 && wq.n > 1 {
|
||||
@@ -3043,33 +3067,30 @@ func (wq *waitQueue) pop() *waitingRequest {
|
||||
|
||||
// Removes the current read pointer (head FIFO) entry.
|
||||
func (wq *waitQueue) removeCurrent() {
|
||||
if wq.rp < 0 {
|
||||
return
|
||||
}
|
||||
wq.reqs[wq.rp] = nil
|
||||
wq.rp = (wq.rp + 1) % cap(wq.reqs)
|
||||
wq.n--
|
||||
// Check if we are empty.
|
||||
if wq.n == 0 {
|
||||
wq.rp, wq.wp = -1, 0
|
||||
}
|
||||
wq.remove(nil, wq.head)
|
||||
}
|
||||
|
||||
// Will compact when we have interior deletes.
|
||||
func (wq *waitQueue) compact() {
|
||||
if wq.isEmpty() {
|
||||
// Remove the wr element from the wait queue.
|
||||
func (wq *waitQueue) remove(pre, wr *waitingRequest) {
|
||||
if wr == nil {
|
||||
return
|
||||
}
|
||||
nreqs, i := make([]*waitingRequest, cap(wq.reqs)), 0
|
||||
for j, rp := 0, wq.rp; j < wq.n; j++ {
|
||||
if wr := wq.reqs[rp]; wr != nil {
|
||||
nreqs[i] = wr
|
||||
i++
|
||||
}
|
||||
rp = (rp + 1) % cap(wq.reqs)
|
||||
if pre != nil {
|
||||
pre.next = wr.next
|
||||
} else if wr == wq.head {
|
||||
// We are removing head here.
|
||||
wq.head = wr.next
|
||||
}
|
||||
// Reset here.
|
||||
wq.rp, wq.wp, wq.n, wq.reqs = 0, i, i, nreqs
|
||||
// Check if wr was our tail.
|
||||
if wr == wq.tail {
|
||||
// Check if we need to assign to pre.
|
||||
if wr.next == nil {
|
||||
wq.tail = pre
|
||||
} else {
|
||||
wq.tail = wr.next
|
||||
}
|
||||
}
|
||||
wq.n--
|
||||
}
|
||||
|
||||
// Return the map of pending requests keyed by the reply subject.
|
||||
@@ -3079,12 +3100,10 @@ func (o *consumer) pendingRequests() map[string]*waitingRequest {
|
||||
return nil
|
||||
}
|
||||
wq, m := o.waiting, make(map[string]*waitingRequest)
|
||||
for i, rp := 0, wq.rp; i < wq.n; i++ {
|
||||
if wr := wq.reqs[rp]; wr != nil {
|
||||
m[wr.reply] = wr
|
||||
}
|
||||
rp = (rp + 1) % cap(wq.reqs)
|
||||
for wr := wq.head; wr != nil; wr = wr.next {
|
||||
m[wr.reply] = wr
|
||||
}
|
||||
|
||||
return m
|
||||
}
|
||||
|
||||
@@ -3254,7 +3273,10 @@ func (o *consumer) processNextMsgRequest(reply string, msg []byte) {
|
||||
// If we have the max number of requests already pending try to expire.
|
||||
if o.waiting.isFull() {
|
||||
// Try to expire some of the requests.
|
||||
o.processWaiting(false)
|
||||
// We do not want to push too hard here so at maximum process once per sec.
|
||||
if time.Since(o.lwqic) > time.Second {
|
||||
o.processWaiting(false)
|
||||
}
|
||||
}
|
||||
|
||||
// If the request is for noWait and we have pending requests already, check if we have room.
|
||||
@@ -3483,100 +3505,39 @@ func (o *consumer) getNextMsg() (*jsPubMsg, uint64, error) {
|
||||
// Hold onto this since we release the lock.
|
||||
store := o.mset.store
|
||||
|
||||
// If no filters are specified, optimize to fetch just non-filtered messages.
|
||||
if len(o.subjf) == 0 {
|
||||
// Grab next message applicable to us.
|
||||
// We will unlock here in case lots of contention, e.g. WQ.
|
||||
o.mu.Unlock()
|
||||
pmsg := getJSPubMsgFromPool()
|
||||
sm, sseq, err := store.LoadNextMsg(_EMPTY_, false, o.sseq, &pmsg.StoreMsg)
|
||||
if sm == nil {
|
||||
pmsg.returnToPool()
|
||||
pmsg = nil
|
||||
}
|
||||
o.mu.Lock()
|
||||
if sseq >= o.sseq {
|
||||
o.sseq = sseq + 1
|
||||
if err == ErrStoreEOF {
|
||||
o.updateSkipped(o.sseq)
|
||||
}
|
||||
}
|
||||
return pmsg, 1, err
|
||||
}
|
||||
var sseq uint64
|
||||
var err error
|
||||
var sm *StoreMsg
|
||||
var pmsg = getJSPubMsgFromPool()
|
||||
|
||||
// if we have filters, iterate over filters and optimize by buffering found messages.
|
||||
for _, filter := range o.subjf {
|
||||
if filter.nextSeq < o.sseq {
|
||||
// o.subjf should always point to the right starting point for reading messages
|
||||
// if anything modified it, make sure our sequence do not start earlier.
|
||||
filter.nextSeq = o.sseq
|
||||
}
|
||||
// if this subject didn't fetch any message before, do it now
|
||||
if filter.pmsg == nil {
|
||||
// We will unlock here in case lots of contention, e.g. WQ.
|
||||
filterSubject, filterWC, nextSeq := filter.subject, filter.hasWildcard, filter.nextSeq
|
||||
o.mu.Unlock()
|
||||
pmsg := getJSPubMsgFromPool()
|
||||
sm, sseq, err := store.LoadNextMsg(filterSubject, filterWC, nextSeq, &pmsg.StoreMsg)
|
||||
o.mu.Lock()
|
||||
|
||||
filter.err = err
|
||||
|
||||
if sm != nil {
|
||||
filter.pmsg = pmsg
|
||||
} else {
|
||||
pmsg.returnToPool()
|
||||
pmsg = nil
|
||||
}
|
||||
if sseq >= filter.nextSeq {
|
||||
filter.nextSeq = sseq + 1
|
||||
}
|
||||
|
||||
// If we're sure that this filter has continuous sequence of messages, skip looking up other filters.
|
||||
if nextSeq == sseq && err != ErrStoreEOF {
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// Don't sort the o.subjf if it's only one entry
|
||||
// Sort uses `reflect` and can noticeably slow down fetching,
|
||||
// even if len == 0 or 1.
|
||||
// TODO(tp): we should have sort based off generics for server
|
||||
// to avoid reflection.
|
||||
if len(o.subjf) > 1 {
|
||||
sort.Slice(o.subjf, func(i, j int) bool {
|
||||
if o.subjf[j].pmsg != nil && o.subjf[i].pmsg == nil {
|
||||
return false
|
||||
}
|
||||
if o.subjf[i].pmsg != nil && o.subjf[j].pmsg == nil {
|
||||
return true
|
||||
}
|
||||
return o.subjf[j].nextSeq > o.subjf[i].nextSeq
|
||||
})
|
||||
}
|
||||
// Grab next message applicable to us.
|
||||
// Sort sequences first, to grab the first message.
|
||||
filter := o.subjf[0]
|
||||
err := filter.err
|
||||
// This means we got a message in this subject fetched.
|
||||
if filter.pmsg != nil {
|
||||
filter.currentSeq = filter.nextSeq
|
||||
o.sseq = filter.currentSeq
|
||||
returned := filter.pmsg
|
||||
filter.pmsg = nil
|
||||
return returned, 1, err
|
||||
// We will unlock here in case lots of contention, e.g. WQ.
|
||||
o.mu.Unlock()
|
||||
// Check if we are multi-filtered or not.
|
||||
if o.filters != nil {
|
||||
sm, sseq, err = store.LoadNextMsgMulti(o.filters, o.sseq, &pmsg.StoreMsg)
|
||||
} else if o.subjf != nil { // Means single filtered subject since o.filters means > 1.
|
||||
filter, wc := o.subjf[0].subject, o.subjf[0].hasWildcard
|
||||
sm, sseq, err = store.LoadNextMsg(filter, wc, o.sseq, &pmsg.StoreMsg)
|
||||
} else {
|
||||
// No filter here.
|
||||
sm, sseq, err = store.LoadNextMsg(_EMPTY_, false, o.sseq, &pmsg.StoreMsg)
|
||||
}
|
||||
if err == ErrStoreEOF {
|
||||
o.updateSkipped(filter.nextSeq)
|
||||
if sm == nil {
|
||||
pmsg.returnToPool()
|
||||
pmsg = nil
|
||||
}
|
||||
|
||||
// set o.sseq to the first subject sequence
|
||||
if filter.nextSeq > o.sseq {
|
||||
o.sseq = filter.nextSeq
|
||||
o.mu.Lock()
|
||||
// Check if we should move our o.sseq.
|
||||
if sseq >= o.sseq {
|
||||
// If we are moving step by step then sseq == o.sseq.
|
||||
// If we have jumped we should update skipped for other replicas.
|
||||
if sseq != o.sseq && err == ErrStoreEOF {
|
||||
o.updateSkipped(sseq + 1)
|
||||
}
|
||||
o.sseq = sseq + 1
|
||||
}
|
||||
return nil, 0, err
|
||||
return pmsg, 1, err
|
||||
}
|
||||
|
||||
// Will check for expiration and lack of interest on waiting requests.
|
||||
@@ -3586,35 +3547,31 @@ func (o *consumer) processWaiting(eos bool) (int, int, int, time.Time) {
|
||||
if o.srv == nil || o.waiting.isEmpty() {
|
||||
return 0, 0, 0, fexp
|
||||
}
|
||||
// Mark our last check time.
|
||||
o.lwqic = time.Now()
|
||||
|
||||
var expired, brp int
|
||||
s, now := o.srv, time.Now()
|
||||
|
||||
// Signals interior deletes, which we will compact if needed.
|
||||
var hid bool
|
||||
remove := func(wr *waitingRequest, i int) {
|
||||
if i == o.waiting.rp {
|
||||
o.waiting.removeCurrent()
|
||||
} else {
|
||||
o.waiting.reqs[i] = nil
|
||||
hid = true
|
||||
}
|
||||
wq := o.waiting
|
||||
remove := func(pre, wr *waitingRequest) *waitingRequest {
|
||||
expired++
|
||||
if o.node != nil {
|
||||
o.removeClusterPendingRequest(wr.reply)
|
||||
}
|
||||
expired++
|
||||
next := wr.next
|
||||
wq.remove(pre, wr)
|
||||
wr.recycle()
|
||||
return next
|
||||
}
|
||||
|
||||
wq := o.waiting
|
||||
for i, rp, n := 0, wq.rp, wq.n; i < n; rp = (rp + 1) % cap(wq.reqs) {
|
||||
wr := wq.reqs[rp]
|
||||
var pre *waitingRequest
|
||||
for wr := wq.head; wr != nil; {
|
||||
// Check expiration.
|
||||
if (eos && wr.noWait && wr.d > 0) || (!wr.expires.IsZero() && now.After(wr.expires)) {
|
||||
hdr := fmt.Appendf(nil, "NATS/1.0 408 Request Timeout\r\n%s: %d\r\n%s: %d\r\n\r\n", JSPullRequestPendingMsgs, wr.n, JSPullRequestPendingBytes, wr.b)
|
||||
o.outq.send(newJSPubMsg(wr.reply, _EMPTY_, _EMPTY_, hdr, nil, nil, 0))
|
||||
remove(wr, rp)
|
||||
i++
|
||||
wr = remove(pre, wr)
|
||||
continue
|
||||
}
|
||||
// Now check interest.
|
||||
@@ -3629,35 +3586,32 @@ func (o *consumer) processWaiting(eos bool) (int, int, int, time.Time) {
|
||||
interest = true
|
||||
}
|
||||
}
|
||||
|
||||
// If interest, update batch pending requests counter and update fexp timer.
|
||||
if interest {
|
||||
brp += wr.n
|
||||
if !wr.hbt.IsZero() {
|
||||
if now.After(wr.hbt) {
|
||||
// Fire off a heartbeat here.
|
||||
o.sendIdleHeartbeat(wr.reply)
|
||||
// Update next HB.
|
||||
wr.hbt = now.Add(wr.hb)
|
||||
}
|
||||
if fexp.IsZero() || wr.hbt.Before(fexp) {
|
||||
fexp = wr.hbt
|
||||
}
|
||||
}
|
||||
if !wr.expires.IsZero() && (fexp.IsZero() || wr.expires.Before(fexp)) {
|
||||
fexp = wr.expires
|
||||
}
|
||||
i++
|
||||
// Check if we have interest.
|
||||
if !interest {
|
||||
// No more interest here so go ahead and remove this one from our list.
|
||||
wr = remove(pre, wr)
|
||||
continue
|
||||
}
|
||||
// No more interest here so go ahead and remove this one from our list.
|
||||
remove(wr, rp)
|
||||
i++
|
||||
}
|
||||
|
||||
// If we have interior deletes from out of order invalidation, compact the waiting queue.
|
||||
if hid {
|
||||
o.waiting.compact()
|
||||
// If interest, update batch pending requests counter and update fexp timer.
|
||||
brp += wr.n
|
||||
if !wr.hbt.IsZero() {
|
||||
if now.After(wr.hbt) {
|
||||
// Fire off a heartbeat here.
|
||||
o.sendIdleHeartbeat(wr.reply)
|
||||
// Update next HB.
|
||||
wr.hbt = now.Add(wr.hb)
|
||||
}
|
||||
if fexp.IsZero() || wr.hbt.Before(fexp) {
|
||||
fexp = wr.hbt
|
||||
}
|
||||
}
|
||||
if !wr.expires.IsZero() && (fexp.IsZero() || wr.expires.Before(fexp)) {
|
||||
fexp = wr.expires
|
||||
}
|
||||
// Update pre and wr here.
|
||||
pre = wr
|
||||
wr = wr.next
|
||||
}
|
||||
|
||||
return expired, wq.len(), brp, fexp
|
||||
@@ -3745,6 +3699,11 @@ func (o *consumer) checkAckFloor() {
|
||||
o.mu.Lock()
|
||||
defer o.mu.Unlock()
|
||||
|
||||
// If we are closed do not change anything and simply return.
|
||||
if o.closed {
|
||||
return
|
||||
}
|
||||
|
||||
// If we are here, and this should be rare, we still are off with our ack floor.
|
||||
// We will set it explicitly to 1 behind our current lowest in pending, or if
|
||||
// pending is empty, to our current delivered -1.
|
||||
@@ -3762,6 +3721,10 @@ func (o *consumer) checkAckFloor() {
|
||||
if psseq < ss.FirstSeq-1 {
|
||||
psseq, pdseq = ss.FirstSeq-1, ss.FirstSeq-1
|
||||
}
|
||||
} else {
|
||||
// Since this was set via the pending, we should not include
|
||||
// it directly but set floors to -1.
|
||||
psseq, pdseq = psseq-1, pdseq-1
|
||||
}
|
||||
o.asflr, o.adflr = psseq, pdseq
|
||||
}
|
||||
@@ -3932,8 +3895,7 @@ func (o *consumer) loopAndGatherMsgs(qch chan struct{}) {
|
||||
pmsg, dc, err = o.getNextMsg()
|
||||
|
||||
// We can release the lock now under getNextMsg so need to check this condition again here.
|
||||
// consumer is closed when mset is set to nil.
|
||||
if o.mset == nil {
|
||||
if o.closed || o.mset == nil {
|
||||
o.mu.Unlock()
|
||||
return
|
||||
}
|
||||
@@ -3985,20 +3947,9 @@ func (o *consumer) loopAndGatherMsgs(qch chan struct{}) {
|
||||
wr.hbt = time.Now().Add(wr.hb)
|
||||
}
|
||||
} else {
|
||||
if o.subjf != nil {
|
||||
tsa := [32]string{}
|
||||
tts := tokenizeSubjectIntoSlice(tsa[:0], pmsg.subj)
|
||||
for i, filter := range o.subjf {
|
||||
if isSubsetMatchTokenized(tts, filter.tokenizedSubject) {
|
||||
o.subjf[i].currentSeq--
|
||||
o.subjf[i].nextSeq--
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
// We will redo this one.
|
||||
o.sseq--
|
||||
// We will redo this one as long as this is not a redelivery.
|
||||
if dc == 1 {
|
||||
o.sseq--
|
||||
o.npc++
|
||||
}
|
||||
pmsg.returnToPool()
|
||||
@@ -4184,50 +4135,51 @@ func (o *consumer) streamNumPending() uint64 {
|
||||
o.npc, o.npf = 0, 0
|
||||
return 0
|
||||
}
|
||||
npc, npf := o.calculateNumPending()
|
||||
o.npc, o.npf = int64(npc), npf
|
||||
return o.numPending()
|
||||
}
|
||||
|
||||
// Will calculate num pending but only requires a read lock.
|
||||
// Depends on delivery policy, for last per subject we calculate differently.
|
||||
// At least RLock should be held.
|
||||
func (o *consumer) calculateNumPending() (npc, npf uint64) {
|
||||
if o.mset == nil || o.mset.store == nil {
|
||||
return 0, 0
|
||||
}
|
||||
|
||||
isLastPerSubject := o.cfg.DeliverPolicy == DeliverLastPerSubject
|
||||
|
||||
// Deliver Last Per Subject calculates num pending differently.
|
||||
if isLastPerSubject {
|
||||
o.npc, o.npf = 0, 0
|
||||
// Consumer without filters.
|
||||
if o.subjf == nil {
|
||||
npc, npf := o.mset.store.NumPending(o.sseq, _EMPTY_, isLastPerSubject)
|
||||
o.npc, o.npf = int64(npc), npf
|
||||
return o.numPending()
|
||||
return o.mset.store.NumPending(o.sseq, _EMPTY_, isLastPerSubject)
|
||||
}
|
||||
// Consumer with filters.
|
||||
for _, filter := range o.subjf {
|
||||
npc, npf := o.mset.store.NumPending(o.sseq, filter.subject, isLastPerSubject)
|
||||
o.npc += int64(npc)
|
||||
if npf > o.npf {
|
||||
o.npf = npf // Always last
|
||||
lnpc, lnpf := o.mset.store.NumPending(o.sseq, filter.subject, isLastPerSubject)
|
||||
npc += lnpc
|
||||
if lnpf > npf {
|
||||
npf = lnpf // Always last
|
||||
}
|
||||
}
|
||||
return o.numPending()
|
||||
return npc, npf
|
||||
}
|
||||
// Every other Delivery Policy is handled here.
|
||||
// Consumer without filters.
|
||||
if o.subjf == nil {
|
||||
npc, npf := o.mset.store.NumPending(o.sseq, o.cfg.FilterSubject, isLastPerSubject)
|
||||
o.npc, o.npf = int64(npc), npf
|
||||
return o.numPending()
|
||||
return o.mset.store.NumPending(o.sseq, _EMPTY_, false)
|
||||
}
|
||||
// Consumer with filters.
|
||||
o.npc, o.npf = 0, 0
|
||||
for _, filter := range o.subjf {
|
||||
// We might loose state of o.subjf, so if we do recover from o.sseq
|
||||
if filter.currentSeq < o.sseq {
|
||||
filter.currentSeq = o.sseq
|
||||
}
|
||||
npc, npf := o.mset.store.NumPending(filter.currentSeq, filter.subject, isLastPerSubject)
|
||||
o.npc += int64(npc)
|
||||
if npf > o.npf {
|
||||
o.npf = npf // Always last
|
||||
lnpc, lnpf := o.mset.store.NumPending(o.sseq, filter.subject, false)
|
||||
npc += lnpc
|
||||
if lnpf > npf {
|
||||
npf = lnpf // Always last
|
||||
}
|
||||
}
|
||||
|
||||
return o.numPending()
|
||||
return npc, npf
|
||||
}
|
||||
|
||||
func convertToHeadersOnly(pmsg *jsPubMsg) {
|
||||
@@ -4279,6 +4231,10 @@ func (o *consumer) deliverMsg(dsubj, ackReply string, pmsg *jsPubMsg, dc uint64,
|
||||
|
||||
// Cant touch pmsg after this sending so capture what we need.
|
||||
seq, ts := pmsg.seq, pmsg.ts
|
||||
|
||||
// Update delivered first.
|
||||
o.updateDelivered(dseq, seq, dc, ts)
|
||||
|
||||
// Send message.
|
||||
o.outq.send(pmsg)
|
||||
|
||||
@@ -4299,9 +4255,6 @@ func (o *consumer) deliverMsg(dsubj, ackReply string, pmsg *jsPubMsg, dc uint64,
|
||||
o.waiting.last = time.Now()
|
||||
}
|
||||
|
||||
// FIXME(dlc) - Capture errors?
|
||||
o.updateDelivered(dseq, seq, dc, ts)
|
||||
|
||||
// If we are ack none and mset is interest only we should make sure stream removes interest.
|
||||
if ap == AckNone && rp != LimitsPolicy {
|
||||
if o.node == nil || o.cfg.Direct {
|
||||
@@ -4406,15 +4359,13 @@ func (o *consumer) trackPending(sseq, dseq uint64) {
|
||||
// Credit back a failed delivery.
|
||||
// lock should be held.
|
||||
func (o *consumer) creditWaitingRequest(reply string) {
|
||||
for i, rp := 0, o.waiting.rp; i < o.waiting.n; i++ {
|
||||
if wr := o.waiting.reqs[rp]; wr != nil {
|
||||
if wr.reply == reply {
|
||||
wr.n++
|
||||
wr.d--
|
||||
return
|
||||
}
|
||||
wq := o.waiting
|
||||
for wr := wq.head; wr != nil; wr = wr.next {
|
||||
if wr.reply == reply {
|
||||
wr.n++
|
||||
wr.d--
|
||||
return
|
||||
}
|
||||
rp = (rp + 1) % cap(o.waiting.reqs)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4440,7 +4391,7 @@ func (o *consumer) didNotDeliver(seq uint64, subj string) {
|
||||
if _, ok := o.pending[seq]; ok {
|
||||
// We found this messsage on pending, we need
|
||||
// to queue it up for immediate redelivery since
|
||||
// we know it was not delivered.
|
||||
// we know it was not delivered
|
||||
if !o.onRedeliverQueue(seq) {
|
||||
o.addToRedeliverQueue(seq)
|
||||
o.signalNewMessages()
|
||||
@@ -4546,7 +4497,11 @@ func (o *consumer) checkPending() {
|
||||
check := len(o.pending) > 1024
|
||||
for seq, p := range o.pending {
|
||||
if check && atomic.LoadInt64(&o.awl) > 0 {
|
||||
o.ptmr.Reset(100 * time.Millisecond)
|
||||
if o.ptmr == nil {
|
||||
o.ptmr = time.AfterFunc(100*time.Millisecond, o.checkPending)
|
||||
} else {
|
||||
o.ptmr.Reset(100 * time.Millisecond)
|
||||
}
|
||||
return
|
||||
}
|
||||
// Check if these are no longer valid.
|
||||
@@ -4740,7 +4695,6 @@ func (o *consumer) selectStartingSeqNo() {
|
||||
// If we are partitioned here this will be properly set when we become leader.
|
||||
for _, filter := range o.subjf {
|
||||
ss := o.mset.store.FilteredState(1, filter.subject)
|
||||
filter.nextSeq = ss.Last
|
||||
if ss.Last > o.sseq {
|
||||
o.sseq = ss.Last
|
||||
}
|
||||
@@ -4821,30 +4775,11 @@ func (o *consumer) selectStartingSeqNo() {
|
||||
|
||||
if state.FirstSeq == 0 {
|
||||
o.sseq = 1
|
||||
for _, filter := range o.subjf {
|
||||
filter.nextSeq = 1
|
||||
}
|
||||
} else if o.sseq < state.FirstSeq {
|
||||
o.sseq = state.FirstSeq
|
||||
} else if o.sseq > state.LastSeq {
|
||||
o.sseq = state.LastSeq + 1
|
||||
}
|
||||
for _, filter := range o.subjf {
|
||||
if state.FirstSeq == 0 {
|
||||
filter.nextSeq = 1
|
||||
}
|
||||
if filter.nextSeq < state.FirstSeq {
|
||||
filter.nextSeq = state.FirstSeq
|
||||
}
|
||||
if filter.nextSeq > state.LastSeq {
|
||||
filter.nextSeq = state.LastSeq + 1
|
||||
}
|
||||
}
|
||||
}
|
||||
if o.subjf != nil {
|
||||
sort.Slice(o.subjf, func(i, j int) bool {
|
||||
return o.subjf[j].nextSeq > o.subjf[i].nextSeq
|
||||
})
|
||||
}
|
||||
|
||||
// Always set delivery sequence to 1.
|
||||
@@ -5290,7 +5225,7 @@ func (o *consumer) decStreamPending(sseq uint64, subj string) {
|
||||
|
||||
// If it was pending process it like an ack.
|
||||
if wasPending {
|
||||
// We could have lock for stream so do this in a go routine.
|
||||
// We could have the lock for the stream so do this in a go routine.
|
||||
// TODO(dlc) - We should do this with ipq vs naked go routines.
|
||||
go o.processTerm(sseq, p.Sequence, rdc, ackTermUnackedLimitsReason)
|
||||
}
|
||||
@@ -5421,7 +5356,7 @@ func (o *consumer) checkStateForInterestStream() error {
|
||||
// See if we need to process this update if our parent stream is not a limits policy stream.
|
||||
mset := o.mset
|
||||
shouldProcessState := mset != nil && o.retention != LimitsPolicy
|
||||
if o.closed || !shouldProcessState {
|
||||
if o.closed || !shouldProcessState || o.store == nil {
|
||||
o.mu.RUnlock()
|
||||
return nil
|
||||
}
|
||||
@@ -5448,7 +5383,7 @@ func (o *consumer) checkStateForInterestStream() error {
|
||||
return errAckFloorHigherThanLastSeq
|
||||
}
|
||||
|
||||
for seq := ss.FirstSeq; seq <= asflr; seq++ {
|
||||
for seq := ss.FirstSeq; asflr > 0 && seq <= asflr; seq++ {
|
||||
mset.ackMsg(o, seq)
|
||||
}
|
||||
|
||||
|
||||
+3
-3
@@ -103,7 +103,7 @@ func newDir(dirPath string, create bool) (string, error) {
|
||||
}
|
||||
|
||||
// future proofing in case new options will be added
|
||||
type dirJWTStoreOption interface{}
|
||||
type dirJWTStoreOption any
|
||||
|
||||
// Creates a directory based jwt store.
|
||||
// Reads files only, does NOT watch directories and files.
|
||||
@@ -598,7 +598,7 @@ func (q *expirationTracker) Swap(i, j int) {
|
||||
pq[j].index = j
|
||||
}
|
||||
|
||||
func (q *expirationTracker) Push(x interface{}) {
|
||||
func (q *expirationTracker) Push(x any) {
|
||||
n := len(q.heap)
|
||||
item := x.(*jwtItem)
|
||||
item.index = n
|
||||
@@ -606,7 +606,7 @@ func (q *expirationTracker) Push(x interface{}) {
|
||||
q.idx[item.publicKey] = q.lru.PushBack(item)
|
||||
}
|
||||
|
||||
func (q *expirationTracker) Pop() interface{} {
|
||||
func (q *expirationTracker) Pop() any {
|
||||
old := q.heap
|
||||
n := len(old)
|
||||
item := old[n-1]
|
||||
|
||||
+1
-1
@@ -318,7 +318,7 @@ type errCtx struct {
|
||||
ctx string
|
||||
}
|
||||
|
||||
func NewErrorCtx(err error, format string, args ...interface{}) error {
|
||||
func NewErrorCtx(err error, format string, args ...any) error {
|
||||
return &errCtx{err, fmt.Sprintf(format, args...)}
|
||||
}
|
||||
|
||||
|
||||
+41
-41
@@ -356,7 +356,7 @@ type pubMsg struct {
|
||||
rply string
|
||||
si *ServerInfo
|
||||
hdr map[string]string
|
||||
msg interface{}
|
||||
msg any
|
||||
oct compressionType
|
||||
echo bool
|
||||
last bool
|
||||
@@ -365,7 +365,7 @@ type pubMsg struct {
|
||||
var pubMsgPool sync.Pool
|
||||
|
||||
func newPubMsg(c *client, sub, rply string, si *ServerInfo, hdr map[string]string,
|
||||
msg interface{}, oct compressionType, echo, last bool) *pubMsg {
|
||||
msg any, oct compressionType, echo, last bool) *pubMsg {
|
||||
|
||||
var m *pubMsg
|
||||
pm := pubMsgPool.Get()
|
||||
@@ -623,12 +623,12 @@ func (s *Server) sendShutdownEvent() {
|
||||
}
|
||||
|
||||
// Used to send an internal message to an arbitrary account.
|
||||
func (s *Server) sendInternalAccountMsg(a *Account, subject string, msg interface{}) error {
|
||||
func (s *Server) sendInternalAccountMsg(a *Account, subject string, msg any) error {
|
||||
return s.sendInternalAccountMsgWithReply(a, subject, _EMPTY_, nil, msg, false)
|
||||
}
|
||||
|
||||
// Used to send an internal message with an optional reply to an arbitrary account.
|
||||
func (s *Server) sendInternalAccountMsgWithReply(a *Account, subject, reply string, hdr map[string]string, msg interface{}, echo bool) error {
|
||||
func (s *Server) sendInternalAccountMsgWithReply(a *Account, subject, reply string, hdr map[string]string, msg any, echo bool) error {
|
||||
s.mu.RLock()
|
||||
if s.sys == nil || s.sys.sendq == nil {
|
||||
s.mu.RUnlock()
|
||||
@@ -665,7 +665,7 @@ func (s *Server) sendInternalAccountSysMsg(a *Account, subj string, si *ServerIn
|
||||
|
||||
// This will queue up a message to be sent.
|
||||
// Lock should not be held.
|
||||
func (s *Server) sendInternalMsgLocked(subj, rply string, si *ServerInfo, msg interface{}) {
|
||||
func (s *Server) sendInternalMsgLocked(subj, rply string, si *ServerInfo, msg any) {
|
||||
s.mu.RLock()
|
||||
s.sendInternalMsg(subj, rply, si, msg)
|
||||
s.mu.RUnlock()
|
||||
@@ -673,7 +673,7 @@ func (s *Server) sendInternalMsgLocked(subj, rply string, si *ServerInfo, msg in
|
||||
|
||||
// This will queue up a message to be sent.
|
||||
// Assumes lock is held on entry.
|
||||
func (s *Server) sendInternalMsg(subj, rply string, si *ServerInfo, msg interface{}) {
|
||||
func (s *Server) sendInternalMsg(subj, rply string, si *ServerInfo, msg any) {
|
||||
if s.sys == nil || s.sys.sendq == nil {
|
||||
return
|
||||
}
|
||||
@@ -692,7 +692,7 @@ func (s *Server) sendInternalResponse(subj string, response *ServerAPIResponse)
|
||||
}
|
||||
|
||||
// Used to send internal messages from other system clients to avoid no echo issues.
|
||||
func (c *client) sendInternalMsg(subj, rply string, si *ServerInfo, msg interface{}) {
|
||||
func (c *client) sendInternalMsg(subj, rply string, si *ServerInfo, msg any) {
|
||||
if c == nil {
|
||||
return
|
||||
}
|
||||
@@ -1082,43 +1082,43 @@ func (s *Server) initEventTracking() {
|
||||
"STATSZ": s.statszReq,
|
||||
"VARZ": func(sub *subscription, c *client, _ *Account, subject, reply string, hdr, msg []byte) {
|
||||
optz := &VarzEventOptions{}
|
||||
s.zReq(c, reply, hdr, msg, &optz.EventFilterOptions, optz, func() (interface{}, error) { return s.Varz(&optz.VarzOptions) })
|
||||
s.zReq(c, reply, hdr, msg, &optz.EventFilterOptions, optz, func() (any, error) { return s.Varz(&optz.VarzOptions) })
|
||||
},
|
||||
"SUBSZ": func(sub *subscription, c *client, _ *Account, subject, reply string, hdr, msg []byte) {
|
||||
optz := &SubszEventOptions{}
|
||||
s.zReq(c, reply, hdr, msg, &optz.EventFilterOptions, optz, func() (interface{}, error) { return s.Subsz(&optz.SubszOptions) })
|
||||
s.zReq(c, reply, hdr, msg, &optz.EventFilterOptions, optz, func() (any, error) { return s.Subsz(&optz.SubszOptions) })
|
||||
},
|
||||
"CONNZ": func(sub *subscription, c *client, _ *Account, subject, reply string, hdr, msg []byte) {
|
||||
optz := &ConnzEventOptions{}
|
||||
s.zReq(c, reply, hdr, msg, &optz.EventFilterOptions, optz, func() (interface{}, error) { return s.Connz(&optz.ConnzOptions) })
|
||||
s.zReq(c, reply, hdr, msg, &optz.EventFilterOptions, optz, func() (any, error) { return s.Connz(&optz.ConnzOptions) })
|
||||
},
|
||||
"ROUTEZ": func(sub *subscription, c *client, _ *Account, subject, reply string, hdr, msg []byte) {
|
||||
optz := &RoutezEventOptions{}
|
||||
s.zReq(c, reply, hdr, msg, &optz.EventFilterOptions, optz, func() (interface{}, error) { return s.Routez(&optz.RoutezOptions) })
|
||||
s.zReq(c, reply, hdr, msg, &optz.EventFilterOptions, optz, func() (any, error) { return s.Routez(&optz.RoutezOptions) })
|
||||
},
|
||||
"GATEWAYZ": func(sub *subscription, c *client, _ *Account, subject, reply string, hdr, msg []byte) {
|
||||
optz := &GatewayzEventOptions{}
|
||||
s.zReq(c, reply, hdr, msg, &optz.EventFilterOptions, optz, func() (interface{}, error) { return s.Gatewayz(&optz.GatewayzOptions) })
|
||||
s.zReq(c, reply, hdr, msg, &optz.EventFilterOptions, optz, func() (any, error) { return s.Gatewayz(&optz.GatewayzOptions) })
|
||||
},
|
||||
"LEAFZ": func(sub *subscription, c *client, _ *Account, subject, reply string, hdr, msg []byte) {
|
||||
optz := &LeafzEventOptions{}
|
||||
s.zReq(c, reply, hdr, msg, &optz.EventFilterOptions, optz, func() (interface{}, error) { return s.Leafz(&optz.LeafzOptions) })
|
||||
s.zReq(c, reply, hdr, msg, &optz.EventFilterOptions, optz, func() (any, error) { return s.Leafz(&optz.LeafzOptions) })
|
||||
},
|
||||
"ACCOUNTZ": func(sub *subscription, c *client, _ *Account, subject, reply string, hdr, msg []byte) {
|
||||
optz := &AccountzEventOptions{}
|
||||
s.zReq(c, reply, hdr, msg, &optz.EventFilterOptions, optz, func() (interface{}, error) { return s.Accountz(&optz.AccountzOptions) })
|
||||
s.zReq(c, reply, hdr, msg, &optz.EventFilterOptions, optz, func() (any, error) { return s.Accountz(&optz.AccountzOptions) })
|
||||
},
|
||||
"JSZ": func(sub *subscription, c *client, _ *Account, subject, reply string, hdr, msg []byte) {
|
||||
optz := &JszEventOptions{}
|
||||
s.zReq(c, reply, hdr, msg, &optz.EventFilterOptions, optz, func() (interface{}, error) { return s.Jsz(&optz.JSzOptions) })
|
||||
s.zReq(c, reply, hdr, msg, &optz.EventFilterOptions, optz, func() (any, error) { return s.Jsz(&optz.JSzOptions) })
|
||||
},
|
||||
"HEALTHZ": func(sub *subscription, c *client, _ *Account, subject, reply string, hdr, msg []byte) {
|
||||
optz := &HealthzEventOptions{}
|
||||
s.zReq(c, reply, hdr, msg, &optz.EventFilterOptions, optz, func() (interface{}, error) { return s.healthz(&optz.HealthzOptions), nil })
|
||||
s.zReq(c, reply, hdr, msg, &optz.EventFilterOptions, optz, func() (any, error) { return s.healthz(&optz.HealthzOptions), nil })
|
||||
},
|
||||
"PROFILEZ": func(sub *subscription, c *client, _ *Account, subject, reply string, hdr, msg []byte) {
|
||||
optz := &ProfilezEventOptions{}
|
||||
s.zReq(c, reply, hdr, msg, &optz.EventFilterOptions, optz, func() (interface{}, error) { return s.profilez(&optz.ProfilezOptions), nil })
|
||||
s.zReq(c, reply, hdr, msg, &optz.EventFilterOptions, optz, func() (any, error) { return s.profilez(&optz.ProfilezOptions), nil })
|
||||
},
|
||||
}
|
||||
for name, req := range monSrvc {
|
||||
@@ -1131,7 +1131,7 @@ func (s *Server) initEventTracking() {
|
||||
s.Errorf("Error setting up internal tracking: %v", err)
|
||||
}
|
||||
}
|
||||
extractAccount := func(c *client, subject string, msg []byte) (string, error) {
|
||||
extractAccount := func(subject string) (string, error) {
|
||||
if tk := strings.Split(subject, tsep); len(tk) != accReqTokens {
|
||||
return _EMPTY_, fmt.Errorf("subject %q is malformed", subject)
|
||||
} else {
|
||||
@@ -1141,8 +1141,8 @@ func (s *Server) initEventTracking() {
|
||||
monAccSrvc := map[string]sysMsgHandler{
|
||||
"SUBSZ": func(sub *subscription, c *client, _ *Account, subject, reply string, hdr, msg []byte) {
|
||||
optz := &SubszEventOptions{}
|
||||
s.zReq(c, reply, hdr, msg, &optz.EventFilterOptions, optz, func() (interface{}, error) {
|
||||
if acc, err := extractAccount(c, subject, msg); err != nil {
|
||||
s.zReq(c, reply, hdr, msg, &optz.EventFilterOptions, optz, func() (any, error) {
|
||||
if acc, err := extractAccount(subject); err != nil {
|
||||
return nil, err
|
||||
} else {
|
||||
optz.SubszOptions.Subscriptions = true
|
||||
@@ -1153,8 +1153,8 @@ func (s *Server) initEventTracking() {
|
||||
},
|
||||
"CONNZ": func(sub *subscription, c *client, _ *Account, subject, reply string, hdr, msg []byte) {
|
||||
optz := &ConnzEventOptions{}
|
||||
s.zReq(c, reply, hdr, msg, &optz.EventFilterOptions, optz, func() (interface{}, error) {
|
||||
if acc, err := extractAccount(c, subject, msg); err != nil {
|
||||
s.zReq(c, reply, hdr, msg, &optz.EventFilterOptions, optz, func() (any, error) {
|
||||
if acc, err := extractAccount(subject); err != nil {
|
||||
return nil, err
|
||||
} else {
|
||||
optz.ConnzOptions.Account = acc
|
||||
@@ -1164,8 +1164,8 @@ func (s *Server) initEventTracking() {
|
||||
},
|
||||
"LEAFZ": func(sub *subscription, c *client, _ *Account, subject, reply string, hdr, msg []byte) {
|
||||
optz := &LeafzEventOptions{}
|
||||
s.zReq(c, reply, hdr, msg, &optz.EventFilterOptions, optz, func() (interface{}, error) {
|
||||
if acc, err := extractAccount(c, subject, msg); err != nil {
|
||||
s.zReq(c, reply, hdr, msg, &optz.EventFilterOptions, optz, func() (any, error) {
|
||||
if acc, err := extractAccount(subject); err != nil {
|
||||
return nil, err
|
||||
} else {
|
||||
optz.LeafzOptions.Account = acc
|
||||
@@ -1175,8 +1175,8 @@ func (s *Server) initEventTracking() {
|
||||
},
|
||||
"JSZ": func(sub *subscription, c *client, _ *Account, subject, reply string, hdr, msg []byte) {
|
||||
optz := &JszEventOptions{}
|
||||
s.zReq(c, reply, hdr, msg, &optz.EventFilterOptions, optz, func() (interface{}, error) {
|
||||
if acc, err := extractAccount(c, subject, msg); err != nil {
|
||||
s.zReq(c, reply, hdr, msg, &optz.EventFilterOptions, optz, func() (any, error) {
|
||||
if acc, err := extractAccount(subject); err != nil {
|
||||
return nil, err
|
||||
} else {
|
||||
optz.Account = acc
|
||||
@@ -1186,8 +1186,8 @@ func (s *Server) initEventTracking() {
|
||||
},
|
||||
"INFO": func(sub *subscription, c *client, _ *Account, subject, reply string, hdr, msg []byte) {
|
||||
optz := &AccInfoEventOptions{}
|
||||
s.zReq(c, reply, hdr, msg, &optz.EventFilterOptions, optz, func() (interface{}, error) {
|
||||
if acc, err := extractAccount(c, subject, msg); err != nil {
|
||||
s.zReq(c, reply, hdr, msg, &optz.EventFilterOptions, optz, func() (any, error) {
|
||||
if acc, err := extractAccount(subject); err != nil {
|
||||
return nil, err
|
||||
} else {
|
||||
return s.accountInfo(acc)
|
||||
@@ -1199,8 +1199,8 @@ func (s *Server) initEventTracking() {
|
||||
// STATZ is also less heavy weight than INFO
|
||||
"STATZ": func(sub *subscription, c *client, _ *Account, subject, reply string, hdr, msg []byte) {
|
||||
optz := &AccountStatzEventOptions{}
|
||||
s.zReq(c, reply, hdr, msg, &optz.EventFilterOptions, optz, func() (interface{}, error) {
|
||||
if acc, err := extractAccount(c, subject, msg); err != nil {
|
||||
s.zReq(c, reply, hdr, msg, &optz.EventFilterOptions, optz, func() (any, error) {
|
||||
if acc, err := extractAccount(subject); err != nil {
|
||||
return nil, err
|
||||
} else if acc == "PING" { // Filter PING subject. Happens for server as well. But wildcards are not used
|
||||
return nil, errSkipZreq
|
||||
@@ -1235,7 +1235,7 @@ func (s *Server) initEventTracking() {
|
||||
if _, err := s.sysSubscribe(fmt.Sprintf(accPingReqSubj, "STATZ"),
|
||||
s.noInlineCallback(func(sub *subscription, c *client, _ *Account, subject, reply string, hdr, msg []byte) {
|
||||
optz := &AccountStatzEventOptions{}
|
||||
s.zReq(c, reply, hdr, msg, &optz.EventFilterOptions, optz, func() (interface{}, error) {
|
||||
s.zReq(c, reply, hdr, msg, &optz.EventFilterOptions, optz, func() (any, error) {
|
||||
if stz, err := s.AccountStatz(&optz.AccountStatzOptions); err != nil {
|
||||
return nil, err
|
||||
} else if len(stz.Accounts) == 0 && !optz.IncludeUnused {
|
||||
@@ -1326,7 +1326,7 @@ func (s *Server) registerSystemImportsForExisting() {
|
||||
return
|
||||
}
|
||||
sacc := s.sys.account
|
||||
s.accounts.Range(func(k, v interface{}) bool {
|
||||
s.accounts.Range(func(k, v any) bool {
|
||||
a := v.(*Account)
|
||||
if a != sacc {
|
||||
accounts = append(accounts, a)
|
||||
@@ -1428,12 +1428,12 @@ func (s *Server) accountClaimUpdate(sub *subscription, c *client, _ *Account, su
|
||||
// Will update the remote count for clients.
|
||||
// Lock assume held.
|
||||
func (s *Server) processRemoteServerShutdown(sid string) {
|
||||
s.accounts.Range(func(k, v interface{}) bool {
|
||||
s.accounts.Range(func(k, v any) bool {
|
||||
v.(*Account).removeRemoteServer(sid)
|
||||
return true
|
||||
})
|
||||
// Update any state in nodeInfo.
|
||||
s.nodeToInfo.Range(func(k, v interface{}) bool {
|
||||
s.nodeToInfo.Range(func(k, v any) bool {
|
||||
ni := v.(nodeInfo)
|
||||
if ni.id == sid {
|
||||
ni.offline = true
|
||||
@@ -1449,7 +1449,7 @@ func (s *Server) sameDomain(domain string) bool {
|
||||
return domain == _EMPTY_ || s.info.Domain == _EMPTY_ || domain == s.info.Domain
|
||||
}
|
||||
|
||||
// remoteServerShutdownEvent is called when we get an event from another server shutting down.
|
||||
// remoteServerShutdown is called when we get an event from another server shutting down.
|
||||
func (s *Server) remoteServerShutdown(sub *subscription, c *client, _ *Account, subject, reply string, hdr, msg []byte) {
|
||||
s.mu.Lock()
|
||||
defer s.mu.Unlock()
|
||||
@@ -1630,7 +1630,7 @@ func (s *Server) shutdownEventing() {
|
||||
defer s.mu.Unlock()
|
||||
|
||||
// Whip through all accounts.
|
||||
s.accounts.Range(func(k, v interface{}) bool {
|
||||
s.accounts.Range(func(k, v any) bool {
|
||||
v.(*Account).clearEventing()
|
||||
return true
|
||||
})
|
||||
@@ -1829,7 +1829,7 @@ const (
|
||||
// ServerAPIResponse is the response type for the server API like varz, connz etc.
|
||||
type ServerAPIResponse struct {
|
||||
Server *ServerInfo `json:"server"`
|
||||
Data interface{} `json:"data,omitempty"`
|
||||
Data any `json:"data,omitempty"`
|
||||
Error *ApiError `json:"error,omitempty"`
|
||||
|
||||
// Private to indicate compression if any.
|
||||
@@ -1907,7 +1907,7 @@ func getAcceptEncoding(hdr []byte) compressionType {
|
||||
return unsupportedCompression
|
||||
}
|
||||
|
||||
func (s *Server) zReq(c *client, reply string, hdr, msg []byte, fOpts *EventFilterOptions, optz interface{}, respf func() (interface{}, error)) {
|
||||
func (s *Server) zReq(_ *client, reply string, hdr, msg []byte, fOpts *EventFilterOptions, optz any, respf func() (any, error)) {
|
||||
if !s.EventsEnabled() || reply == _EMPTY_ {
|
||||
return
|
||||
}
|
||||
@@ -2764,7 +2764,7 @@ func (s *Server) reloadConfig(sub *subscription, c *client, _ *Account, subject,
|
||||
}
|
||||
|
||||
optz := &EventFilterOptions{}
|
||||
s.zReq(c, reply, hdr, msg, optz, optz, func() (interface{}, error) {
|
||||
s.zReq(c, reply, hdr, msg, optz, optz, func() (any, error) {
|
||||
// Reload the server config, as requested.
|
||||
return nil, s.Reload()
|
||||
})
|
||||
@@ -2790,7 +2790,7 @@ func (s *Server) kickClient(_ *subscription, c *client, _ *Account, subject, rep
|
||||
}
|
||||
|
||||
optz := &EventFilterOptions{}
|
||||
s.zReq(c, reply, hdr, msg, optz, optz, func() (interface{}, error) {
|
||||
s.zReq(c, reply, hdr, msg, optz, optz, func() (any, error) {
|
||||
return nil, s.DisconnectClientByID(req.CID)
|
||||
})
|
||||
|
||||
@@ -2808,7 +2808,7 @@ func (s *Server) ldmClient(_ *subscription, c *client, _ *Account, subject, repl
|
||||
}
|
||||
|
||||
optz := &EventFilterOptions{}
|
||||
s.zReq(c, reply, hdr, msg, optz, optz, func() (interface{}, error) {
|
||||
s.zReq(c, reply, hdr, msg, optz, optz, func() (any, error) {
|
||||
return nil, s.LDMClientByID(req.CID)
|
||||
})
|
||||
}
|
||||
|
||||
+227
-97
@@ -641,7 +641,7 @@ func genEncryptionKey(sc StoreCipher, seed []byte) (ek cipher.AEAD, err error) {
|
||||
} else if sc == AES {
|
||||
block, e := aes.NewCipher(seed)
|
||||
if e != nil {
|
||||
return nil, err
|
||||
return nil, e
|
||||
}
|
||||
ek, err = cipher.NewGCMWithNonceSize(block, block.BlockSize())
|
||||
} else {
|
||||
@@ -671,8 +671,10 @@ func (fs *fileStore) genEncryptionKeys(context string) (aek cipher.AEAD, bek cip
|
||||
|
||||
const seedSize = 32
|
||||
seed = make([]byte, seedSize)
|
||||
if n, err := rand.Read(seed); err != nil || n != seedSize {
|
||||
if n, err := rand.Read(seed); err != nil {
|
||||
return nil, nil, nil, nil, err
|
||||
} else if n != seedSize {
|
||||
return nil, nil, nil, nil, fmt.Errorf("not enough seed bytes read (%d != %d", n, seedSize)
|
||||
}
|
||||
|
||||
aek, err = genEncryptionKey(sc, seed)
|
||||
@@ -682,7 +684,11 @@ func (fs *fileStore) genEncryptionKeys(context string) (aek cipher.AEAD, bek cip
|
||||
|
||||
// Generate our nonce. Use same buffer to hold encrypted seed.
|
||||
nonce := make([]byte, kek.NonceSize(), kek.NonceSize()+len(seed)+kek.Overhead())
|
||||
rand.Read(nonce)
|
||||
if n, err := rand.Read(nonce); err != nil {
|
||||
return nil, nil, nil, nil, err
|
||||
} else if n != len(nonce) {
|
||||
return nil, nil, nil, nil, fmt.Errorf("not enough nonce bytes read (%d != %d)", n, len(nonce))
|
||||
}
|
||||
|
||||
bek, err = genBlockEncryptionKey(sc, seed[:], nonce)
|
||||
if err != nil {
|
||||
@@ -776,7 +782,11 @@ func (fs *fileStore) writeStreamMeta() error {
|
||||
// Encrypt if needed.
|
||||
if fs.aek != nil {
|
||||
nonce := make([]byte, fs.aek.NonceSize(), fs.aek.NonceSize()+len(b)+fs.aek.Overhead())
|
||||
rand.Read(nonce)
|
||||
if n, err := rand.Read(nonce); err != nil {
|
||||
return err
|
||||
} else if n != len(nonce) {
|
||||
return fmt.Errorf("not enough nonce bytes read (%d != %d)", n, len(nonce))
|
||||
}
|
||||
b = fs.aek.Seal(nonce, nonce, b, nil)
|
||||
}
|
||||
|
||||
@@ -1494,7 +1504,7 @@ func updateTrackingState(state *StreamState, mb *msgBlock) {
|
||||
func trackingStatesEqual(fs, mb *StreamState) bool {
|
||||
// When a fs is brand new the fs state will have first seq of 0, but tracking mb may have 1.
|
||||
// If either has a first sequence that is not 0 or 1 we will check if they are the same, otherwise skip.
|
||||
if fs.FirstSeq > 1 || mb.FirstSeq > 1 {
|
||||
if (fs.FirstSeq > 1 && mb.FirstSeq > 1) || mb.FirstSeq > 1 {
|
||||
return fs.Msgs == mb.Msgs && fs.FirstSeq == mb.FirstSeq && fs.LastSeq == mb.LastSeq && fs.Bytes == mb.Bytes
|
||||
}
|
||||
return fs.Msgs == mb.Msgs && fs.LastSeq == mb.LastSeq && fs.Bytes == mb.Bytes
|
||||
@@ -2228,6 +2238,50 @@ func (fs *fileStore) GetSeqFromTime(t time.Time) uint64 {
|
||||
return 0
|
||||
}
|
||||
|
||||
// Find the first matching message against a sublist.
|
||||
func (mb *msgBlock) firstMatchingMulti(sl *Sublist, start uint64, sm *StoreMsg) (*StoreMsg, bool, error) {
|
||||
mb.mu.Lock()
|
||||
defer mb.mu.Unlock()
|
||||
|
||||
// Will just do linear walk for now.
|
||||
// TODO(dlc) - Be better at skipping blocks that will not match us regardless.
|
||||
|
||||
var didLoad bool
|
||||
// Need messages loaded from here on out.
|
||||
if mb.cacheNotLoaded() {
|
||||
if err := mb.loadMsgsWithLock(); err != nil {
|
||||
return nil, false, err
|
||||
}
|
||||
didLoad = true
|
||||
}
|
||||
|
||||
// Make sure to start at mb.first.seq if fseq < mb.first.seq
|
||||
if seq := atomic.LoadUint64(&mb.first.seq); seq > start {
|
||||
start = seq
|
||||
}
|
||||
lseq := atomic.LoadUint64(&mb.last.seq)
|
||||
|
||||
if sm == nil {
|
||||
sm = new(StoreMsg)
|
||||
}
|
||||
|
||||
for seq := start; seq <= lseq; seq++ {
|
||||
llseq := mb.llseq
|
||||
fsm, err := mb.cacheLookup(seq, sm)
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
expireOk := seq == lseq && mb.llseq == seq
|
||||
|
||||
if r := sl.Match(fsm.subj); len(r.psubs) > 0 {
|
||||
return fsm, expireOk, nil
|
||||
}
|
||||
// If we are here we did not match, so put the llseq back.
|
||||
mb.llseq = llseq
|
||||
}
|
||||
return nil, didLoad, ErrStoreMsgNotFound
|
||||
}
|
||||
|
||||
// Find the first matching message.
|
||||
func (mb *msgBlock) firstMatching(filter string, wc bool, start uint64, sm *StoreMsg) (*StoreMsg, bool, error) {
|
||||
mb.mu.Lock()
|
||||
@@ -2267,6 +2321,7 @@ func (mb *msgBlock) firstMatching(filter string, wc bool, start uint64, sm *Stor
|
||||
}
|
||||
// Only do linear scan if isAll or we are wildcarded and have to traverse more fss than actual messages.
|
||||
doLinearScan := isAll || (wc && len(mb.fss) > int(lseq-fseq))
|
||||
|
||||
if !doLinearScan {
|
||||
// If we have a wildcard match against all tracked subjects we know about.
|
||||
if wc {
|
||||
@@ -2276,6 +2331,10 @@ func (mb *msgBlock) firstMatching(filter string, wc bool, start uint64, sm *Stor
|
||||
subs = append(subs, subj)
|
||||
}
|
||||
}
|
||||
// Check if we matched anything
|
||||
if len(subs) == 0 {
|
||||
return nil, didLoad, ErrStoreMsgNotFound
|
||||
}
|
||||
}
|
||||
fseq = lseq + 1
|
||||
for _, subj := range subs {
|
||||
@@ -2294,6 +2353,10 @@ func (mb *msgBlock) firstMatching(filter string, wc bool, start uint64, sm *Stor
|
||||
}
|
||||
}
|
||||
|
||||
if fseq > lseq {
|
||||
return nil, didLoad, ErrStoreMsgNotFound
|
||||
}
|
||||
|
||||
// If we guess to not do a linear scan, but the above resulted in alot of subs that will
|
||||
// need to be checked for every scanned message, revert.
|
||||
// TODO(dlc) - we could memoize the subs across calls.
|
||||
@@ -2301,10 +2364,6 @@ func (mb *msgBlock) firstMatching(filter string, wc bool, start uint64, sm *Stor
|
||||
doLinearScan = true
|
||||
}
|
||||
|
||||
if fseq > lseq {
|
||||
return nil, didLoad, ErrStoreMsgNotFound
|
||||
}
|
||||
|
||||
// Need messages loaded from here on out.
|
||||
if mb.cacheNotLoaded() {
|
||||
if err := mb.loadMsgsWithLock(); err != nil {
|
||||
@@ -2321,6 +2380,9 @@ func (mb *msgBlock) firstMatching(filter string, wc bool, start uint64, sm *Stor
|
||||
llseq := mb.llseq
|
||||
fsm, err := mb.cacheLookup(seq, sm)
|
||||
if err != nil {
|
||||
if err == errPartialCache || err == errNoCache {
|
||||
return nil, false, err
|
||||
}
|
||||
continue
|
||||
}
|
||||
expireOk := seq == lseq && mb.llseq == seq
|
||||
@@ -3089,12 +3151,16 @@ func (fs *fileStore) storeRawMsg(subj string, hdr, msg []byte, seq uint64, ts in
|
||||
}
|
||||
asl = true
|
||||
}
|
||||
if fs.cfg.MaxMsgs > 0 && fs.state.Msgs >= uint64(fs.cfg.MaxMsgs) && !asl {
|
||||
return ErrMaxMsgs
|
||||
}
|
||||
if fs.cfg.MaxBytes > 0 && fs.state.Bytes+fileStoreMsgSize(subj, hdr, msg) >= uint64(fs.cfg.MaxBytes) {
|
||||
if !asl || fs.sizeForSeq(fseq) <= int(fileStoreMsgSize(subj, hdr, msg)) {
|
||||
return ErrMaxBytes
|
||||
// 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 fs.cfg.Retention == LimitsPolicy || fs.cfg.Replicas == 1 {
|
||||
if fs.cfg.MaxMsgs > 0 && fs.state.Msgs >= uint64(fs.cfg.MaxMsgs) && !asl {
|
||||
return ErrMaxMsgs
|
||||
}
|
||||
if fs.cfg.MaxBytes > 0 && fs.state.Bytes+fileStoreMsgSize(subj, hdr, msg) >= uint64(fs.cfg.MaxBytes) {
|
||||
if !asl || fs.sizeForSeq(fseq) <= int(fileStoreMsgSize(subj, hdr, msg)) {
|
||||
return ErrMaxBytes
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -3495,12 +3561,11 @@ func (fs *fileStore) enforceMsgPerSubjectLimit(fireCallback bool) {
|
||||
|
||||
// collect all that are not correct.
|
||||
needAttention := make(map[string]*psi)
|
||||
fs.psim.Iter(func(subj []byte, psi *psi) bool {
|
||||
fs.psim.Match([]byte(fwcs), func(subj []byte, psi *psi) {
|
||||
numMsgs += psi.total
|
||||
if psi.total > maxMsgsPer {
|
||||
needAttention[string(subj)] = psi
|
||||
}
|
||||
return true
|
||||
})
|
||||
|
||||
// We had an issue with a use case where psim (and hence fss) were correct but idx was not and was not properly being caught.
|
||||
@@ -3520,11 +3585,10 @@ func (fs *fileStore) enforceMsgPerSubjectLimit(fireCallback bool) {
|
||||
fs.rebuildStateLocked(nil)
|
||||
// Need to redo blocks that need attention.
|
||||
needAttention = make(map[string]*psi)
|
||||
fs.psim.Iter(func(subj []byte, psi *psi) bool {
|
||||
fs.psim.Match([]byte(fwcs), func(subj []byte, psi *psi) {
|
||||
if psi.total > maxMsgsPer {
|
||||
needAttention[string(subj)] = psi
|
||||
}
|
||||
return true
|
||||
})
|
||||
}
|
||||
|
||||
@@ -3653,15 +3717,6 @@ func (fs *fileStore) removeMsg(seq uint64, secure, viaLimits, needFSLock bool) (
|
||||
secure = false
|
||||
}
|
||||
|
||||
if fs.state.Msgs == 0 {
|
||||
var err = ErrStoreEOF
|
||||
if seq <= fs.state.LastSeq {
|
||||
err = ErrStoreMsgNotFound
|
||||
}
|
||||
fsUnlock()
|
||||
return false, err
|
||||
}
|
||||
|
||||
mb := fs.selectMsgBlock(seq)
|
||||
if mb == nil {
|
||||
var err = ErrStoreEOF
|
||||
@@ -3674,15 +3729,8 @@ func (fs *fileStore) removeMsg(seq uint64, secure, viaLimits, needFSLock bool) (
|
||||
|
||||
mb.mu.Lock()
|
||||
|
||||
// See if we are closed or the sequence number is still relevant.
|
||||
if mb.closed || seq < atomic.LoadUint64(&mb.first.seq) {
|
||||
mb.mu.Unlock()
|
||||
fsUnlock()
|
||||
return false, nil
|
||||
}
|
||||
|
||||
// Now check dmap if it is there.
|
||||
if mb.dmap.Exists(seq) {
|
||||
// See if we are closed or the sequence number is still relevant or if we know its deleted.
|
||||
if mb.closed || seq < atomic.LoadUint64(&mb.first.seq) || mb.dmap.Exists(seq) {
|
||||
mb.mu.Unlock()
|
||||
fsUnlock()
|
||||
return false, nil
|
||||
@@ -3692,27 +3740,11 @@ func (fs *fileStore) removeMsg(seq uint64, secure, viaLimits, needFSLock bool) (
|
||||
// Now just load regardless.
|
||||
// TODO(dlc) - Figure out a way not to have to load it in, we need subject tracking outside main data block.
|
||||
if mb.cacheNotLoaded() {
|
||||
// We do not want to block possible activity within another msg block.
|
||||
// We have to unlock both locks and acquire the mb lock in the loadMsgs() call to avoid a deadlock if another
|
||||
// go routine was trying to get fs then this mb lock at the same time. E.g. another call to remove for same block.
|
||||
mb.mu.Unlock()
|
||||
fsUnlock()
|
||||
if err := mb.loadMsgs(); err != nil {
|
||||
return false, err
|
||||
}
|
||||
fsLock()
|
||||
// We need to check if things changed out from underneath us.
|
||||
if fs.closed {
|
||||
fsUnlock()
|
||||
return false, ErrStoreClosed
|
||||
}
|
||||
mb.mu.Lock()
|
||||
if mb.closed || seq < atomic.LoadUint64(&mb.first.seq) {
|
||||
if err := mb.loadMsgsWithLock(); err != nil {
|
||||
mb.mu.Unlock()
|
||||
fsUnlock()
|
||||
return false, nil
|
||||
return false, err
|
||||
}
|
||||
// cacheLookup below will do dmap check so no need to repeat here.
|
||||
}
|
||||
|
||||
var smv StoreMsg
|
||||
@@ -3765,7 +3797,9 @@ func (fs *fileStore) removeMsg(seq uint64, secure, viaLimits, needFSLock bool) (
|
||||
if secure {
|
||||
// Grab record info.
|
||||
ri, rl, _, _ := mb.slotInfo(int(seq - mb.cache.fseq))
|
||||
mb.eraseMsg(seq, int(ri), int(rl))
|
||||
if err := mb.eraseMsg(seq, int(ri), int(rl)); err != nil {
|
||||
return false, err
|
||||
}
|
||||
}
|
||||
|
||||
fifo := seq == atomic.LoadUint64(&mb.first.seq)
|
||||
@@ -4123,7 +4157,11 @@ func (mb *msgBlock) eraseMsg(seq uint64, ri, rl int) error {
|
||||
|
||||
// Randomize record
|
||||
data := make([]byte, rl-emptyRecordLen)
|
||||
rand.Read(data)
|
||||
if n, err := rand.Read(data); err != nil {
|
||||
return err
|
||||
} else if n != len(data) {
|
||||
return fmt.Errorf("not enough overwrite bytes read (%d != %d)", n, len(data))
|
||||
}
|
||||
|
||||
// Now write to underlying buffer.
|
||||
var b bytes.Buffer
|
||||
@@ -4169,8 +4207,11 @@ func (mb *msgBlock) eraseMsg(seq uint64, ri, rl int) error {
|
||||
|
||||
// Truncate this message block to the storedMsg.
|
||||
func (mb *msgBlock) truncate(sm *StoreMsg) (nmsgs, nbytes uint64, err error) {
|
||||
mb.mu.Lock()
|
||||
defer mb.mu.Unlock()
|
||||
|
||||
// Make sure we are loaded to process messages etc.
|
||||
if err := mb.loadMsgs(); err != nil {
|
||||
if err := mb.loadMsgsWithLock(); err != nil {
|
||||
return 0, 0, err
|
||||
}
|
||||
|
||||
@@ -4184,8 +4225,6 @@ func (mb *msgBlock) truncate(sm *StoreMsg) (nmsgs, nbytes uint64, err error) {
|
||||
|
||||
var purged, bytes uint64
|
||||
|
||||
mb.mu.Lock()
|
||||
|
||||
checkDmap := mb.dmap.Size() > 0
|
||||
var smv StoreMsg
|
||||
|
||||
@@ -4271,7 +4310,6 @@ func (mb *msgBlock) truncate(sm *StoreMsg) (nmsgs, nbytes uint64, err error) {
|
||||
mb.mfd.ReadAt(lchk[:], eof-8)
|
||||
copy(mb.lchk[0:], lchk[:])
|
||||
} else {
|
||||
mb.mu.Unlock()
|
||||
return 0, 0, fmt.Errorf("failed to truncate msg block %d, file not open", mb.index)
|
||||
}
|
||||
|
||||
@@ -4285,10 +4323,8 @@ func (mb *msgBlock) truncate(sm *StoreMsg) (nmsgs, nbytes uint64, err error) {
|
||||
// Redo per subject info for this block.
|
||||
mb.resetPerSubjectInfo()
|
||||
|
||||
mb.mu.Unlock()
|
||||
|
||||
// Load msgs again.
|
||||
mb.loadMsgs()
|
||||
mb.loadMsgsWithLock()
|
||||
|
||||
return purged, bytes, nil
|
||||
}
|
||||
@@ -5088,7 +5124,7 @@ func (fs *fileStore) syncBlocks() {
|
||||
}
|
||||
|
||||
// Check if we need to sync. We will not hold lock during actual sync.
|
||||
needSync, fn := mb.needSync, mb.mfn
|
||||
needSync := mb.needSync
|
||||
if needSync {
|
||||
// Flush anything that may be pending.
|
||||
mb.flushPendingMsgsLocked()
|
||||
@@ -5105,23 +5141,32 @@ func (fs *fileStore) syncBlocks() {
|
||||
fs.mu.RUnlock()
|
||||
}
|
||||
|
||||
// Check if we need to sync.
|
||||
// This is done not holding any locks.
|
||||
// Check if we need to sync this block.
|
||||
if needSync {
|
||||
<-dios
|
||||
fd, _ := os.OpenFile(fn, os.O_RDWR, defaultFilePerms)
|
||||
dios <- struct{}{}
|
||||
mb.mu.Lock()
|
||||
var fd *os.File
|
||||
var didOpen bool
|
||||
if mb.mfd != nil {
|
||||
fd = mb.mfd
|
||||
} else {
|
||||
<-dios
|
||||
fd, _ = os.OpenFile(mb.mfn, os.O_RDWR, defaultFilePerms)
|
||||
dios <- struct{}{}
|
||||
didOpen = true
|
||||
}
|
||||
// If we have an fd.
|
||||
if fd != nil {
|
||||
canClear := fd.Sync() == nil
|
||||
fd.Close()
|
||||
// If we opened the file close the fd.
|
||||
if didOpen {
|
||||
fd.Close()
|
||||
}
|
||||
// Only clear sync flag on success.
|
||||
if canClear {
|
||||
mb.mu.Lock()
|
||||
mb.needSync = false
|
||||
mb.mu.Unlock()
|
||||
}
|
||||
}
|
||||
mb.mu.Unlock()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5131,15 +5176,13 @@ func (fs *fileStore) syncBlocks() {
|
||||
return
|
||||
}
|
||||
fs.setSyncTimer()
|
||||
fn := filepath.Join(fs.fcfg.StoreDir, msgDir, streamStreamStateFile)
|
||||
syncAlways := fs.fcfg.SyncAlways
|
||||
if markDirty {
|
||||
fs.dirty++
|
||||
}
|
||||
fs.mu.Unlock()
|
||||
|
||||
// Sync state file if we are not running with sync always.
|
||||
if !syncAlways {
|
||||
if !fs.fcfg.SyncAlways {
|
||||
fn := filepath.Join(fs.fcfg.StoreDir, msgDir, streamStreamStateFile)
|
||||
<-dios
|
||||
fd, _ := os.OpenFile(fn, os.O_RDWR, defaultFilePerms)
|
||||
dios <- struct{}{}
|
||||
@@ -5148,6 +5191,7 @@ func (fs *fileStore) syncBlocks() {
|
||||
fd.Close()
|
||||
}
|
||||
}
|
||||
fs.mu.Unlock()
|
||||
}
|
||||
|
||||
// Select the message block where this message should be found.
|
||||
@@ -5161,7 +5205,7 @@ func (fs *fileStore) selectMsgBlock(seq uint64) *msgBlock {
|
||||
// Lock should be held.
|
||||
func (fs *fileStore) selectMsgBlockWithIndex(seq uint64) (int, *msgBlock) {
|
||||
// Check for out of range.
|
||||
if seq < fs.state.FirstSeq || seq > fs.state.LastSeq {
|
||||
if seq < fs.state.FirstSeq || seq > fs.state.LastSeq || fs.state.Msgs == 0 {
|
||||
return -1, nil
|
||||
}
|
||||
|
||||
@@ -5232,6 +5276,13 @@ func (mb *msgBlock) indexCacheBuf(buf []byte) error {
|
||||
mbFirstSeq := atomic.LoadUint64(&mb.first.seq)
|
||||
mbLastSeq := atomic.LoadUint64(&mb.last.seq)
|
||||
|
||||
// Sanity check here since we calculate size to allocate based on this.
|
||||
if mbFirstSeq > (mbLastSeq + 1) { // Purged state first == last + 1
|
||||
mb.fs.warn("indexCacheBuf corrupt state: mb.first %d mb.last %d", mbFirstSeq, mbLastSeq)
|
||||
// This would cause idxSz to wrap.
|
||||
return errCorruptState
|
||||
}
|
||||
|
||||
// Capture beginning size of dmap.
|
||||
dms := uint64(mb.dmap.Size())
|
||||
idxSz := mbLastSeq - mbFirstSeq + 1
|
||||
@@ -5274,6 +5325,7 @@ func (mb *msgBlock) indexCacheBuf(buf []byte) error {
|
||||
|
||||
// Do some quick sanity checks here.
|
||||
if dlen < 0 || slen > (dlen-recordHashSize) || dlen > int(rl) || index+rl > lbuf || rl > rlBadThresh {
|
||||
mb.fs.warn("indexCacheBuf corrupt record state: dlen %d slen %d index %d rl %d lbuf %d", dlen, slen, index, rl, lbuf)
|
||||
// This means something is off.
|
||||
// TODO(dlc) - Add into bad list?
|
||||
return errCorruptState
|
||||
@@ -5654,6 +5706,7 @@ checkCache:
|
||||
// We want to hold the mb lock here to avoid any changes to state.
|
||||
buf, err := mb.loadBlock(nil)
|
||||
if err != nil {
|
||||
mb.fs.warn("loadBlock error: ", err)
|
||||
if err == errNoBlkData {
|
||||
if ld, _, err := mb.rebuildStateLocked(); err != nil && ld != nil {
|
||||
// Rebuild fs state too.
|
||||
@@ -5795,10 +5848,22 @@ func (mb *msgBlock) cacheLookup(seq uint64, sm *StoreMsg) (*StoreMsg, error) {
|
||||
|
||||
// Detect no cache loaded.
|
||||
if mb.cache == nil || mb.cache.fseq == 0 || len(mb.cache.idx) == 0 || len(mb.cache.buf) == 0 {
|
||||
var reason string
|
||||
if mb.cache == nil {
|
||||
reason = "no cache"
|
||||
} else if mb.cache.fseq == 0 {
|
||||
reason = "fseq is 0"
|
||||
} else if len(mb.cache.idx) == 0 {
|
||||
reason = "no idx present"
|
||||
} else {
|
||||
reason = "cache buf empty"
|
||||
}
|
||||
mb.fs.warn("Cache lookup detected no cache: %s", reason)
|
||||
return nil, errNoCache
|
||||
}
|
||||
// Check partial cache status.
|
||||
if seq < mb.cache.fseq {
|
||||
mb.fs.warn("Cache lookup detected partial cache: seq %d vs cache fseq %d", seq, mb.cache.fseq)
|
||||
return nil, errPartialCache
|
||||
}
|
||||
|
||||
@@ -6053,6 +6118,44 @@ func (fs *fileStore) LoadLastMsg(subject string, smv *StoreMsg) (sm *StoreMsg, e
|
||||
return sm, err
|
||||
}
|
||||
|
||||
// LoadNextMsgMulti will find the next message matching any entry in the sublist.
|
||||
func (fs *fileStore) LoadNextMsgMulti(sl *Sublist, start uint64, smp *StoreMsg) (sm *StoreMsg, skip uint64, err error) {
|
||||
if sl == nil {
|
||||
return fs.LoadNextMsg(_EMPTY_, false, start, smp)
|
||||
}
|
||||
fs.mu.RLock()
|
||||
defer fs.mu.RUnlock()
|
||||
|
||||
if fs.closed {
|
||||
return nil, 0, ErrStoreClosed
|
||||
}
|
||||
if fs.state.Msgs == 0 {
|
||||
return nil, fs.state.LastSeq, ErrStoreEOF
|
||||
}
|
||||
if start < fs.state.FirstSeq {
|
||||
start = fs.state.FirstSeq
|
||||
}
|
||||
|
||||
if bi, _ := fs.selectMsgBlockWithIndex(start); bi >= 0 {
|
||||
for i := bi; i < len(fs.blks); i++ {
|
||||
mb := fs.blks[i]
|
||||
if sm, expireOk, err := mb.firstMatchingMulti(sl, start, smp); err == nil {
|
||||
if expireOk {
|
||||
mb.tryForceExpireCache()
|
||||
}
|
||||
return sm, sm.seq, nil
|
||||
} else if err != ErrStoreMsgNotFound {
|
||||
return nil, 0, err
|
||||
} else if expireOk {
|
||||
mb.tryForceExpireCache()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return nil, fs.state.LastSeq, ErrStoreEOF
|
||||
|
||||
}
|
||||
|
||||
func (fs *fileStore) LoadNextMsg(filter string, wc bool, start uint64, sm *StoreMsg) (*StoreMsg, uint64, error) {
|
||||
fs.mu.RLock()
|
||||
defer fs.mu.RUnlock()
|
||||
@@ -6060,6 +6163,9 @@ func (fs *fileStore) LoadNextMsg(filter string, wc bool, start uint64, sm *Store
|
||||
if fs.closed {
|
||||
return nil, 0, ErrStoreClosed
|
||||
}
|
||||
if fs.state.Msgs == 0 {
|
||||
return nil, fs.state.LastSeq, ErrStoreEOF
|
||||
}
|
||||
if start < fs.state.FirstSeq {
|
||||
start = fs.state.FirstSeq
|
||||
}
|
||||
@@ -6121,6 +6227,8 @@ func (fs *fileStore) FastState(state *StreamState) {
|
||||
state.FirstTime = fs.state.FirstTime
|
||||
state.LastSeq = fs.state.LastSeq
|
||||
state.LastTime = fs.state.LastTime
|
||||
// Make sure to reset if being re-used.
|
||||
state.Deleted, state.NumDeleted = nil, 0
|
||||
if state.LastSeq > state.FirstSeq {
|
||||
state.NumDeleted = int((state.LastSeq - state.FirstSeq + 1) - state.Msgs)
|
||||
if state.NumDeleted < 0 {
|
||||
@@ -6675,7 +6783,6 @@ func (fs *fileStore) Compact(seq uint64) (uint64, error) {
|
||||
|
||||
var smv StoreMsg
|
||||
var err error
|
||||
var isEmpty bool
|
||||
|
||||
smb.mu.Lock()
|
||||
if atomic.LoadUint64(&smb.first.seq) == seq {
|
||||
@@ -6715,13 +6822,16 @@ func (fs *fileStore) Compact(seq uint64) (uint64, error) {
|
||||
}
|
||||
|
||||
// Check if empty after processing, could happen if tail of messages are all deleted.
|
||||
isEmpty = smb.msgs == 0
|
||||
if isEmpty {
|
||||
smb.dirtyCloseWithRemove(true)
|
||||
if isEmpty := smb.msgs == 0; isEmpty {
|
||||
// Only remove if not the last block.
|
||||
if smb != fs.lmb {
|
||||
smb.dirtyCloseWithRemove(true)
|
||||
deleted++
|
||||
}
|
||||
// Update fs first here as well.
|
||||
fs.state.FirstSeq = atomic.LoadUint64(&smb.last.seq) + 1
|
||||
fs.state.FirstTime = time.Time{}
|
||||
deleted++
|
||||
|
||||
} else {
|
||||
// Make sure to sync changes.
|
||||
smb.needSync = true
|
||||
@@ -6903,6 +7013,7 @@ func (fs *fileStore) Truncate(seq uint64) error {
|
||||
// Set lmb to nlmb and make sure writeable.
|
||||
fs.lmb = nlmb
|
||||
if err := nlmb.enableForWriting(fs.fip); err != nil {
|
||||
fs.mu.Unlock()
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -7101,7 +7212,6 @@ func (mb *msgBlock) recalculateFirstForSubj(subj string, startSeq uint64, ss *Si
|
||||
|
||||
// Mark first as updated.
|
||||
ss.firstNeedsUpdate = false
|
||||
startSeq++
|
||||
|
||||
startSlot := int(startSeq - mb.cache.fseq)
|
||||
if startSlot >= len(mb.cache.idx) {
|
||||
@@ -7548,7 +7658,11 @@ func (fs *fileStore) writeFullState() error {
|
||||
return err
|
||||
}
|
||||
nonce := make([]byte, fs.aek.NonceSize(), fs.aek.NonceSize()+len(buf)+fs.aek.Overhead())
|
||||
rand.Read(nonce)
|
||||
if n, err := rand.Read(nonce); err != nil {
|
||||
return err
|
||||
} else if n != len(nonce) {
|
||||
return fmt.Errorf("not enough nonce bytes read (%d != %d)", n, len(nonce))
|
||||
}
|
||||
buf = fs.aek.Seal(nonce, nonce, buf, nil)
|
||||
}
|
||||
|
||||
@@ -7561,8 +7675,7 @@ func (fs *fileStore) writeFullState() error {
|
||||
// Snapshot prior dirty count.
|
||||
priorDirty := fs.dirty
|
||||
|
||||
// Check tracking state.
|
||||
statesEqual := trackingStatesEqual(&fs.state, &mstate)
|
||||
statesEqual := trackingStatesEqual(&fs.state, &mstate) || len(fs.blks) > 0
|
||||
// Release lock.
|
||||
fs.mu.Unlock()
|
||||
|
||||
@@ -7680,7 +7793,7 @@ func (fs *fileStore) stop(writeState bool) error {
|
||||
const errFile = "errors.txt"
|
||||
|
||||
// Stream our snapshot through S2 compression and tar.
|
||||
func (fs *fileStore) streamSnapshot(w io.WriteCloser, state *StreamState, includeConsumers bool) {
|
||||
func (fs *fileStore) streamSnapshot(w io.WriteCloser, includeConsumers bool) {
|
||||
defer w.Close()
|
||||
|
||||
enc := s2.NewWriter(w)
|
||||
@@ -7893,7 +8006,7 @@ func (fs *fileStore) Snapshot(deadline time.Duration, checkMsgs, includeConsumer
|
||||
fs.FastState(&state)
|
||||
|
||||
// Stream in separate Go routine.
|
||||
go fs.streamSnapshot(pw, &state, includeConsumers)
|
||||
go fs.streamSnapshot(pw, includeConsumers)
|
||||
|
||||
return &SnapshotResult{pr, state}, nil
|
||||
}
|
||||
@@ -8157,8 +8270,12 @@ func (fs *fileStore) ConsumerStore(name string, cfg *ConsumerConfig) (ConsumerSt
|
||||
// Redo the state file as well here if we have one and we can tell it was plaintext.
|
||||
if buf, err := os.ReadFile(o.ifn); err == nil {
|
||||
if _, err := decodeConsumerState(buf); err == nil {
|
||||
state, err := o.encryptState(buf)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
<-dios
|
||||
err := os.WriteFile(o.ifn, o.encryptState(buf), defaultFilePerms)
|
||||
err = os.WriteFile(o.ifn, state, defaultFilePerms)
|
||||
dios <- struct{}{}
|
||||
if err != nil {
|
||||
if didCreate {
|
||||
@@ -8564,14 +8681,18 @@ func (o *consumerFileStore) Update(state *ConsumerState) error {
|
||||
|
||||
// Will encrypt the state with our asset key. Will be a no-op if encryption not enabled.
|
||||
// Lock should be held.
|
||||
func (o *consumerFileStore) encryptState(buf []byte) []byte {
|
||||
func (o *consumerFileStore) encryptState(buf []byte) ([]byte, error) {
|
||||
if o.aek == nil {
|
||||
return buf
|
||||
return buf, nil
|
||||
}
|
||||
// TODO(dlc) - Optimize on space usage a bit?
|
||||
nonce := make([]byte, o.aek.NonceSize(), o.aek.NonceSize()+len(buf)+o.aek.Overhead())
|
||||
rand.Read(nonce)
|
||||
return o.aek.Seal(nonce, nonce, buf, nil)
|
||||
if n, err := rand.Read(nonce); err != nil {
|
||||
return nil, err
|
||||
} else if n != len(nonce) {
|
||||
return nil, fmt.Errorf("not enough nonce bytes read (%d != %d)", n, len(nonce))
|
||||
}
|
||||
return o.aek.Seal(nonce, nonce, buf, nil), nil
|
||||
}
|
||||
|
||||
// Used to limit number of disk IO calls in flight since they could all be blocking an OS thread.
|
||||
@@ -8600,7 +8721,10 @@ func (o *consumerFileStore) writeState(buf []byte) error {
|
||||
|
||||
// Check on encryption.
|
||||
if o.aek != nil {
|
||||
buf = o.encryptState(buf)
|
||||
var err error
|
||||
if buf, err = o.encryptState(buf); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
o.writing = true
|
||||
@@ -8665,7 +8789,11 @@ func (cfs *consumerFileStore) writeConsumerMeta() error {
|
||||
// Encrypt if needed.
|
||||
if cfs.aek != nil {
|
||||
nonce := make([]byte, cfs.aek.NonceSize(), cfs.aek.NonceSize()+len(b)+cfs.aek.Overhead())
|
||||
rand.Read(nonce)
|
||||
if n, err := rand.Read(nonce); err != nil {
|
||||
return err
|
||||
} else if n != len(nonce) {
|
||||
return fmt.Errorf("not enough nonce bytes read (%d != %d)", n, len(nonce))
|
||||
}
|
||||
b = cfs.aek.Seal(nonce, nonce, b, nil)
|
||||
}
|
||||
|
||||
@@ -8961,7 +9089,9 @@ func (o *consumerFileStore) Stop() error {
|
||||
// Make sure to write this out..
|
||||
if buf, err = o.encodeState(); err == nil && len(buf) > 0 {
|
||||
if o.aek != nil {
|
||||
buf = o.encryptState(buf)
|
||||
if buf, err = o.encryptState(buf); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+67
-4
@@ -430,6 +430,22 @@ func (g *srvGateway) updateRemotesTLSConfig(opts *Options) {
|
||||
} else if opts.Gateway.TLSConfig != nil {
|
||||
cfg.TLSConfig = opts.Gateway.TLSConfig.Clone()
|
||||
}
|
||||
|
||||
// Ensure that OCSP callbacks are always setup after a reload if needed.
|
||||
mustStaple := opts.OCSPConfig != nil && opts.OCSPConfig.Mode == OCSPModeAlways
|
||||
if mustStaple && opts.Gateway.TLSConfig != nil {
|
||||
clientCB := opts.Gateway.TLSConfig.GetClientCertificate
|
||||
verifyCB := opts.Gateway.TLSConfig.VerifyConnection
|
||||
if mustStaple && cfg.TLSConfig != nil {
|
||||
if clientCB != nil && cfg.TLSConfig.GetClientCertificate == nil {
|
||||
cfg.TLSConfig.GetClientCertificate = clientCB
|
||||
}
|
||||
if verifyCB != nil && cfg.TLSConfig.VerifyConnection == nil {
|
||||
cfg.TLSConfig.VerifyConnection = verifyCB
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
cfg.Unlock()
|
||||
}
|
||||
}
|
||||
@@ -808,10 +824,35 @@ func (s *Server) createGateway(cfg *gatewayCfg, url *url.URL, conn net.Conn) {
|
||||
var timeout float64
|
||||
|
||||
if solicit {
|
||||
var (
|
||||
mustStaple = opts.OCSPConfig != nil && opts.OCSPConfig.Mode == OCSPModeAlways
|
||||
clientCB func(*tls.CertificateRequestInfo) (*tls.Certificate, error)
|
||||
verifyCB func(tls.ConnectionState) error
|
||||
)
|
||||
// Snapshot callbacks for OCSP outside an ongoing reload which might be happening.
|
||||
if mustStaple {
|
||||
s.reloadMu.RLock()
|
||||
s.optsMu.RLock()
|
||||
clientCB = s.opts.Gateway.TLSConfig.GetClientCertificate
|
||||
verifyCB = s.opts.Gateway.TLSConfig.VerifyConnection
|
||||
s.optsMu.RUnlock()
|
||||
s.reloadMu.RUnlock()
|
||||
}
|
||||
|
||||
cfg.RLock()
|
||||
tlsName = cfg.tlsName
|
||||
tlsConfig = cfg.TLSConfig.Clone()
|
||||
timeout = cfg.TLSTimeout
|
||||
|
||||
// Ensure that OCSP callbacks are always setup on gateway reconnect when OCSP policy is set to always.
|
||||
if mustStaple {
|
||||
if clientCB != nil && tlsConfig.GetClientCertificate == nil {
|
||||
tlsConfig.GetClientCertificate = clientCB
|
||||
}
|
||||
if verifyCB != nil && tlsConfig.VerifyConnection == nil {
|
||||
tlsConfig.VerifyConnection = verifyCB
|
||||
}
|
||||
}
|
||||
cfg.RUnlock()
|
||||
} else {
|
||||
tlsConfig = opts.Gateway.TLSConfig
|
||||
@@ -877,6 +918,7 @@ func (s *Server) createGateway(cfg *gatewayCfg, url *url.URL, conn net.Conn) {
|
||||
// Builds and sends the CONNECT protocol for a gateway.
|
||||
// Client lock held on entry.
|
||||
func (c *client) sendGatewayConnect(opts *Options) {
|
||||
// FIXME: This can race with updateRemotesTLSConfig
|
||||
tlsRequired := c.gw.cfg.TLSConfig != nil
|
||||
url := c.gw.connectURL
|
||||
c.gw.connectURL = nil
|
||||
@@ -1154,7 +1196,7 @@ func (c *client) processGatewayInfo(info *Info) {
|
||||
// Starting 2.9.0, we are phasing out the optimistic mode, so change
|
||||
// all accounts to interest-only mode, unless instructed not to do so
|
||||
// in some tests.
|
||||
s.accounts.Range(func(_, v interface{}) bool {
|
||||
s.accounts.Range(func(_, v any) bool {
|
||||
acc := v.(*Account)
|
||||
s.switchAccountToInterestMode(acc.GetName())
|
||||
return true
|
||||
@@ -1307,7 +1349,7 @@ func (s *Server) sendSubsToGateway(c *client, accountName string) {
|
||||
// This function will then execute appropriate function based on the command
|
||||
// contained in the protocol.
|
||||
// <Invoked from a route connection's readLoop>
|
||||
func (s *Server) processGatewayInfoFromRoute(info *Info, routeSrvID string, route *client) {
|
||||
func (s *Server) processGatewayInfoFromRoute(info *Info, routeSrvID string) {
|
||||
switch info.GatewayCmd {
|
||||
case gatewayCmdGossip:
|
||||
s.processImplicitGateway(info)
|
||||
@@ -1708,6 +1750,15 @@ func (s *Server) removeRemoteGatewayConnection(c *client) {
|
||||
cid := c.cid
|
||||
isOutbound := c.gw.outbound
|
||||
gwName := c.gw.name
|
||||
if isOutbound && c.gw.outsim != nil {
|
||||
// We do this to allow the GC to release this connection.
|
||||
// Since the map is used by the rest of the code without client lock,
|
||||
// we can't simply set it to nil, instead, just make sure we empty it.
|
||||
c.gw.outsim.Range(func(k, _ any) bool {
|
||||
c.gw.outsim.Delete(k)
|
||||
return true
|
||||
})
|
||||
}
|
||||
c.mu.Unlock()
|
||||
|
||||
gw := s.gateway
|
||||
@@ -1744,6 +1795,7 @@ func (s *Server) removeRemoteGatewayConnection(c *client) {
|
||||
qSubsRemoved++
|
||||
}
|
||||
}
|
||||
c.subs = nil
|
||||
c.mu.Unlock()
|
||||
// Update total count of qsubs in remote gateways.
|
||||
atomic.AddInt64(&c.srv.gateway.totalQSubs, -qSubsRemoved)
|
||||
@@ -1758,6 +1810,7 @@ func (s *Server) removeRemoteGatewayConnection(c *client) {
|
||||
for _, sub := range c.subs {
|
||||
subs = append(subs, sub)
|
||||
}
|
||||
c.subs = nil
|
||||
c.mu.Unlock()
|
||||
for _, sub := range subs {
|
||||
c.removeReplySub(sub)
|
||||
@@ -1869,6 +1922,10 @@ func (c *client) processGatewayRUnsub(arg []byte) error {
|
||||
return nil
|
||||
}
|
||||
defer c.mu.Unlock()
|
||||
// If closed, c.subs map will be nil, so bail out.
|
||||
if c.isClosed() {
|
||||
return nil
|
||||
}
|
||||
|
||||
ei, _ := c.gw.outsim.Load(accName)
|
||||
if ei != nil {
|
||||
@@ -1975,6 +2032,10 @@ func (c *client) processGatewayRSub(arg []byte) error {
|
||||
return nil
|
||||
}
|
||||
defer c.mu.Unlock()
|
||||
// If closed, c.subs map will be nil, so bail out.
|
||||
if c.isClosed() {
|
||||
return nil
|
||||
}
|
||||
|
||||
ei, _ := c.gw.outsim.Load(bytesToString(accName))
|
||||
// We should always have an existing entry for plain subs because
|
||||
@@ -2421,7 +2482,7 @@ func (g *srvGateway) shouldMapReplyForGatewaySend(acc *Account, reply []byte) bo
|
||||
}
|
||||
|
||||
var subPool = &sync.Pool{
|
||||
New: func() interface{} {
|
||||
New: func() any {
|
||||
return &subscription{}
|
||||
},
|
||||
}
|
||||
@@ -2616,6 +2677,8 @@ func (c *client) sendMsgToGateways(acc *Account, msg, subject, reply []byte, qgr
|
||||
}
|
||||
// Done with subscription, put back to pool. We don't need
|
||||
// to reset content since we explicitly set when using it.
|
||||
// However, make sure to not hold a reference to a connection.
|
||||
sub.client = nil
|
||||
subPool.Put(sub)
|
||||
return didDeliver
|
||||
}
|
||||
@@ -3221,7 +3284,7 @@ func (s *Server) startGWReplyMapExpiration() {
|
||||
}
|
||||
now := time.Now().UnixNano()
|
||||
mapEmpty := true
|
||||
s.gwrm.m.Range(func(k, v interface{}) bool {
|
||||
s.gwrm.m.Range(func(k, v any) bool {
|
||||
g := k.(*gwReplyMapping)
|
||||
l := v.(sync.Locker)
|
||||
l.Lock()
|
||||
|
||||
+7
-11
@@ -842,7 +842,7 @@ func (s *Server) JetStreamEnabledForDomain() bool {
|
||||
var jsFound bool
|
||||
// If we are here we do not have JetStream enabled for ourselves, but we need to check all connected servers.
|
||||
// TODO(dlc) - Could optimize and memoize this.
|
||||
s.nodeToInfo.Range(func(k, v interface{}) bool {
|
||||
s.nodeToInfo.Range(func(k, v any) bool {
|
||||
// This should not be dependent on online status, so only check js.
|
||||
if v.(nodeInfo).js {
|
||||
jsFound = true
|
||||
@@ -1410,10 +1410,6 @@ func (a *Account) EnableJetStream(limits map[string]JetStreamAccountLimits) erro
|
||||
if !cfg.Created.IsZero() {
|
||||
obs.setCreatedTime(cfg.Created)
|
||||
}
|
||||
lseq := e.mset.lastSeq()
|
||||
obs.mu.Lock()
|
||||
err = obs.readStoredState(lseq)
|
||||
obs.mu.Unlock()
|
||||
if err != nil {
|
||||
s.Warnf(" Error restoring consumer %q state: %v", cfg.Name, err)
|
||||
}
|
||||
@@ -1486,8 +1482,8 @@ func (a *Account) filteredStreams(filter string) []*stream {
|
||||
return nil
|
||||
}
|
||||
|
||||
jsa.mu.Lock()
|
||||
defer jsa.mu.Unlock()
|
||||
jsa.mu.RLock()
|
||||
defer jsa.mu.RUnlock()
|
||||
|
||||
var msets []*stream
|
||||
for _, mset := range jsa.streams {
|
||||
@@ -1515,8 +1511,8 @@ func (a *Account) lookupStream(name string) (*stream, error) {
|
||||
if jsa == nil {
|
||||
return nil, NewJSNotEnabledForAccountError()
|
||||
}
|
||||
jsa.mu.Lock()
|
||||
defer jsa.mu.Unlock()
|
||||
jsa.mu.RLock()
|
||||
defer jsa.mu.RUnlock()
|
||||
|
||||
mset, ok := jsa.streams[name]
|
||||
if !ok {
|
||||
@@ -2290,8 +2286,8 @@ func (jsa *jsAccount) delete() {
|
||||
jsa.templates = nil
|
||||
jsa.mu.Unlock()
|
||||
|
||||
for _, ms := range streams {
|
||||
ms.stop(false, false)
|
||||
for _, mset := range streams {
|
||||
mset.stop(false, false)
|
||||
}
|
||||
|
||||
for _, t := range ts {
|
||||
|
||||
+9
-8
@@ -1304,7 +1304,7 @@ func (s *Server) jsTemplateDeleteRequest(sub *subscription, c *client, _ *Accoun
|
||||
s.sendAPIResponse(ci, acc, subject, reply, string(msg), s.jsonResponse(resp))
|
||||
}
|
||||
|
||||
func (s *Server) jsonResponse(v interface{}) string {
|
||||
func (s *Server) jsonResponse(v any) string {
|
||||
b, err := json.Marshal(v)
|
||||
if err != nil {
|
||||
s.Warnf("Problem marshaling JSON for JetStream API:", err)
|
||||
@@ -1908,6 +1908,7 @@ func (s *Server) jsStreamInfoRequest(sub *subscription, c *client, a *Account, s
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
config := mset.config()
|
||||
|
||||
resp.StreamInfo = &StreamInfo{
|
||||
@@ -2512,7 +2513,7 @@ func (s *Server) jsLeaderServerStreamMoveRequest(sub *subscription, c *client, _
|
||||
peers = nil
|
||||
|
||||
clusters := map[string]struct{}{}
|
||||
s.nodeToInfo.Range(func(_, ni interface{}) bool {
|
||||
s.nodeToInfo.Range(func(_, ni any) bool {
|
||||
if currCluster != ni.(nodeInfo).cluster {
|
||||
clusters[ni.(nodeInfo).cluster] = struct{}{}
|
||||
}
|
||||
@@ -2818,11 +2819,11 @@ func isEmptyRequest(req []byte) bool {
|
||||
return true
|
||||
}
|
||||
// If we are here we didn't get our simple match, but still could be valid.
|
||||
var v interface{}
|
||||
var v any
|
||||
if err := json.Unmarshal(req, &v); err != nil {
|
||||
return false
|
||||
}
|
||||
vm, ok := v.(map[string]interface{})
|
||||
vm, ok := v.(map[string]any)
|
||||
if !ok {
|
||||
return false
|
||||
}
|
||||
@@ -3327,7 +3328,7 @@ func (s *Server) jsStreamRestoreRequest(sub *subscription, c *client, _ *Account
|
||||
}
|
||||
|
||||
if s.JetStreamIsClustered() {
|
||||
s.jsClusteredStreamRestoreRequest(ci, acc, &req, stream, subject, reply, rmsg)
|
||||
s.jsClusteredStreamRestoreRequest(ci, acc, &req, subject, reply, rmsg)
|
||||
return
|
||||
}
|
||||
|
||||
@@ -3657,7 +3658,7 @@ func (s *Server) jsStreamSnapshotRequest(sub *subscription, c *client, _ *Accoun
|
||||
})
|
||||
|
||||
// Now do the real streaming.
|
||||
s.streamSnapshot(ci, acc, mset, sr, &req)
|
||||
s.streamSnapshot(acc, mset, sr, &req)
|
||||
|
||||
end := time.Now().UTC()
|
||||
|
||||
@@ -3687,7 +3688,7 @@ const defaultSnapshotChunkSize = 128 * 1024
|
||||
const defaultSnapshotWindowSize = 8 * 1024 * 1024 // 8MB
|
||||
|
||||
// streamSnapshot will stream out our snapshot to the reply subject.
|
||||
func (s *Server) streamSnapshot(ci *ClientInfo, acc *Account, mset *stream, sr *SnapshotResult, req *JSApiStreamSnapshotRequest) {
|
||||
func (s *Server) streamSnapshot(acc *Account, mset *stream, sr *SnapshotResult, req *JSApiStreamSnapshotRequest) {
|
||||
chunkSize := req.ChunkSize
|
||||
if chunkSize == 0 {
|
||||
chunkSize = defaultSnapshotChunkSize
|
||||
@@ -4230,7 +4231,7 @@ func (s *Server) jsConsumerInfoRequest(sub *subscription, c *client, _ *Account,
|
||||
return
|
||||
}
|
||||
|
||||
// If we are in clustered mode we need to be the stream leader to proceed.
|
||||
// If we are in clustered mode we need to be the consumer leader to proceed.
|
||||
if s.JetStreamIsClustered() {
|
||||
// Check to make sure the consumer is assigned.
|
||||
js, cc := s.getJetStreamCluster()
|
||||
|
||||
+168
-78
@@ -26,6 +26,7 @@ import (
|
||||
"path/filepath"
|
||||
"reflect"
|
||||
"sort"
|
||||
"strconv"
|
||||
"strings"
|
||||
"sync/atomic"
|
||||
"time"
|
||||
@@ -188,7 +189,7 @@ func (s *Server) trackedJetStreamServers() (js, total int) {
|
||||
if !s.isRunning() || !s.eventsEnabled() {
|
||||
return -1, -1
|
||||
}
|
||||
s.nodeToInfo.Range(func(k, v interface{}) bool {
|
||||
s.nodeToInfo.Range(func(k, v any) bool {
|
||||
si := v.(nodeInfo)
|
||||
if si.js {
|
||||
js++
|
||||
@@ -537,7 +538,7 @@ func (js *jetStream) isStreamHealthy(acc *Account, sa *streamAssignment) bool {
|
||||
if !mset.isCatchingUp() {
|
||||
return true
|
||||
}
|
||||
} else if node != nil {
|
||||
} else { // node != nil
|
||||
if node != mset.raftNode() {
|
||||
s.Warnf("Detected stream cluster node skew '%s > %s'", acc.GetName(), streamName)
|
||||
node.Delete()
|
||||
@@ -550,7 +551,7 @@ func (js *jetStream) isStreamHealthy(acc *Account, sa *streamAssignment) bool {
|
||||
return false
|
||||
}
|
||||
|
||||
// isConsumerCurrent will determine if the consumer is up to date.
|
||||
// isConsumerHealthy will determine if the consumer is up to date.
|
||||
// For R1 it will make sure the consunmer is present on this server.
|
||||
func (js *jetStream) isConsumerHealthy(mset *stream, consumer string, ca *consumerAssignment) bool {
|
||||
if mset == nil {
|
||||
@@ -2151,6 +2152,58 @@ func genPeerInfo(peers []string, split int) (newPeers, oldPeers []string, newPee
|
||||
return
|
||||
}
|
||||
|
||||
// This will wait for a period of time until all consumers are registered and have
|
||||
// their consumer assignments assigned.
|
||||
// Should only be called from monitorStream.
|
||||
func (mset *stream) waitOnConsumerAssignments() {
|
||||
mset.mu.RLock()
|
||||
s, js, acc, sa, name := mset.srv, mset.js, mset.acc, mset.sa, mset.cfg.Name
|
||||
mset.mu.RUnlock()
|
||||
|
||||
if s == nil || js == nil || acc == nil || sa == nil {
|
||||
return
|
||||
}
|
||||
|
||||
js.mu.RLock()
|
||||
numExpectedConsumers := len(sa.consumers)
|
||||
js.mu.RUnlock()
|
||||
|
||||
// Max to wait.
|
||||
const maxWaitTime = 10 * time.Second
|
||||
const sleepTime = 500 * time.Millisecond
|
||||
|
||||
// Wait up to 10s
|
||||
timeout := time.Now().Add(maxWaitTime)
|
||||
for time.Now().Before(timeout) {
|
||||
var numReady int
|
||||
for _, o := range mset.getConsumers() {
|
||||
// Make sure we are registered with our consumer assignment.
|
||||
if ca := o.consumerAssignment(); ca != nil {
|
||||
numReady++
|
||||
} else {
|
||||
break
|
||||
}
|
||||
}
|
||||
// Check if we are good.
|
||||
if numReady >= numExpectedConsumers {
|
||||
break
|
||||
}
|
||||
|
||||
s.Debugf("Waiting for consumers for interest based stream '%s > %s'", acc.Name, name)
|
||||
select {
|
||||
case <-s.quitCh:
|
||||
return
|
||||
case <-mset.monitorQuitC():
|
||||
return
|
||||
case <-time.After(sleepTime):
|
||||
}
|
||||
}
|
||||
|
||||
if actual := mset.numConsumers(); actual < numExpectedConsumers {
|
||||
s.Warnf("All consumers not online for '%s > %s': expected %d but only have %d", acc.Name, name, numExpectedConsumers, actual)
|
||||
}
|
||||
}
|
||||
|
||||
// Monitor our stream node for this stream.
|
||||
func (js *jetStream) monitorStream(mset *stream, sa *streamAssignment, sendSnapshot bool) {
|
||||
s, cc := js.server(), js.cluster
|
||||
@@ -2303,33 +2356,11 @@ func (js *jetStream) monitorStream(mset *stream, sa *streamAssignment, sendSnaps
|
||||
}
|
||||
defer stopDirectMonitoring()
|
||||
|
||||
// Check if we are interest based and if so and we have an active stream wait until we
|
||||
// have the consumers attached. This can become important when a server has lots of assets
|
||||
// since we process streams first then consumers as an asset class.
|
||||
if mset != nil && mset.isInterestRetention() {
|
||||
js.mu.RLock()
|
||||
numExpectedConsumers := len(sa.consumers)
|
||||
js.mu.RUnlock()
|
||||
if mset.numConsumers() < numExpectedConsumers {
|
||||
s.Debugf("Waiting for consumers for interest based stream '%s > %s'", accName, mset.name())
|
||||
// Wait up to 10s
|
||||
const maxWaitTime = 10 * time.Second
|
||||
const sleepTime = 250 * time.Millisecond
|
||||
timeout := time.Now().Add(maxWaitTime)
|
||||
for time.Now().Before(timeout) {
|
||||
if mset.numConsumers() >= numExpectedConsumers {
|
||||
break
|
||||
}
|
||||
select {
|
||||
case <-s.quitCh:
|
||||
return
|
||||
case <-time.After(sleepTime):
|
||||
}
|
||||
}
|
||||
if actual := mset.numConsumers(); actual < numExpectedConsumers {
|
||||
s.Warnf("All consumers not online for '%s > %s': expected %d but only have %d", accName, mset.name(), numExpectedConsumers, actual)
|
||||
}
|
||||
}
|
||||
// Wait on our consumers to be assigned and running before proceeding.
|
||||
// This can become important when a server has lots of assets
|
||||
// since we process streams first then consumers as an asset class.
|
||||
mset.waitOnConsumerAssignments()
|
||||
}
|
||||
|
||||
// This is triggered during a scale up from R1 to clustered mode. We need the new followers to catchup,
|
||||
@@ -2358,7 +2389,7 @@ func (js *jetStream) monitorStream(mset *stream, sa *streamAssignment, sendSnaps
|
||||
if ce == nil {
|
||||
isRecovering = false
|
||||
// If we are interest based make sure to check consumers if interest retention policy.
|
||||
// This is to make sure we process any outstanding acks.
|
||||
// This is to make sure we process any outstanding acks from all consumers.
|
||||
mset.checkInterestState()
|
||||
// Make sure we create a new snapshot in case things have changed such that any existing
|
||||
// snapshot may no longer be valid.
|
||||
@@ -2851,10 +2882,9 @@ func (js *jetStream) applyStreamEntries(mset *stream, ce *CommittedEntry, isReco
|
||||
if lseq-clfs < last {
|
||||
s.Debugf("Apply stream entries for '%s > %s' skipping message with sequence %d with last of %d",
|
||||
mset.account(), mset.name(), lseq+1-clfs, last)
|
||||
|
||||
mset.mu.Lock()
|
||||
// Check for any preAcks in case we are interest based.
|
||||
mset.clearAllPreAcks(lseq + 1 - mset.clfs)
|
||||
mset.clearAllPreAcks(lseq + 1 - clfs)
|
||||
mset.mu.Unlock()
|
||||
continue
|
||||
}
|
||||
@@ -3154,11 +3184,11 @@ func (js *jetStream) processStreamLeaderChange(mset *stream, isLeader bool) {
|
||||
|
||||
// Clear clseq. If we become leader again, it will be fixed up
|
||||
// automatically on the next processClusteredInboundMsg call.
|
||||
mset.mu.Lock()
|
||||
mset.clMu.Lock()
|
||||
if mset.clseq > 0 {
|
||||
mset.clseq = 0
|
||||
}
|
||||
mset.mu.Unlock()
|
||||
mset.clMu.Unlock()
|
||||
}
|
||||
|
||||
// Tell stream to switch leader status.
|
||||
@@ -4150,7 +4180,6 @@ func (js *jetStream) processConsumerRemoval(ca *consumerAssignment) {
|
||||
js.mu.Unlock()
|
||||
return
|
||||
}
|
||||
isMember := ca.Group.isMember(cc.meta.ID())
|
||||
wasLeader := cc.isConsumerLeader(ca.Client.serviceAccount(), ca.Stream, ca.Name)
|
||||
|
||||
// Delete from our state.
|
||||
@@ -4169,7 +4198,7 @@ func (js *jetStream) processConsumerRemoval(ca *consumerAssignment) {
|
||||
js.mu.Unlock()
|
||||
|
||||
if needDelete {
|
||||
js.processClusterDeleteConsumer(ca, isMember, wasLeader)
|
||||
js.processClusterDeleteConsumer(ca, wasLeader)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4247,7 +4276,7 @@ func (js *jetStream) processClusterCreateConsumer(ca *consumerAssignment, state
|
||||
var didCreate, isConfigUpdate, needsLocalResponse bool
|
||||
if o == nil {
|
||||
// Add in the consumer if needed.
|
||||
if o, err = mset.addConsumerWithAssignment(ca.Config, ca.Name, ca, wasExisting, ActionCreateOrUpdate); err == nil {
|
||||
if o, err = mset.addConsumerWithAssignment(ca.Config, ca.Name, ca, js.isMetaRecovering(), ActionCreateOrUpdate); err == nil {
|
||||
didCreate = true
|
||||
}
|
||||
} else {
|
||||
@@ -4427,7 +4456,7 @@ func (js *jetStream) processClusterCreateConsumer(ca *consumerAssignment, state
|
||||
}
|
||||
}
|
||||
|
||||
func (js *jetStream) processClusterDeleteConsumer(ca *consumerAssignment, isMember, wasLeader bool) {
|
||||
func (js *jetStream) processClusterDeleteConsumer(ca *consumerAssignment, wasLeader bool) {
|
||||
if ca == nil {
|
||||
return
|
||||
}
|
||||
@@ -4709,8 +4738,6 @@ func (js *jetStream) monitorConsumer(o *consumer, ca *consumerAssignment) {
|
||||
if n.NeedSnapshot() {
|
||||
doSnapshot(true)
|
||||
}
|
||||
// Check our state if we are under an interest based stream.
|
||||
o.checkStateForInterestStream()
|
||||
} else if err := js.applyConsumerEntries(o, ce, isLeader); err == nil {
|
||||
ne, nb := n.Applied(ce.Index)
|
||||
ce.ReturnToPool()
|
||||
@@ -4730,7 +4757,18 @@ func (js *jetStream) monitorConsumer(o *consumer, ca *consumerAssignment) {
|
||||
|
||||
// Process the change.
|
||||
if err := js.processConsumerLeaderChange(o, isLeader); err == nil && isLeader {
|
||||
// Check our state if we are under an interest based stream.
|
||||
o.checkStateForInterestStream()
|
||||
// Do a snapshot.
|
||||
doSnapshot(true)
|
||||
// Synchronize followers to our state. Only send out if we have state.
|
||||
if n != nil {
|
||||
if _, _, applied := n.Progress(); applied > 0 {
|
||||
if snap, err := o.store.EncodedState(); err == nil {
|
||||
n.SendSnapshot(snap)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// We may receive a leader change after the consumer assignment which would cancel us
|
||||
@@ -4845,9 +4883,9 @@ func (js *jetStream) applyConsumerEntries(o *consumer, ce *CommittedEntry, isLea
|
||||
if s != nil && mset != nil {
|
||||
s.Warnf("Consumer '%s > %s > %s' error on store update from snapshot entry: %v", acc, mset.name(), name, err)
|
||||
}
|
||||
} else {
|
||||
o.checkStateForInterestStream()
|
||||
}
|
||||
// Check our interest state if applicable.
|
||||
o.checkStateForInterestStream()
|
||||
}
|
||||
|
||||
} else if e.Type == EntryRemovePeer {
|
||||
@@ -4953,6 +4991,10 @@ func (o *consumer) processReplicatedAck(dseq, sseq uint64) error {
|
||||
o.mu.Unlock()
|
||||
return errConsumerClosed
|
||||
}
|
||||
if mset.closed.Load() {
|
||||
o.mu.Unlock()
|
||||
return errStreamClosed
|
||||
}
|
||||
|
||||
// Update activity.
|
||||
o.lat = time.Now()
|
||||
@@ -5221,7 +5263,8 @@ func (js *jetStream) processStreamAssignmentResults(sub *subscription, c *client
|
||||
// If cluster is defined we can not retry.
|
||||
if cfg.Placement == nil || cfg.Placement.Cluster == _EMPTY_ {
|
||||
// If we have additional clusters to try we can retry.
|
||||
if ci != nil && len(ci.Alternates) > 0 {
|
||||
// We have already verified that ci != nil.
|
||||
if len(ci.Alternates) > 0 {
|
||||
if rg, err := js.createGroupForStream(ci, cfg); err != nil {
|
||||
s.Warnf("Retrying cluster placement for stream '%s > %s' failed due to placement error: %+v", result.Account, result.Stream, err)
|
||||
} else {
|
||||
@@ -6022,7 +6065,7 @@ var (
|
||||
|
||||
// blocking utility call to perform requests on the system account
|
||||
// returns (synchronized) v or error
|
||||
func sysRequest[T any](s *Server, subjFormat string, args ...interface{}) (*T, error) {
|
||||
func sysRequest[T any](s *Server, subjFormat string, args ...any) (*T, error) {
|
||||
isubj := fmt.Sprintf(subjFormat, args...)
|
||||
|
||||
s.mu.Lock()
|
||||
@@ -6445,7 +6488,7 @@ func (s *Server) jsClusteredStreamRestoreRequest(
|
||||
ci *ClientInfo,
|
||||
acc *Account,
|
||||
req *JSApiStreamRestoreRequest,
|
||||
stream, subject, reply string, rmsg []byte) {
|
||||
subject, reply string, rmsg []byte) {
|
||||
|
||||
js, cc := s.getJetStreamCluster()
|
||||
if js == nil || cc == nil {
|
||||
@@ -7014,7 +7057,7 @@ func (cc *jetStreamCluster) createGroupForConsumer(cfg *ConsumerConfig, sa *stre
|
||||
return &raftGroup{Name: groupNameForConsumer(peers, storage), Storage: storage, Peers: peers}
|
||||
}
|
||||
|
||||
// jsClusteredConsumerRequest is first point of entry to create a consumer with R > 1.
|
||||
// jsClusteredConsumerRequest is first point of entry to create a consumer in clustered mode.
|
||||
func (s *Server) jsClusteredConsumerRequest(ci *ClientInfo, acc *Account, subject, reply string, rmsg []byte, stream string, cfg *ConsumerConfig, action ConsumerAction) {
|
||||
js, cc := s.getJetStreamCluster()
|
||||
if js == nil || cc == nil {
|
||||
@@ -7189,6 +7232,40 @@ func (s *Server) jsClusteredConsumerRequest(ci *ClientInfo, acc *Account, subjec
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Check if we are work queue policy.
|
||||
// We will do pre-checks here to avoid thrashing meta layer.
|
||||
if sa.Config.Retention == WorkQueuePolicy && !cfg.Direct {
|
||||
if cfg.AckPolicy != AckExplicit {
|
||||
resp.Error = NewJSConsumerWQRequiresExplicitAckError()
|
||||
s.sendAPIErrResponse(ci, acc, subject, reply, string(rmsg), s.jsonResponse(&resp))
|
||||
return
|
||||
}
|
||||
subjects := gatherSubjectFilters(cfg.FilterSubject, cfg.FilterSubjects)
|
||||
if len(subjects) == 0 && len(sa.consumers) > 0 {
|
||||
resp.Error = NewJSConsumerWQMultipleUnfilteredError()
|
||||
s.sendAPIErrResponse(ci, acc, subject, reply, string(rmsg), s.jsonResponse(&resp))
|
||||
return
|
||||
}
|
||||
// Check here to make sure we have not collided with another.
|
||||
if len(sa.consumers) > 0 {
|
||||
for _, oca := range sa.consumers {
|
||||
if oca.Name == oname {
|
||||
continue
|
||||
}
|
||||
for _, psubj := range gatherSubjectFilters(oca.Config.FilterSubject, oca.Config.FilterSubjects) {
|
||||
for _, subj := range subjects {
|
||||
if SubjectsCollide(subj, psubj) {
|
||||
resp.Error = NewJSConsumerWQConsumerNotUniqueError()
|
||||
s.sendAPIErrResponse(ci, acc, subject, reply, string(rmsg), s.jsonResponse(&resp))
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ca = &consumerAssignment{
|
||||
Group: rg,
|
||||
Stream: stream,
|
||||
@@ -7272,8 +7349,6 @@ func (s *Server) jsClusteredConsumerRequest(ci *ClientInfo, acc *Account, subjec
|
||||
ca = nca
|
||||
}
|
||||
|
||||
eca := encodeAddConsumerAssignment(ca)
|
||||
|
||||
// Mark this as pending.
|
||||
if sa.consumers == nil {
|
||||
sa.consumers = make(map[string]*consumerAssignment)
|
||||
@@ -7281,7 +7356,7 @@ func (s *Server) jsClusteredConsumerRequest(ci *ClientInfo, acc *Account, subjec
|
||||
sa.consumers[ca.Name] = ca
|
||||
|
||||
// Do formal proposal.
|
||||
cc.meta.Propose(eca)
|
||||
cc.meta.Propose(encodeAddConsumerAssignment(ca))
|
||||
}
|
||||
|
||||
func encodeAddConsumerAssignment(ca *consumerAssignment) []byte {
|
||||
@@ -7551,12 +7626,6 @@ func (mset *stream) processClusteredInboundMsg(subject, reply string, hdr, msg [
|
||||
maxMsgSize, lseq := int(mset.cfg.MaxMsgSize), mset.lseq
|
||||
interestPolicy, discard, maxMsgs, maxBytes := mset.cfg.Retention != LimitsPolicy, mset.cfg.Discard, mset.cfg.MaxMsgs, mset.cfg.MaxBytes
|
||||
isLeader, isSealed := mset.isLeader(), mset.cfg.Sealed
|
||||
|
||||
// We need to track state to check limits if interest retention and discard new with max msgs or bytes.
|
||||
var state StreamState
|
||||
if interestPolicy && discard == DiscardNew && (maxMsgs > 0 || maxBytes > 0) {
|
||||
mset.store.FastState(&state)
|
||||
}
|
||||
mset.mu.RUnlock()
|
||||
|
||||
// This should not happen but possible now that we allow scale up, and scale down where this could trigger.
|
||||
@@ -7620,7 +7689,21 @@ func (mset *stream) processClusteredInboundMsg(subject, reply string, hdr, msg [
|
||||
}
|
||||
|
||||
// Some header checks can be checked pre proposal. Most can not.
|
||||
var msgId string
|
||||
if len(hdr) > 0 {
|
||||
// Since we encode header len as u16 make sure we do not exceed.
|
||||
// Again this works if it goes through but better to be pre-emptive.
|
||||
if len(hdr) > math.MaxUint16 {
|
||||
err := fmt.Errorf("JetStream header size exceeds limits for '%s > %s'", jsa.acc().Name, mset.cfg.Name)
|
||||
s.RateLimitWarnf(err.Error())
|
||||
if canRespond {
|
||||
var resp = &JSPubAckResponse{PubAck: &PubAck{Stream: name}}
|
||||
resp.Error = NewJSStreamHeaderExceedsMaximumError()
|
||||
response, _ = json.Marshal(resp)
|
||||
outq.send(newJSPubMsg(reply, _EMPTY_, _EMPTY_, nil, response, nil, 0))
|
||||
}
|
||||
return err
|
||||
}
|
||||
// Expected last sequence per subject.
|
||||
// We can check for last sequence per subject but only if the expected seq <= lseq.
|
||||
if seq, exists := getExpectedLastSeqPerSubject(hdr); exists && store != nil && seq > 0 && seq <= lseq {
|
||||
@@ -7650,22 +7733,29 @@ func (mset *stream) processClusteredInboundMsg(subject, reply string, hdr, msg [
|
||||
b, _ := json.Marshal(resp)
|
||||
outq.sendMsg(reply, b)
|
||||
}
|
||||
return errors.New("expected stream does not match")
|
||||
return errStreamMismatch
|
||||
}
|
||||
}
|
||||
|
||||
// Since we encode header len as u16 make sure we do not exceed.
|
||||
// Again this works if it goes through but better to be pre-emptive.
|
||||
if len(hdr) > math.MaxUint16 {
|
||||
err := fmt.Errorf("JetStream header size exceeds limits for '%s > %s'", jsa.acc().Name, mset.cfg.Name)
|
||||
s.RateLimitWarnf(err.Error())
|
||||
if canRespond {
|
||||
var resp = &JSPubAckResponse{PubAck: &PubAck{Stream: name}}
|
||||
resp.Error = NewJSStreamHeaderExceedsMaximumError()
|
||||
response, _ = json.Marshal(resp)
|
||||
outq.send(newJSPubMsg(reply, _EMPTY_, _EMPTY_, nil, response, nil, 0))
|
||||
// Check for MsgIds here at the cluster level to avoid excessive CLFS accounting.
|
||||
// Will help during restarts.
|
||||
if msgId = getMsgId(hdr); msgId != _EMPTY_ {
|
||||
mset.mu.Lock()
|
||||
if dde := mset.checkMsgId(msgId); dde != nil {
|
||||
var buf [256]byte
|
||||
pubAck := append(buf[:0], mset.pubAck...)
|
||||
seq := dde.seq
|
||||
mset.mu.Unlock()
|
||||
if canRespond {
|
||||
response := append(pubAck, strconv.FormatUint(seq, 10)...)
|
||||
response = append(response, ",\"duplicate\": true}"...)
|
||||
outq.sendMsg(reply, response)
|
||||
}
|
||||
return errMsgIdDuplicate
|
||||
}
|
||||
// FIXME(dlc) - locking conflict with accessing mset.clseq
|
||||
// For now we stage with zero, and will update in processStreamMsg.
|
||||
mset.storeMsgIdLocked(&ddentry{msgId, 0, time.Now().UnixNano()})
|
||||
mset.mu.Unlock()
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
// Proceed with proposing this message.
|
||||
@@ -7688,12 +7778,15 @@ func (mset *stream) processClusteredInboundMsg(subject, reply string, hdr, msg [
|
||||
if mset.inflight == nil {
|
||||
mset.inflight = make(map[uint64]uint64)
|
||||
}
|
||||
if mset.cfg.Storage == FileStorage {
|
||||
if stype == FileStorage {
|
||||
mset.inflight[mset.clseq] = fileStoreMsgSize(subject, hdr, msg)
|
||||
} else {
|
||||
mset.inflight[mset.clseq] = memStoreMsgSize(subject, hdr, msg)
|
||||
}
|
||||
|
||||
var state StreamState
|
||||
mset.store.FastState(&state)
|
||||
|
||||
var err error
|
||||
if maxMsgs > 0 && state.Msgs+uint64(len(mset.inflight)) > uint64(maxMsgs) {
|
||||
err = ErrMaxMsgs
|
||||
@@ -7721,12 +7814,10 @@ func (mset *stream) processClusteredInboundMsg(subject, reply string, hdr, msg [
|
||||
}
|
||||
|
||||
esm := encodeStreamMsgAllowCompress(subject, reply, hdr, msg, mset.clseq, time.Now().UnixNano(), mset.compressOK)
|
||||
mset.clseq++
|
||||
|
||||
// Do proposal.
|
||||
err := node.Propose(esm)
|
||||
if err != nil && mset.clseq > 0 {
|
||||
mset.clseq--
|
||||
if err == nil {
|
||||
mset.clseq++
|
||||
}
|
||||
|
||||
// Check to see if we are being overrun.
|
||||
@@ -7745,10 +7836,9 @@ func (mset *stream) processClusteredInboundMsg(subject, reply string, hdr, msg [
|
||||
// If we errored out respond here.
|
||||
outq.send(newJSPubMsg(reply, _EMPTY_, _EMPTY_, nil, response, nil, 0))
|
||||
}
|
||||
}
|
||||
|
||||
if err != nil && isOutOfSpaceErr(err) {
|
||||
s.handleOutOfSpace(mset)
|
||||
if isOutOfSpaceErr(err) {
|
||||
s.handleOutOfSpace(mset)
|
||||
}
|
||||
}
|
||||
|
||||
return err
|
||||
|
||||
+1
-1
@@ -76,7 +76,7 @@ func (e *ApiError) Error() string {
|
||||
return fmt.Sprintf("%s (%d)", e.Description, e.ErrCode)
|
||||
}
|
||||
|
||||
func (e *ApiError) toReplacerArgs(replacements []interface{}) []string {
|
||||
func (e *ApiError) toReplacerArgs(replacements []any) []string {
|
||||
var (
|
||||
ra []string
|
||||
key string
|
||||
|
||||
+16
-4
@@ -96,6 +96,8 @@ type leaf struct {
|
||||
tsubt *time.Timer
|
||||
// Selected compression mode, which may be different from the server configured mode.
|
||||
compression string
|
||||
// This is for GW map replies.
|
||||
gwSub *subscription
|
||||
}
|
||||
|
||||
// Used for remote (solicited) leafnodes.
|
||||
@@ -1690,9 +1692,16 @@ func (s *Server) addLeafNodeConnection(c *client, srvName, clusterName string, c
|
||||
func (s *Server) removeLeafNodeConnection(c *client) {
|
||||
c.mu.Lock()
|
||||
cid := c.cid
|
||||
if c.leaf != nil && c.leaf.tsubt != nil {
|
||||
c.leaf.tsubt.Stop()
|
||||
c.leaf.tsubt = nil
|
||||
if c.leaf != nil {
|
||||
if c.leaf.tsubt != nil {
|
||||
c.leaf.tsubt.Stop()
|
||||
c.leaf.tsubt = nil
|
||||
}
|
||||
if c.leaf.gwSub != nil {
|
||||
s.gwLeafSubs.Remove(c.leaf.gwSub)
|
||||
// We need to set this to nil for GC to release the connection
|
||||
c.leaf.gwSub = nil
|
||||
}
|
||||
}
|
||||
c.mu.Unlock()
|
||||
s.mu.Lock()
|
||||
@@ -1980,7 +1989,10 @@ func (s *Server) initLeafNodeSmapAndSendSubs(c *client) {
|
||||
if c.isSpokeLeafNode() {
|
||||
// Add a fake subscription for this solicited leafnode connection
|
||||
// so that we can send back directly for mapped GW replies.
|
||||
c.srv.gwLeafSubs.Insert(&subscription{client: c, subject: []byte(gwReplyPrefix + ">")})
|
||||
// We need to keep track of this subscription so it can be removed
|
||||
// when the connection is closed so that the GC can release it.
|
||||
c.leaf.gwSub = &subscription{client: c, subject: []byte(gwReplyPrefix + ">")}
|
||||
c.srv.gwLeafSubs.Insert(c.leaf.gwSub)
|
||||
}
|
||||
|
||||
// Now walk the results and add them to our smap
|
||||
|
||||
+26
-26
@@ -27,22 +27,22 @@ import (
|
||||
type Logger interface {
|
||||
|
||||
// Log a notice statement
|
||||
Noticef(format string, v ...interface{})
|
||||
Noticef(format string, v ...any)
|
||||
|
||||
// Log a warning statement
|
||||
Warnf(format string, v ...interface{})
|
||||
Warnf(format string, v ...any)
|
||||
|
||||
// Log a fatal error
|
||||
Fatalf(format string, v ...interface{})
|
||||
Fatalf(format string, v ...any)
|
||||
|
||||
// Log an error
|
||||
Errorf(format string, v ...interface{})
|
||||
Errorf(format string, v ...any)
|
||||
|
||||
// Log a debug statement
|
||||
Debugf(format string, v ...interface{})
|
||||
Debugf(format string, v ...any)
|
||||
|
||||
// Log a trace statement
|
||||
Tracef(format string, v ...interface{})
|
||||
Tracef(format string, v ...any)
|
||||
}
|
||||
|
||||
// ConfigureLogger configures and sets the logger for the server.
|
||||
@@ -178,48 +178,48 @@ func (s *Server) ReOpenLogFile() {
|
||||
}
|
||||
|
||||
// Noticef logs a notice statement
|
||||
func (s *Server) Noticef(format string, v ...interface{}) {
|
||||
s.executeLogCall(func(logger Logger, format string, v ...interface{}) {
|
||||
func (s *Server) Noticef(format string, v ...any) {
|
||||
s.executeLogCall(func(logger Logger, format string, v ...any) {
|
||||
logger.Noticef(format, v...)
|
||||
}, format, v...)
|
||||
}
|
||||
|
||||
// Errorf logs an error
|
||||
func (s *Server) Errorf(format string, v ...interface{}) {
|
||||
s.executeLogCall(func(logger Logger, format string, v ...interface{}) {
|
||||
func (s *Server) Errorf(format string, v ...any) {
|
||||
s.executeLogCall(func(logger Logger, format string, v ...any) {
|
||||
logger.Errorf(format, v...)
|
||||
}, format, v...)
|
||||
}
|
||||
|
||||
// Error logs an error with a scope
|
||||
func (s *Server) Errors(scope interface{}, e error) {
|
||||
s.executeLogCall(func(logger Logger, format string, v ...interface{}) {
|
||||
func (s *Server) Errors(scope any, e error) {
|
||||
s.executeLogCall(func(logger Logger, format string, v ...any) {
|
||||
logger.Errorf(format, v...)
|
||||
}, "%s - %s", scope, UnpackIfErrorCtx(e))
|
||||
}
|
||||
|
||||
// Error logs an error with a context
|
||||
func (s *Server) Errorc(ctx string, e error) {
|
||||
s.executeLogCall(func(logger Logger, format string, v ...interface{}) {
|
||||
s.executeLogCall(func(logger Logger, format string, v ...any) {
|
||||
logger.Errorf(format, v...)
|
||||
}, "%s: %s", ctx, UnpackIfErrorCtx(e))
|
||||
}
|
||||
|
||||
// Error logs an error with a scope and context
|
||||
func (s *Server) Errorsc(scope interface{}, ctx string, e error) {
|
||||
s.executeLogCall(func(logger Logger, format string, v ...interface{}) {
|
||||
func (s *Server) Errorsc(scope any, ctx string, e error) {
|
||||
s.executeLogCall(func(logger Logger, format string, v ...any) {
|
||||
logger.Errorf(format, v...)
|
||||
}, "%s - %s: %s", scope, ctx, UnpackIfErrorCtx(e))
|
||||
}
|
||||
|
||||
// Warnf logs a warning error
|
||||
func (s *Server) Warnf(format string, v ...interface{}) {
|
||||
s.executeLogCall(func(logger Logger, format string, v ...interface{}) {
|
||||
func (s *Server) Warnf(format string, v ...any) {
|
||||
s.executeLogCall(func(logger Logger, format string, v ...any) {
|
||||
logger.Warnf(format, v...)
|
||||
}, format, v...)
|
||||
}
|
||||
|
||||
func (s *Server) RateLimitWarnf(format string, v ...interface{}) {
|
||||
func (s *Server) RateLimitWarnf(format string, v ...any) {
|
||||
statement := fmt.Sprintf(format, v...)
|
||||
if _, loaded := s.rateLimitLogging.LoadOrStore(statement, time.Now()); loaded {
|
||||
return
|
||||
@@ -227,7 +227,7 @@ func (s *Server) RateLimitWarnf(format string, v ...interface{}) {
|
||||
s.Warnf("%s", statement)
|
||||
}
|
||||
|
||||
func (s *Server) RateLimitDebugf(format string, v ...interface{}) {
|
||||
func (s *Server) RateLimitDebugf(format string, v ...any) {
|
||||
statement := fmt.Sprintf(format, v...)
|
||||
if _, loaded := s.rateLimitLogging.LoadOrStore(statement, time.Now()); loaded {
|
||||
return
|
||||
@@ -236,35 +236,35 @@ func (s *Server) RateLimitDebugf(format string, v ...interface{}) {
|
||||
}
|
||||
|
||||
// Fatalf logs a fatal error
|
||||
func (s *Server) Fatalf(format string, v ...interface{}) {
|
||||
s.executeLogCall(func(logger Logger, format string, v ...interface{}) {
|
||||
func (s *Server) Fatalf(format string, v ...any) {
|
||||
s.executeLogCall(func(logger Logger, format string, v ...any) {
|
||||
logger.Fatalf(format, v...)
|
||||
}, format, v...)
|
||||
}
|
||||
|
||||
// Debugf logs a debug statement
|
||||
func (s *Server) Debugf(format string, v ...interface{}) {
|
||||
func (s *Server) Debugf(format string, v ...any) {
|
||||
if atomic.LoadInt32(&s.logging.debug) == 0 {
|
||||
return
|
||||
}
|
||||
|
||||
s.executeLogCall(func(logger Logger, format string, v ...interface{}) {
|
||||
s.executeLogCall(func(logger Logger, format string, v ...any) {
|
||||
logger.Debugf(format, v...)
|
||||
}, format, v...)
|
||||
}
|
||||
|
||||
// Tracef logs a trace statement
|
||||
func (s *Server) Tracef(format string, v ...interface{}) {
|
||||
func (s *Server) Tracef(format string, v ...any) {
|
||||
if atomic.LoadInt32(&s.logging.trace) == 0 {
|
||||
return
|
||||
}
|
||||
|
||||
s.executeLogCall(func(logger Logger, format string, v ...interface{}) {
|
||||
s.executeLogCall(func(logger Logger, format string, v ...any) {
|
||||
logger.Tracef(format, v...)
|
||||
}, format, v...)
|
||||
}
|
||||
|
||||
func (s *Server) executeLogCall(f func(logger Logger, format string, v ...interface{}), format string, args ...interface{}) {
|
||||
func (s *Server) executeLogCall(f func(logger Logger, format string, v ...any), format string, args ...any) {
|
||||
s.logging.RLock()
|
||||
defer s.logging.RUnlock()
|
||||
if s.logging.logger == nil {
|
||||
|
||||
+65
-27
@@ -126,23 +126,27 @@ func (ms *memStore) storeRawMsg(subj string, hdr, msg []byte, seq uint64, ts int
|
||||
if asl && ms.cfg.DiscardNewPer {
|
||||
return ErrMaxMsgsPerSubject
|
||||
}
|
||||
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 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 {
|
||||
ms.recalculateFirstForSubj(subj, ss.First, ss)
|
||||
}
|
||||
sm, ok := ms.msgs[ss.First]
|
||||
if !ok || memStoreMsgSize(sm.subj, sm.hdr, sm.msg) < memStoreMsgSize(subj, hdr, msg) {
|
||||
return ErrMaxBytes
|
||||
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 {
|
||||
ms.recalculateFirstForSubj(subj, ss.First, ss)
|
||||
}
|
||||
sm, ok := ms.msgs[ss.First]
|
||||
if !ok || memStoreMsgSize(sm.subj, sm.hdr, sm.msg) < memStoreMsgSize(subj, hdr, msg) {
|
||||
return ErrMaxBytes
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -794,15 +798,15 @@ func (ms *memStore) Compact(seq uint64) (uint64, error) {
|
||||
ms.mu.Lock()
|
||||
cb := ms.scb
|
||||
if seq <= ms.state.LastSeq {
|
||||
sm, ok := ms.msgs[seq]
|
||||
if !ok {
|
||||
ms.mu.Unlock()
|
||||
return 0, ErrStoreMsgNotFound
|
||||
}
|
||||
fseq := ms.state.FirstSeq
|
||||
ms.state.FirstSeq = seq
|
||||
ms.state.FirstTime = time.Unix(0, sm.ts).UTC()
|
||||
|
||||
// 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)
|
||||
@@ -985,6 +989,40 @@ func (ms *memStore) LoadLastMsg(subject string, smp *StoreMsg) (*StoreMsg, error
|
||||
return smp, nil
|
||||
}
|
||||
|
||||
// LoadNextMsgMulti will find the next message matching any entry in the sublist.
|
||||
func (ms *memStore) LoadNextMsgMulti(sl *Sublist, 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 r := sl.Match(sm.subj); len(r.psubs) > 0 {
|
||||
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) {
|
||||
@@ -996,7 +1034,7 @@ func (ms *memStore) LoadNextMsg(filter string, wc bool, start uint64, smp *Store
|
||||
}
|
||||
|
||||
// If past the end no results.
|
||||
if start > ms.state.LastSeq {
|
||||
if start > ms.state.LastSeq || ms.state.Msgs == 0 {
|
||||
return nil, ms.state.LastSeq, ErrStoreEOF
|
||||
}
|
||||
|
||||
@@ -1005,7 +1043,7 @@ func (ms *memStore) LoadNextMsg(filter string, wc bool, start uint64, smp *Store
|
||||
}
|
||||
isAll := filter == fwcs
|
||||
|
||||
// Skip scan of ms.fss is number of messages in the block are less than
|
||||
// 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)
|
||||
@@ -1638,7 +1676,7 @@ func (o *consumerMemStore) stateWithCopy(doCopy bool) (*ConsumerState, error) {
|
||||
return state, nil
|
||||
}
|
||||
|
||||
// EncodeState for this consumer store.
|
||||
// EncodedState for this consumer store.
|
||||
func (o *consumerMemStore) EncodedState() ([]byte, error) {
|
||||
o.mu.Lock()
|
||||
defer o.mu.Unlock()
|
||||
|
||||
+9
-9
@@ -993,7 +993,7 @@ func (s *Server) Subsz(opts *SubszOptions) (*Subsz, error) {
|
||||
if subdetail {
|
||||
var raw [4096]*subscription
|
||||
subs := raw[:0]
|
||||
s.accounts.Range(func(k, v interface{}) bool {
|
||||
s.accounts.Range(func(k, v any) bool {
|
||||
acc := v.(*Account)
|
||||
if filterAcc != _EMPTY_ && acc.GetName() != filterAcc {
|
||||
return true
|
||||
@@ -1034,7 +1034,7 @@ func (s *Server) Subsz(opts *SubszOptions) (*Subsz, error) {
|
||||
sz.Subs = details[minoff:maxoff]
|
||||
sz.Total = len(sz.Subs)
|
||||
} else {
|
||||
s.accounts.Range(func(k, v interface{}) bool {
|
||||
s.accounts.Range(func(k, v any) bool {
|
||||
acc := v.(*Account)
|
||||
if filterAcc != _EMPTY_ && acc.GetName() != filterAcc {
|
||||
return true
|
||||
@@ -1714,7 +1714,7 @@ func (s *Server) updateVarzRuntimeFields(v *Varz, forceUpdate bool, pcpu float64
|
||||
|
||||
// Make sure to reset in case we are re-using.
|
||||
v.Subscriptions = 0
|
||||
s.accounts.Range(func(k, val interface{}) bool {
|
||||
s.accounts.Range(func(k, val any) bool {
|
||||
acc := val.(*Account)
|
||||
v.Subscriptions += acc.sl.Count()
|
||||
return true
|
||||
@@ -2006,7 +2006,7 @@ func createOutboundAccountsGatewayz(opts *GatewayzOptions, gw *gateway) []*Accou
|
||||
}
|
||||
|
||||
accs := make([]*AccountGatewayz, 0, 4)
|
||||
gw.outsim.Range(func(k, v interface{}) bool {
|
||||
gw.outsim.Range(func(k, v any) bool {
|
||||
name := k.(string)
|
||||
a := createAccountOutboundGatewayz(name, v)
|
||||
accs = append(accs, a)
|
||||
@@ -2016,7 +2016,7 @@ func createOutboundAccountsGatewayz(opts *GatewayzOptions, gw *gateway) []*Accou
|
||||
}
|
||||
|
||||
// Returns an AccountGatewayz for this gateway outbound connection
|
||||
func createAccountOutboundGatewayz(name string, ei interface{}) *AccountGatewayz {
|
||||
func createAccountOutboundGatewayz(name string, ei any) *AccountGatewayz {
|
||||
a := &AccountGatewayz{
|
||||
Name: name,
|
||||
InterestOnlyThreshold: gatewayMaxRUnsubBeforeSwitch,
|
||||
@@ -2285,7 +2285,7 @@ func (s *Server) AccountStatz(opts *AccountStatzOptions) (*AccountStatz, error)
|
||||
Accounts: []*AccountStat{},
|
||||
}
|
||||
if opts == nil || len(opts.Accounts) == 0 {
|
||||
s.accounts.Range(func(key, a interface{}) bool {
|
||||
s.accounts.Range(func(key, a any) bool {
|
||||
acc := a.(*Account)
|
||||
acc.mu.RLock()
|
||||
if opts.IncludeUnused || acc.numLocalConnections() != 0 {
|
||||
@@ -2535,7 +2535,7 @@ func (s *Server) Accountz(optz *AccountzOptions) (*Accountz, error) {
|
||||
}
|
||||
if optz == nil || optz.Account == _EMPTY_ {
|
||||
a.Accounts = []string{}
|
||||
s.accounts.Range(func(key, value interface{}) bool {
|
||||
s.accounts.Range(func(key, value any) bool {
|
||||
a.Accounts = append(a.Accounts, key.(string))
|
||||
return true
|
||||
})
|
||||
@@ -2827,8 +2827,8 @@ func (s *Server) accountDetail(jsa *jsAccount, optStreams, optConsumers, optCfg,
|
||||
detail.JetStreamStats.ReservedMemory = uint64(reserved.MaxMemory)
|
||||
detail.JetStreamStats.ReservedStore = uint64(reserved.MaxStore)
|
||||
}
|
||||
|
||||
jsa.usageMu.RUnlock()
|
||||
|
||||
var streams []*stream
|
||||
if optStreams {
|
||||
for _, stream := range jsa.streams {
|
||||
@@ -2934,7 +2934,7 @@ func (s *Server) Jsz(opts *JSzOptions) (*JSInfo, error) {
|
||||
if opts.Consumer {
|
||||
opts.Streams = true
|
||||
}
|
||||
if opts.Streams {
|
||||
if opts.Streams && opts.Account == _EMPTY_ {
|
||||
opts.Accounts = true
|
||||
}
|
||||
|
||||
|
||||
+17
-16
@@ -766,7 +766,7 @@ func (c *client) mqttParse(buf []byte) error {
|
||||
// PUBREC, PUBCOMP.
|
||||
case mqttPacketPubAck:
|
||||
var pi uint16
|
||||
pi, err = mqttParsePIPacket(r, pl)
|
||||
pi, err = mqttParsePIPacket(r)
|
||||
if trace {
|
||||
c.traceInOp("PUBACK", errOrTrace(err, fmt.Sprintf("pi=%v", pi)))
|
||||
}
|
||||
@@ -776,7 +776,7 @@ func (c *client) mqttParse(buf []byte) error {
|
||||
|
||||
case mqttPacketPubRec:
|
||||
var pi uint16
|
||||
pi, err = mqttParsePIPacket(r, pl)
|
||||
pi, err = mqttParsePIPacket(r)
|
||||
if trace {
|
||||
c.traceInOp("PUBREC", errOrTrace(err, fmt.Sprintf("pi=%v", pi)))
|
||||
}
|
||||
@@ -786,7 +786,7 @@ func (c *client) mqttParse(buf []byte) error {
|
||||
|
||||
case mqttPacketPubComp:
|
||||
var pi uint16
|
||||
pi, err = mqttParsePIPacket(r, pl)
|
||||
pi, err = mqttParsePIPacket(r)
|
||||
if trace {
|
||||
c.traceInOp("PUBCOMP", errOrTrace(err, fmt.Sprintf("pi=%v", pi)))
|
||||
}
|
||||
@@ -811,7 +811,7 @@ func (c *client) mqttParse(buf []byte) error {
|
||||
|
||||
case mqttPacketPubRel:
|
||||
var pi uint16
|
||||
pi, err = mqttParsePIPacket(r, pl)
|
||||
pi, err = mqttParsePIPacket(r)
|
||||
if trace {
|
||||
c.traceInOp("PUBREL", errOrTrace(err, fmt.Sprintf("pi=%v", pi)))
|
||||
}
|
||||
@@ -874,7 +874,7 @@ func (c *client) mqttParse(buf []byte) error {
|
||||
var rc byte
|
||||
var cp *mqttConnectProto
|
||||
var sessp bool
|
||||
rc, cp, err = c.mqttParseConnect(r, pl, hasMappings)
|
||||
rc, cp, err = c.mqttParseConnect(r, hasMappings)
|
||||
// Add the client id to the client's string, regardless of error.
|
||||
// We may still get the client_id if the call above fails somewhere
|
||||
// after parsing the client ID itself.
|
||||
@@ -991,7 +991,7 @@ func (s *Server) mqttHandleClosedClient(c *client) {
|
||||
// No lock held on entry.
|
||||
func (s *Server) mqttUpdateMaxAckPending(newmaxp uint16) {
|
||||
msm := &s.mqtt.sessmgr
|
||||
s.accounts.Range(func(k, _ interface{}) bool {
|
||||
s.accounts.Range(func(k, _ any) bool {
|
||||
accName := k.(string)
|
||||
msm.mu.RLock()
|
||||
asm := msm.sessions[accName]
|
||||
@@ -1075,7 +1075,7 @@ func mqttParsePubRelNATSHeader(headerBytes []byte) uint16 {
|
||||
// Returns the MQTT sessions manager for a given account.
|
||||
// If new, creates the required JetStream streams/consumers
|
||||
// for handling of sessions and messages.
|
||||
func (s *Server) getOrCreateMQTTAccountSessionManager(clientID string, c *client) (*mqttAccountSessionManager, error) {
|
||||
func (s *Server) getOrCreateMQTTAccountSessionManager(c *client) (*mqttAccountSessionManager, error) {
|
||||
sm := &s.mqtt.sessmgr
|
||||
|
||||
c.mu.Lock()
|
||||
@@ -1518,7 +1518,7 @@ func (s *Server) mqttDetermineReplicas() int {
|
||||
//
|
||||
//////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
func (jsa *mqttJSA) newRequest(kind, subject string, hdr int, msg []byte) (interface{}, error) {
|
||||
func (jsa *mqttJSA) newRequest(kind, subject string, hdr int, msg []byte) (any, error) {
|
||||
return jsa.newRequestEx(kind, subject, _EMPTY_, hdr, msg, mqttJSAPITimeout)
|
||||
}
|
||||
|
||||
@@ -2112,7 +2112,7 @@ func (as *mqttAccountSessionManager) cleanupRetainedMessageCache(s *Server, clos
|
||||
// should eventually clean up everything.
|
||||
i, maxScan := 0, 10*1000
|
||||
now := time.Now()
|
||||
as.rmsCache.Range(func(key, value interface{}) bool {
|
||||
as.rmsCache.Range(func(key, value any) bool {
|
||||
rm := value.(*mqttRetainedMsg)
|
||||
if now.After(rm.expiresFromCache) {
|
||||
as.rmsCache.Delete(key)
|
||||
@@ -2374,14 +2374,13 @@ func (sess *mqttSession) processQOS12Sub(
|
||||
c *client, // subscribing client.
|
||||
subject, sid []byte, isReserved bool, qos byte, jsDurName string, h msgHandler, // subscription parameters.
|
||||
) (*subscription, error) {
|
||||
return sess.processSub(c, subject, sid, isReserved, qos, jsDurName, h, false, false, nil, false, nil)
|
||||
return sess.processSub(c, subject, sid, isReserved, qos, jsDurName, h, false, nil, false, nil)
|
||||
}
|
||||
|
||||
func (sess *mqttSession) processSub(
|
||||
c *client, // subscribing client.
|
||||
subject, sid []byte, isReserved bool, qos byte, jsDurName string, h msgHandler, // subscription parameters.
|
||||
initShadow bool, // do we need to scan for shadow subscriptions? (not for QOS1+)
|
||||
serializeRMS bool, // do we need to serialize RMS?
|
||||
rms map[string]*mqttRetainedMsg, // preloaded rms (can be empty, or missing items if errors)
|
||||
trace bool, // trace serialized retained messages in the log?
|
||||
as *mqttAccountSessionManager, // needed only for rms serialization.
|
||||
@@ -2578,7 +2577,7 @@ func (as *mqttAccountSessionManager) processSubs(sess *mqttSession, c *client,
|
||||
bsubject, bsid, isReserved, f.qos, // main subject
|
||||
_EMPTY_, mqttDeliverMsgCbQoS0, // no jsDur for QOS0
|
||||
processShadowSubs,
|
||||
serializeRMS, rms, trace, as)
|
||||
rms, trace, as)
|
||||
sess.mu.Unlock()
|
||||
as.mu.Unlock()
|
||||
|
||||
@@ -2612,7 +2611,7 @@ func (as *mqttAccountSessionManager) processSubs(sess *mqttSession, c *client,
|
||||
[]byte(fwcsubject), []byte(fwcsid), isReserved, f.qos, // FWC (top-level wildcard) subject
|
||||
_EMPTY_, mqttDeliverMsgCbQoS0, // no jsDur for QOS0
|
||||
processShadowSubs,
|
||||
serializeRMS, rms, trace, as)
|
||||
rms, trace, as)
|
||||
sess.mu.Unlock()
|
||||
as.mu.Unlock()
|
||||
if err != nil {
|
||||
@@ -3390,7 +3389,7 @@ func (sess *mqttSession) deleteConsumer(cc *ConsumerConfig) {
|
||||
//////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
// Parse the MQTT connect protocol
|
||||
func (c *client) mqttParseConnect(r *mqttReader, pl int, hasMappings bool) (byte, *mqttConnectProto, error) {
|
||||
func (c *client) mqttParseConnect(r *mqttReader, hasMappings bool) (byte, *mqttConnectProto, error) {
|
||||
// Protocol name
|
||||
proto, err := r.readBytes("protocol name", false)
|
||||
if err != nil {
|
||||
@@ -3629,7 +3628,7 @@ func (s *Server) mqttProcessConnect(c *client, cp *mqttConnectProto, trace bool)
|
||||
// Get the account's level MQTT sessions manager. If it does not exists yet,
|
||||
// this will create it along with the streams where sessions and messages
|
||||
// are stored.
|
||||
asm, err := s.getOrCreateMQTTAccountSessionManager(cid, c)
|
||||
asm, err := s.getOrCreateMQTTAccountSessionManager(c)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -4380,7 +4379,7 @@ func (c *client) mqttEnqueuePubResponse(packetType byte, pi uint16, trace bool)
|
||||
}
|
||||
}
|
||||
|
||||
func mqttParsePIPacket(r *mqttReader, pl int) (uint16, error) {
|
||||
func mqttParsePIPacket(r *mqttReader) (uint16, error) {
|
||||
pi, err := r.readUint16("packet identifier")
|
||||
if err != nil {
|
||||
return 0, err
|
||||
@@ -4984,7 +4983,9 @@ func (sess *mqttSession) processJSConsumer(c *client, subject, sid string,
|
||||
// The JS durable consumer's delivery subject is on a NUID of
|
||||
// the form: mqttSubPrefix + <nuid>. It is also used as the sid
|
||||
// for the NATS subscription, so use that for the lookup.
|
||||
c.mu.Lock()
|
||||
sub := c.subs[cc.DeliverSubject]
|
||||
c.mu.Unlock()
|
||||
|
||||
sess.mu.Lock()
|
||||
delete(sess.cons, sid)
|
||||
|
||||
+19
-13
@@ -450,7 +450,7 @@ func (srv *Server) NewOCSPMonitor(config *tlsConfigKind) (*tls.Config, *OCSPMoni
|
||||
// Get the certificate status from the memory, then remote OCSP responder.
|
||||
if _, resp, err := mon.getStatus(); err != nil {
|
||||
return nil, nil, fmt.Errorf("bad OCSP status update for certificate at '%s': %s", certFile, err)
|
||||
} else if err == nil && resp != nil && resp.Status != ocsp.Good && shutdownOnRevoke {
|
||||
} else if resp != nil && resp.Status != ocsp.Good && shutdownOnRevoke {
|
||||
return nil, nil, fmt.Errorf("found existing OCSP status for certificate at '%s': %s", certFile, ocspStatusString(resp.Status))
|
||||
}
|
||||
|
||||
@@ -460,18 +460,18 @@ func (srv *Server) NewOCSPMonitor(config *tlsConfigKind) (*tls.Config, *OCSPMoni
|
||||
|
||||
// GetCertificate returns a certificate that's presented to a client.
|
||||
tc.GetCertificate = func(info *tls.ClientHelloInfo) (*tls.Certificate, error) {
|
||||
ccert := cert
|
||||
raw, _, err := mon.getStatus()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &tls.Certificate{
|
||||
OCSPStaple: raw,
|
||||
Certificate: cert.Certificate,
|
||||
PrivateKey: cert.PrivateKey,
|
||||
SupportedSignatureAlgorithms: cert.SupportedSignatureAlgorithms,
|
||||
SignedCertificateTimestamps: cert.SignedCertificateTimestamps,
|
||||
Leaf: cert.Leaf,
|
||||
Certificate: ccert.Certificate,
|
||||
PrivateKey: ccert.PrivateKey,
|
||||
SupportedSignatureAlgorithms: ccert.SupportedSignatureAlgorithms,
|
||||
SignedCertificateTimestamps: ccert.SignedCertificateTimestamps,
|
||||
Leaf: ccert.Leaf,
|
||||
}, nil
|
||||
}
|
||||
|
||||
@@ -532,13 +532,20 @@ func (srv *Server) NewOCSPMonitor(config *tlsConfigKind) (*tls.Config, *OCSPMoni
|
||||
|
||||
// When server makes a peer connection, need to also present an OCSP Staple.
|
||||
tc.GetClientCertificate = func(info *tls.CertificateRequestInfo) (*tls.Certificate, error) {
|
||||
ccert := cert
|
||||
raw, _, err := mon.getStatus()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
cert.OCSPStaple = raw
|
||||
// NOTE: crypto/tls.sendClientCertificate internally also calls getClientCertificate
|
||||
// so if for some reason these callbacks are triggered concurrently during a reconnect
|
||||
// there can be a race. To avoid that, the OCSP monitor lock is used to serialize access
|
||||
// to the staple which could also change inflight during an update.
|
||||
mon.mu.Lock()
|
||||
ccert.OCSPStaple = raw
|
||||
mon.mu.Unlock()
|
||||
|
||||
return &cert, nil
|
||||
return &ccert, nil
|
||||
}
|
||||
default:
|
||||
// GetClientCertificate returns a certificate that's presented to a server.
|
||||
@@ -546,7 +553,6 @@ func (srv *Server) NewOCSPMonitor(config *tlsConfigKind) (*tls.Config, *OCSPMoni
|
||||
return &cert, nil
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
return tc, mon, nil
|
||||
}
|
||||
@@ -761,8 +767,8 @@ func (s *Server) reloadOCSP() error {
|
||||
if mon != nil {
|
||||
ocspm = append(ocspm, mon)
|
||||
|
||||
// Apply latest TLS configuration.
|
||||
config.apply(tc)
|
||||
// Apply latest TLS configuration after OCSP monitors have started.
|
||||
defer config.apply(tc)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -774,7 +780,7 @@ func (s *Server) reloadOCSP() error {
|
||||
}
|
||||
if plugged && tc != nil {
|
||||
s.ocspPeerVerify = true
|
||||
config.apply(tc)
|
||||
defer config.apply(tc)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+2
-2
@@ -26,11 +26,11 @@ import (
|
||||
"github.com/nats-io/nats-server/v2/server/certidp"
|
||||
)
|
||||
|
||||
func parseOCSPPeer(v interface{}) (pcfg *certidp.OCSPPeerConfig, retError error) {
|
||||
func parseOCSPPeer(v any) (pcfg *certidp.OCSPPeerConfig, retError error) {
|
||||
var lt token
|
||||
defer convertPanicToError(<, &retError)
|
||||
tk, v := unwrapValue(v, <)
|
||||
cm, ok := v.(map[string]interface{})
|
||||
cm, ok := v.(map[string]any)
|
||||
if !ok {
|
||||
return nil, &configErr{tk, fmt.Sprintf(certidp.ErrIllegalPeerOptsConfig, v)}
|
||||
}
|
||||
|
||||
+2
-2
@@ -571,11 +571,11 @@ func (s *Server) stopOCSPResponseCache() {
|
||||
s.ocsprc.Stop(s)
|
||||
}
|
||||
|
||||
func parseOCSPResponseCache(v interface{}) (pcfg *OCSPResponseCacheConfig, retError error) {
|
||||
func parseOCSPResponseCache(v any) (pcfg *OCSPResponseCacheConfig, retError error) {
|
||||
var lt token
|
||||
defer convertPanicToError(<, &retError)
|
||||
tk, v := unwrapValue(v, <)
|
||||
cm, ok := v.(map[string]interface{})
|
||||
cm, ok := v.(map[string]any)
|
||||
if !ok {
|
||||
return nil, &configErr{tk, fmt.Sprintf(certidp.ErrIllegalCacheOptsConfig, v)}
|
||||
}
|
||||
|
||||
+179
-179
File diff suppressed because it is too large
Load Diff
+25
-29
@@ -1635,12 +1635,6 @@ func (n *raft) shutdown(shouldDelete bool) {
|
||||
}
|
||||
s, g, wal := n.s, n.group, n.wal
|
||||
|
||||
// Delete our peer state and vote state and any snapshots.
|
||||
if shouldDelete {
|
||||
os.Remove(filepath.Join(n.sd, peerStateFile))
|
||||
os.Remove(filepath.Join(n.sd, termVoteFile))
|
||||
os.RemoveAll(filepath.Join(n.sd, snapshotsDir))
|
||||
}
|
||||
// Unregistering ipQueues do not prevent them from push/pop
|
||||
// just will remove them from the central monitoring map
|
||||
queues := []interface {
|
||||
@@ -1652,11 +1646,7 @@ func (n *raft) shutdown(shouldDelete bool) {
|
||||
n.Unlock()
|
||||
|
||||
s.unregisterRaftNode(g)
|
||||
if shouldDelete {
|
||||
n.debug("Deleted")
|
||||
} else {
|
||||
n.debug("Shutdown")
|
||||
}
|
||||
|
||||
if wal != nil {
|
||||
if shouldDelete {
|
||||
wal.Delete()
|
||||
@@ -1664,6 +1654,14 @@ func (n *raft) shutdown(shouldDelete bool) {
|
||||
wal.Stop()
|
||||
}
|
||||
}
|
||||
|
||||
if shouldDelete {
|
||||
// Delete all our peer state and vote state and any snapshots.
|
||||
os.RemoveAll(n.sd)
|
||||
n.debug("Deleted")
|
||||
} else {
|
||||
n.debug("Shutdown")
|
||||
}
|
||||
}
|
||||
|
||||
// Wipe will force an on disk state reset and then call Delete().
|
||||
@@ -1840,19 +1838,19 @@ func (n *raft) run() {
|
||||
}
|
||||
}
|
||||
|
||||
func (n *raft) debug(format string, args ...interface{}) {
|
||||
func (n *raft) debug(format string, args ...any) {
|
||||
if n.dflag {
|
||||
nf := fmt.Sprintf("RAFT [%s - %s] %s", n.id, n.group, format)
|
||||
n.s.Debugf(nf, args...)
|
||||
}
|
||||
}
|
||||
|
||||
func (n *raft) warn(format string, args ...interface{}) {
|
||||
func (n *raft) warn(format string, args ...any) {
|
||||
nf := fmt.Sprintf("RAFT [%s - %s] %s", n.id, n.group, format)
|
||||
n.s.RateLimitWarnf(nf, args...)
|
||||
}
|
||||
|
||||
func (n *raft) error(format string, args ...interface{}) {
|
||||
func (n *raft) error(format string, args ...any) {
|
||||
nf := fmt.Sprintf("RAFT [%s - %s] %s", n.id, n.group, format)
|
||||
n.s.Errorf(nf, args...)
|
||||
}
|
||||
@@ -3761,22 +3759,20 @@ func (n *raft) setWriteErrLocked(err error) {
|
||||
return
|
||||
}
|
||||
// Ignore non-write errors.
|
||||
if err != nil {
|
||||
if err == ErrStoreClosed ||
|
||||
err == ErrStoreEOF ||
|
||||
err == ErrInvalidSequence ||
|
||||
err == ErrStoreMsgNotFound ||
|
||||
err == errNoPending ||
|
||||
err == errPartialCache {
|
||||
return
|
||||
}
|
||||
// If this is a not found report but do not disable.
|
||||
if os.IsNotExist(err) {
|
||||
n.error("Resource not found: %v", err)
|
||||
return
|
||||
}
|
||||
n.error("Critical write error: %v", err)
|
||||
if err == ErrStoreClosed ||
|
||||
err == ErrStoreEOF ||
|
||||
err == ErrInvalidSequence ||
|
||||
err == ErrStoreMsgNotFound ||
|
||||
err == errNoPending ||
|
||||
err == errPartialCache {
|
||||
return
|
||||
}
|
||||
// If this is a not found report but do not disable.
|
||||
if os.IsNotExist(err) {
|
||||
n.error("Resource not found: %v", err)
|
||||
return
|
||||
}
|
||||
n.error("Critical write error: %v", err)
|
||||
n.werr = err
|
||||
|
||||
if isOutOfSpaceErr(err) {
|
||||
|
||||
+6
-6
@@ -1126,7 +1126,7 @@ func (s *Server) reloadOptions(curOpts, newOpts *Options) error {
|
||||
}
|
||||
|
||||
// For the purpose of comparing, impose a order on slice data types where order does not matter
|
||||
func imposeOrder(value interface{}) error {
|
||||
func imposeOrder(value any) error {
|
||||
switch value := value.(type) {
|
||||
case []*Account:
|
||||
sort.Slice(value, func(i, j int) bool {
|
||||
@@ -1876,7 +1876,7 @@ func (s *Server) reloadAuthorization() {
|
||||
}
|
||||
// Now range over existing accounts and keep track of the ones deleted
|
||||
// so some cleanup can be made after releasing the server lock.
|
||||
s.accounts.Range(func(k, v interface{}) bool {
|
||||
s.accounts.Range(func(k, v any) bool {
|
||||
an, acc := k.(string), v.(*Account)
|
||||
// Exclude default and system account from this test since those
|
||||
// may not actually be in opts.Accounts.
|
||||
@@ -1903,7 +1903,7 @@ func (s *Server) reloadAuthorization() {
|
||||
// With a memory resolver we want to do something similar to configured accounts.
|
||||
// We will walk the accounts and delete them if they are no longer present via fetch.
|
||||
// If they are present we will force a claim update to process changes.
|
||||
s.accounts.Range(func(k, v interface{}) bool {
|
||||
s.accounts.Range(func(k, v any) bool {
|
||||
acc := v.(*Account)
|
||||
// Skip global account.
|
||||
if acc == s.gacc {
|
||||
@@ -1955,7 +1955,7 @@ func (s *Server) reloadAuthorization() {
|
||||
s.accounts.Store(s.sys.account.Name, s.sys.account)
|
||||
}
|
||||
|
||||
s.accounts.Range(func(k, v interface{}) bool {
|
||||
s.accounts.Range(func(k, v any) bool {
|
||||
acc := v.(*Account)
|
||||
acc.mu.RLock()
|
||||
// Check for sysclients accounting, ignore the system account.
|
||||
@@ -2151,7 +2151,7 @@ func (s *Server) reloadClusterPermissions(oldPerms *RoutePermissions) {
|
||||
// Then, go over all accounts and gather local subscriptions that need to be
|
||||
// sent over as SUB or removed as UNSUB, and routed subscriptions that need
|
||||
// to be dropped due to export permissions.
|
||||
s.accounts.Range(func(_, v interface{}) bool {
|
||||
s.accounts.Range(func(_, v any) bool {
|
||||
acc := v.(*Account)
|
||||
acc.mu.RLock()
|
||||
accName, sl, poolIdx := acc.Name, acc.sl, acc.routePoolIdx
|
||||
@@ -2366,7 +2366,7 @@ func (s *Server) reloadClusterPoolAndAccounts(co *clusterOption, opts *Options)
|
||||
// pool index. Note that the added/removed accounts will be reset there
|
||||
// too, but that's ok (we could use a map to exclude them, but not worth it).
|
||||
if co.poolSizeChanged {
|
||||
s.accounts.Range(func(_, v interface{}) bool {
|
||||
s.accounts.Range(func(_, v any) bool {
|
||||
acc := v.(*Account)
|
||||
acc.mu.Lock()
|
||||
s.setRouteInfo(acc)
|
||||
|
||||
+5
-5
@@ -447,7 +447,7 @@ func (c *client) processRoutedMsgArgs(arg []byte) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// processInboundRouteMsg is called to process an inbound msg from a route.
|
||||
// processInboundRoutedMsg is called to process an inbound msg from a route.
|
||||
func (c *client) processInboundRoutedMsg(msg []byte) {
|
||||
// Update statistics
|
||||
c.in.msgs++
|
||||
@@ -644,7 +644,7 @@ func (c *client) processRouteInfo(info *Info) {
|
||||
info.Gateway, remoteID)
|
||||
return
|
||||
}
|
||||
s.processGatewayInfoFromRoute(info, remoteID, c)
|
||||
s.processGatewayInfoFromRoute(info, remoteID)
|
||||
return
|
||||
}
|
||||
|
||||
@@ -942,7 +942,7 @@ func (s *Server) updateRemoteRoutePerms(c *client, info *Info) {
|
||||
allSubs = _allSubs[:0]
|
||||
)
|
||||
|
||||
s.accounts.Range(func(_, v interface{}) bool {
|
||||
s.accounts.Range(func(_, v any) bool {
|
||||
acc := v.(*Account)
|
||||
acc.mu.RLock()
|
||||
accName, sl, accPoolIdx := acc.Name, acc.sl, acc.routePoolIdx
|
||||
@@ -1177,7 +1177,7 @@ func (c *client) removeRemoteSubs() {
|
||||
c.mu.Lock()
|
||||
srv := c.srv
|
||||
subs := c.subs
|
||||
c.subs = make(map[string]*subscription)
|
||||
c.subs = nil
|
||||
c.mu.Unlock()
|
||||
|
||||
for key, sub := range subs {
|
||||
@@ -1591,7 +1591,7 @@ func (s *Server) sendSubsToRoute(route *client, idx int, account string) {
|
||||
a.mu.RUnlock()
|
||||
}
|
||||
} else {
|
||||
s.accounts.Range(func(k, v interface{}) bool {
|
||||
s.accounts.Range(func(k, v any) bool {
|
||||
a := v.(*Account)
|
||||
a.mu.RLock()
|
||||
// We are here for regular or pooled routes (not per-account).
|
||||
|
||||
+10
-8
@@ -1187,7 +1187,7 @@ func (s *Server) configureAccounts(reloading bool) (map[string]struct{}, error)
|
||||
}
|
||||
}
|
||||
var numAccounts int
|
||||
s.accounts.Range(func(k, v interface{}) bool {
|
||||
s.accounts.Range(func(k, v any) bool {
|
||||
numAccounts++
|
||||
acc := v.(*Account)
|
||||
acc.mu.Lock()
|
||||
@@ -1376,7 +1376,7 @@ func (s *Server) globalAccountOnly() bool {
|
||||
}
|
||||
|
||||
s.mu.RLock()
|
||||
s.accounts.Range(func(k, v interface{}) bool {
|
||||
s.accounts.Range(func(k, v any) bool {
|
||||
acc := v.(*Account)
|
||||
// Ignore global and system
|
||||
if acc == s.gacc || (s.sys != nil && acc == s.sys.account) {
|
||||
@@ -1402,7 +1402,7 @@ func (s *Server) configuredRoutes() int {
|
||||
|
||||
// activePeers is used in bootstrapping raft groups like the JetStream meta controller.
|
||||
func (s *Server) ActivePeers() (peers []string) {
|
||||
s.nodeToInfo.Range(func(k, v interface{}) bool {
|
||||
s.nodeToInfo.Range(func(k, v any) bool {
|
||||
si := v.(nodeInfo)
|
||||
if !si.offline {
|
||||
peers = append(peers, k.(string))
|
||||
@@ -1559,7 +1559,7 @@ func (s *Server) decActiveAccounts() {
|
||||
func (s *Server) numAccounts() int {
|
||||
count := 0
|
||||
s.mu.RLock()
|
||||
s.accounts.Range(func(k, v interface{}) bool {
|
||||
s.accounts.Range(func(k, v any) bool {
|
||||
count++
|
||||
return true
|
||||
})
|
||||
@@ -1722,7 +1722,7 @@ func (s *Server) setSystemAccount(acc *Account) error {
|
||||
|
||||
// If we have existing accounts make sure we enable account tracking.
|
||||
s.mu.Lock()
|
||||
s.accounts.Range(func(k, v interface{}) bool {
|
||||
s.accounts.Range(func(k, v any) bool {
|
||||
acc := v.(*Account)
|
||||
s.enableAccountTracking(acc)
|
||||
return true
|
||||
@@ -2279,7 +2279,7 @@ func (s *Server) Start() {
|
||||
var hasSys, hasGlobal bool
|
||||
var total int
|
||||
|
||||
s.accounts.Range(func(k, v interface{}) bool {
|
||||
s.accounts.Range(func(k, v any) bool {
|
||||
total++
|
||||
acc := v.(*Account)
|
||||
if acc == sa {
|
||||
@@ -3288,6 +3288,8 @@ func (s *Server) saveClosedClient(c *client, nc net.Conn, reason ClosedState) {
|
||||
for _, sub := range c.subs {
|
||||
cc.subs = append(cc.subs, newSubDetail(sub))
|
||||
}
|
||||
// Now set this to nil to allow connection to be released.
|
||||
c.subs = nil
|
||||
}
|
||||
// Hold user as well.
|
||||
cc.user = c.getRawAuthUser()
|
||||
@@ -3533,7 +3535,7 @@ func (s *Server) NumSubscriptions() uint32 {
|
||||
// Lock should be held.
|
||||
func (s *Server) numSubscriptions() uint32 {
|
||||
var subs int
|
||||
s.accounts.Range(func(k, v interface{}) bool {
|
||||
s.accounts.Range(func(k, v any) bool {
|
||||
acc := v.(*Account)
|
||||
subs += acc.TotalSubs()
|
||||
return true
|
||||
@@ -4370,7 +4372,7 @@ func (s *Server) startRateLimitLogExpiration() {
|
||||
case interval = <-s.rateLimitLoggingCh:
|
||||
ticker.Reset(interval)
|
||||
case <-ticker.C:
|
||||
s.rateLimitLogging.Range(func(k, v interface{}) bool {
|
||||
s.rateLimitLogging.Range(func(k, v any) bool {
|
||||
start := v.(time.Time)
|
||||
if time.Since(start) >= interval {
|
||||
s.rateLimitLogging.Delete(k)
|
||||
|
||||
+1
@@ -88,6 +88,7 @@ type StreamStore interface {
|
||||
SkipMsgs(seq uint64, num uint64) error
|
||||
LoadMsg(seq uint64, sm *StoreMsg) (*StoreMsg, error)
|
||||
LoadNextMsg(filter string, wc bool, start uint64, smp *StoreMsg) (sm *StoreMsg, skip uint64, err error)
|
||||
LoadNextMsgMulti(sl *Sublist, start uint64, smp *StoreMsg) (sm *StoreMsg, skip uint64, err error)
|
||||
LoadLastMsg(subject string, sm *StoreMsg) (*StoreMsg, error)
|
||||
RemoveMsg(seq uint64) (bool, error)
|
||||
EraseMsg(seq uint64) (bool, error)
|
||||
|
||||
+141
-45
@@ -854,6 +854,11 @@ func (mset *stream) setLeader(isLeader bool) error {
|
||||
mset.leader = _EMPTY_
|
||||
}
|
||||
mset.mu.Unlock()
|
||||
|
||||
// If we are interest based make sure to check consumers.
|
||||
// This is to make sure we process any outstanding acks.
|
||||
mset.checkInterestState()
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -1282,7 +1287,7 @@ func (s *Server) checkStreamCfg(config *StreamConfig, acc *Account) (StreamConfi
|
||||
return StreamConfig{}, NewJSMirrorInvalidSubjectFilterError()
|
||||
}
|
||||
for inner, innertr := range cfg.Mirror.SubjectTransforms {
|
||||
if inner != outer && subjectIsSubsetMatch(tr.Source, innertr.Source) {
|
||||
if inner != outer && SubjectsCollide(tr.Source, innertr.Source) {
|
||||
return StreamConfig{}, NewJSMirrorOverlappingSubjectFiltersError()
|
||||
}
|
||||
}
|
||||
@@ -2217,7 +2222,7 @@ func (mset *stream) processInboundMirrorMsg(m *inMsg) bool {
|
||||
var needsRetry bool
|
||||
// Flow controls have reply subjects.
|
||||
if m.rply != _EMPTY_ {
|
||||
mset.handleFlowControl(mset.mirror, m)
|
||||
mset.handleFlowControl(m)
|
||||
} else {
|
||||
// For idle heartbeats make sure we did not miss anything and check if we are considered stalled.
|
||||
if ldseq := parseInt64(getHeader(JSLastConsumerSeq, m.hdr)); ldseq > 0 && uint64(ldseq) != mset.mirror.dseq {
|
||||
@@ -3143,7 +3148,7 @@ func (mset *stream) sendFlowControlReply(reply string) {
|
||||
|
||||
// handleFlowControl will properly handle flow control messages for both R==1 and R>1.
|
||||
// Lock should be held.
|
||||
func (mset *stream) handleFlowControl(si *sourceInfo, m *inMsg) {
|
||||
func (mset *stream) handleFlowControl(m *inMsg) {
|
||||
// If we are clustered we will send the flow control message through the replication stack.
|
||||
if mset.isClustered() {
|
||||
mset.node.Propose(encodeStreamMsg(_EMPTY_, m.rply, m.hdr, nil, 0, 0))
|
||||
@@ -3179,7 +3184,7 @@ func (mset *stream) processInboundSourceMsg(si *sourceInfo, m *inMsg) bool {
|
||||
var needsRetry bool
|
||||
// Flow controls have reply subjects.
|
||||
if m.rply != _EMPTY_ {
|
||||
mset.handleFlowControl(si, m)
|
||||
mset.handleFlowControl(m)
|
||||
} else {
|
||||
// For idle heartbeats make sure we did not miss anything.
|
||||
if ldseq := parseInt64(getHeader(JSLastConsumerSeq, m.hdr)); ldseq > 0 && uint64(ldseq) != si.dseq {
|
||||
@@ -3231,6 +3236,9 @@ func (mset *stream) processInboundSourceMsg(si *sourceInfo, m *inMsg) bool {
|
||||
// If we are daisy chained here make sure to remove the original one.
|
||||
if len(hdr) > 0 {
|
||||
hdr = removeHeaderIfPresent(hdr, JSStreamSource)
|
||||
|
||||
// Remove any Nats-Expected- headers as we don't want to validate them.
|
||||
hdr = removeHeaderIfPrefixPresent(hdr, "Nats-Expected-")
|
||||
}
|
||||
// Hold onto the origin reply which has all the metadata.
|
||||
hdr = genHeader(hdr, JSStreamSource, si.genSourceHeader(m.rply))
|
||||
@@ -3999,7 +4007,7 @@ func (mset *stream) queueInboundMsg(subj, rply string, hdr, msg []byte) {
|
||||
}
|
||||
|
||||
var dgPool = sync.Pool{
|
||||
New: func() interface{} {
|
||||
New: func() any {
|
||||
return &directGetReq{}
|
||||
},
|
||||
}
|
||||
@@ -4151,6 +4159,7 @@ var (
|
||||
errMsgIdDuplicate = errors.New("msgid is duplicate")
|
||||
errStreamClosed = errors.New("stream closed")
|
||||
errInvalidMsgHandler = errors.New("undefined message handler")
|
||||
errStreamMismatch = errors.New("expected stream does not match")
|
||||
)
|
||||
|
||||
// processJetStreamMsg is where we try to actually process the stream msg.
|
||||
@@ -4269,23 +4278,28 @@ func (mset *stream) processJetStreamMsg(subject, reply string, hdr, msg []byte,
|
||||
b, _ := json.Marshal(resp)
|
||||
outq.sendMsg(reply, b)
|
||||
}
|
||||
return errors.New("expected stream does not match")
|
||||
return errStreamMismatch
|
||||
}
|
||||
}
|
||||
|
||||
// Dedupe detection.
|
||||
// Dedupe detection. This is done at the cluster level for dedupe detectiom above the
|
||||
// lower layers. But we still need to pull out the msgId.
|
||||
if msgId = getMsgId(hdr); msgId != _EMPTY_ {
|
||||
if dde := mset.checkMsgId(msgId); dde != nil {
|
||||
mset.mu.Unlock()
|
||||
bumpCLFS()
|
||||
if canRespond {
|
||||
response := append(pubAck, strconv.FormatUint(dde.seq, 10)...)
|
||||
response = append(response, ",\"duplicate\": true}"...)
|
||||
outq.sendMsg(reply, response)
|
||||
// Do real check only if not clustered or traceOnly flag is set.
|
||||
if !isClustered {
|
||||
if dde := mset.checkMsgId(msgId); dde != nil {
|
||||
mset.mu.Unlock()
|
||||
bumpCLFS()
|
||||
if canRespond {
|
||||
response := append(pubAck, strconv.FormatUint(dde.seq, 10)...)
|
||||
response = append(response, ",\"duplicate\": true}"...)
|
||||
outq.sendMsg(reply, response)
|
||||
}
|
||||
return errMsgIdDuplicate
|
||||
}
|
||||
return errMsgIdDuplicate
|
||||
}
|
||||
}
|
||||
|
||||
// Expected last sequence per subject.
|
||||
// If we are clustered we have prechecked seq > 0.
|
||||
if seq, exists := getExpectedLastSeqPerSubject(hdr); exists {
|
||||
@@ -4547,8 +4561,18 @@ func (mset *stream) processJetStreamMsg(subject, reply string, hdr, msg []byte,
|
||||
}
|
||||
|
||||
// If we have a msgId make sure to save.
|
||||
// This will replace our estimate from the cluster layer if we are clustered.
|
||||
if msgId != _EMPTY_ {
|
||||
mset.storeMsgIdLocked(&ddentry{msgId, seq, ts})
|
||||
if isClustered && isLeader && mset.ddmap != nil {
|
||||
if dde := mset.ddmap[msgId]; dde != nil {
|
||||
dde.seq, dde.ts = seq, ts
|
||||
} else {
|
||||
mset.storeMsgIdLocked(&ddentry{msgId, seq, ts})
|
||||
}
|
||||
} else {
|
||||
// R1 or not leader..
|
||||
mset.storeMsgIdLocked(&ddentry{msgId, seq, ts})
|
||||
}
|
||||
}
|
||||
|
||||
// If here we succeeded in storing the message.
|
||||
@@ -4981,9 +5005,7 @@ func (mset *stream) stop(deleteFlag, advisory bool) error {
|
||||
accName := jsa.account.Name
|
||||
jsa.mu.Unlock()
|
||||
|
||||
// Mark as closed, kick monitor and collect consumers first.
|
||||
mset.closed.Store(true)
|
||||
|
||||
// Kick monitor and collect consumers first.
|
||||
mset.mu.Lock()
|
||||
// Signal to the monitor loop.
|
||||
// Can't use qch here.
|
||||
@@ -5045,6 +5067,9 @@ func (mset *stream) stop(deleteFlag, advisory bool) error {
|
||||
mset.sendDeleteAdvisoryLocked()
|
||||
}
|
||||
|
||||
// Mark closed.
|
||||
mset.closed.Store(true)
|
||||
|
||||
// Quit channel, do this after sending the delete advisory
|
||||
if mset.qch != nil {
|
||||
close(mset.qch)
|
||||
@@ -5176,25 +5201,95 @@ func (mset *stream) getPublicConsumers() []*consumer {
|
||||
}
|
||||
|
||||
// Will check for interest retention and make sure messages
|
||||
// that have been acked are processed.
|
||||
// that have been acked are processed and removed.
|
||||
// This will check the ack floors of all consumers, and adjust our first sequence accordingly.
|
||||
func (mset *stream) checkInterestState() {
|
||||
if mset == nil {
|
||||
if mset == nil || !mset.isInterestRetention() {
|
||||
// If we are limits based nothing to do.
|
||||
return
|
||||
}
|
||||
mset.mu.RLock()
|
||||
// If we are limits based nothing to do.
|
||||
if mset.cfg.Retention == LimitsPolicy {
|
||||
mset.mu.RUnlock()
|
||||
return
|
||||
}
|
||||
consumers := make([]*consumer, 0, len(mset.consumers))
|
||||
for _, o := range mset.consumers {
|
||||
consumers = append(consumers, o)
|
||||
}
|
||||
mset.mu.RUnlock()
|
||||
|
||||
var zeroAcks []*consumer
|
||||
var lowAckFloor uint64 = math.MaxUint64
|
||||
consumers := mset.getConsumers()
|
||||
|
||||
for _, o := range consumers {
|
||||
o.checkStateForInterestStream()
|
||||
|
||||
o.mu.Lock()
|
||||
if o.isLeader() {
|
||||
// We need to account for consumers with ack floor of zero.
|
||||
// We will collect them and see if we need to check pending below.
|
||||
if o.asflr == 0 {
|
||||
zeroAcks = append(zeroAcks, o)
|
||||
} else if o.asflr < lowAckFloor {
|
||||
lowAckFloor = o.asflr
|
||||
}
|
||||
} else {
|
||||
// We are a follower so only have the store state, so read that in.
|
||||
state, err := o.store.State()
|
||||
if err != nil {
|
||||
// On error we will not have enough information to process correctly so bail.
|
||||
o.mu.Unlock()
|
||||
return
|
||||
}
|
||||
// We need to account for consumers with ack floor of zero.
|
||||
if state.AckFloor.Stream == 0 {
|
||||
zeroAcks = append(zeroAcks, o)
|
||||
} else if state.AckFloor.Stream < lowAckFloor {
|
||||
lowAckFloor = state.AckFloor.Stream
|
||||
}
|
||||
// We are a follower here but if we detect a drift from when we were previous leader correct here.
|
||||
if o.asflr > state.AckFloor.Stream || o.sseq > state.Delivered.Stream+1 {
|
||||
o.applyState(state)
|
||||
}
|
||||
}
|
||||
o.mu.Unlock()
|
||||
}
|
||||
|
||||
// If nothing was set we can bail.
|
||||
if lowAckFloor == math.MaxUint64 {
|
||||
return
|
||||
}
|
||||
|
||||
// Hold stream write lock in case we need to purge.
|
||||
mset.mu.Lock()
|
||||
defer mset.mu.Unlock()
|
||||
|
||||
// Capture our current state.
|
||||
var state StreamState
|
||||
mset.store.FastState(&state)
|
||||
|
||||
if lowAckFloor < math.MaxUint64 && lowAckFloor > state.FirstSeq {
|
||||
// Check if we had any zeroAcks, we will need to check them.
|
||||
for _, o := range zeroAcks {
|
||||
var np uint64
|
||||
o.mu.RLock()
|
||||
if o.isLeader() {
|
||||
np = uint64(o.numPending())
|
||||
} else {
|
||||
np, _ = o.calculateNumPending()
|
||||
}
|
||||
o.mu.RUnlock()
|
||||
// This means we have pending and can not remove anything at this time.
|
||||
if np > 0 {
|
||||
return
|
||||
}
|
||||
}
|
||||
if lowAckFloor <= state.LastSeq {
|
||||
// Purge the stream to lowest ack floor + 1
|
||||
mset.store.PurgeEx(_EMPTY_, lowAckFloor+1, 0)
|
||||
} else {
|
||||
// Here we have a low ack floor higher then our last seq.
|
||||
// So we will just do normal purge.
|
||||
mset.store.Purge()
|
||||
}
|
||||
}
|
||||
// Make sure to reset our local lseq.
|
||||
mset.store.FastState(&state)
|
||||
mset.lseq = state.LastSeq
|
||||
// Also make sure we clear any pending acks.
|
||||
mset.clearAllPreAcksBelowFloor(state.FirstSeq)
|
||||
}
|
||||
|
||||
func (mset *stream) isInterestRetention() bool {
|
||||
@@ -5285,6 +5380,7 @@ func (mset *stream) swapSigSubs(o *consumer, newFilters []string) {
|
||||
|
||||
if o.closed || o.mset == nil {
|
||||
o.mu.Unlock()
|
||||
mset.clsMu.Unlock()
|
||||
return
|
||||
}
|
||||
|
||||
@@ -5384,8 +5480,6 @@ func (mset *stream) Store() StreamStore {
|
||||
// Lock should be held.
|
||||
func (mset *stream) partitionUnique(name string, partitions []string) bool {
|
||||
for _, partition := range partitions {
|
||||
psa := [32]string{}
|
||||
pts := tokenizeSubjectIntoSlice(psa[:0], partition)
|
||||
for n, o := range mset.consumers {
|
||||
// Skip the consumer being checked.
|
||||
if n == name {
|
||||
@@ -5395,8 +5489,7 @@ func (mset *stream) partitionUnique(name string, partitions []string) bool {
|
||||
return false
|
||||
}
|
||||
for _, filter := range o.subjf {
|
||||
if isSubsetMatchTokenized(pts, filter.tokenizedSubject) ||
|
||||
isSubsetMatchTokenized(filter.tokenizedSubject, pts) {
|
||||
if SubjectsCollide(partition, filter.subject) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
@@ -5559,15 +5652,9 @@ func (mset *stream) ackMsg(o *consumer, seq uint64) {
|
||||
return
|
||||
}
|
||||
|
||||
store := mset.store
|
||||
var state StreamState
|
||||
mset.store.FastState(&state)
|
||||
|
||||
// Make sure this sequence is not below our first sequence.
|
||||
if seq < state.FirstSeq {
|
||||
mset.clearPreAck(o, seq)
|
||||
mset.mu.Unlock()
|
||||
return
|
||||
}
|
||||
store.FastState(&state)
|
||||
|
||||
// If this has arrived before we have processed the message itself.
|
||||
if seq > state.LastSeq {
|
||||
@@ -5576,6 +5663,15 @@ func (mset *stream) ackMsg(o *consumer, seq uint64) {
|
||||
return
|
||||
}
|
||||
|
||||
// Always clear pre-ack if here.
|
||||
mset.clearPreAck(o, seq)
|
||||
|
||||
// Make sure this sequence is not below our first sequence.
|
||||
if seq < state.FirstSeq {
|
||||
mset.mu.Unlock()
|
||||
return
|
||||
}
|
||||
|
||||
var shouldRemove bool
|
||||
switch mset.cfg.Retention {
|
||||
case WorkQueuePolicy:
|
||||
@@ -5593,7 +5689,7 @@ func (mset *stream) ackMsg(o *consumer, seq uint64) {
|
||||
}
|
||||
|
||||
// If we are here we should attempt to remove.
|
||||
if _, err := mset.store.RemoveMsg(seq); err == ErrStoreEOF {
|
||||
if _, err := store.RemoveMsg(seq); err == ErrStoreEOF {
|
||||
// This should not happen, but being pedantic.
|
||||
mset.registerPreAckLock(o, seq)
|
||||
}
|
||||
|
||||
+3
-3
@@ -504,7 +504,7 @@ func (s *Sublist) addToCache(subject string, sub *subscription) {
|
||||
|
||||
// removeFromCache will remove the sub from any active cache entries.
|
||||
// Assumes write lock is held.
|
||||
func (s *Sublist) removeFromCache(subject string, sub *subscription) {
|
||||
func (s *Sublist) removeFromCache(subject string) {
|
||||
if s.cache == nil {
|
||||
return
|
||||
}
|
||||
@@ -635,7 +635,7 @@ func (s *Sublist) UpdateRemoteQSub(sub *subscription) {
|
||||
// it unless we are thrashing the cache. Just remove from our L2 and update
|
||||
// the genid so L1 will be flushed.
|
||||
s.Lock()
|
||||
s.removeFromCache(string(sub.subject), sub)
|
||||
s.removeFromCache(string(sub.subject))
|
||||
atomic.AddUint64(&s.genid, 1)
|
||||
s.Unlock()
|
||||
}
|
||||
@@ -798,7 +798,7 @@ func (s *Sublist) remove(sub *subscription, shouldLock bool, doCacheUpdates bool
|
||||
}
|
||||
}
|
||||
if doCacheUpdates {
|
||||
s.removeFromCache(subject, sub)
|
||||
s.removeFromCache(subject)
|
||||
atomic.AddUint64(&s.genid, 1)
|
||||
}
|
||||
|
||||
|
||||
+5
-3
@@ -1,4 +1,4 @@
|
||||
// Copyright 2019 The NATS Authors
|
||||
// Copyright 2019-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
|
||||
@@ -26,7 +26,9 @@ func sysctlInt64(name string) int64 {
|
||||
if err != nil {
|
||||
return 0
|
||||
}
|
||||
// hack because the string conversion above drops a \0
|
||||
b := []byte(s)
|
||||
// Make sure it's 8 bytes when we do the cast below.
|
||||
// We were getting fatal error: checkptr: converted pointer straddles multiple allocations in go 1.22.1 on darwin.
|
||||
var b [8]byte
|
||||
copy(b[:], s)
|
||||
return *(*int64)(unsafe.Pointer(&b[0]))
|
||||
}
|
||||
|
||||
+8
-8
@@ -1,12 +1,12 @@
|
||||
language: go
|
||||
go:
|
||||
- "1.22.x"
|
||||
- "1.21.x"
|
||||
- "1.20.x"
|
||||
go_import_path: github.com/nats-io/nats.go
|
||||
install:
|
||||
- go get -t ./...
|
||||
- curl -sSfL https://raw.githubusercontent.com/golangci/golangci-lint/master/install.sh | sh -s -- -b $(go env GOPATH)/bin
|
||||
- if [[ "$TRAVIS_GO_VERSION" =~ 1.21 ]]; then
|
||||
- if [[ "$TRAVIS_GO_VERSION" =~ 1.22 ]]; then
|
||||
go install github.com/mattn/goveralls@latest;
|
||||
go install github.com/wadey/gocovmerge@latest;
|
||||
go install honnef.co/go/tools/cmd/staticcheck@latest;
|
||||
@@ -15,22 +15,22 @@ install:
|
||||
before_script:
|
||||
- $(exit $(go fmt ./... | wc -l))
|
||||
- go vet -modfile=go_test.mod ./...
|
||||
- if [[ "$TRAVIS_GO_VERSION" =~ 1.21 ]]; then
|
||||
- if [[ "$TRAVIS_GO_VERSION" =~ 1.22 ]]; then
|
||||
find . -type f -name "*.go" | xargs misspell -error -locale US;
|
||||
GOFLAGS="-mod=mod -modfile=go_test.mod" staticcheck ./...;
|
||||
fi
|
||||
- golangci-lint run ./jetstream/...
|
||||
script:
|
||||
- go test -modfile=go_test.mod -v -run=TestNoRace -p=1 ./... --failfast -vet=off
|
||||
- if [[ "$TRAVIS_GO_VERSION" =~ 1.21 ]]; then ./scripts/cov.sh TRAVIS; else go test -modfile=go_test.mod -race -v -p=1 ./... --failfast -vet=off -tags=internal_testing; fi
|
||||
- if [[ "$TRAVIS_GO_VERSION" =~ 1.22 ]]; then ./scripts/cov.sh TRAVIS; else go test -modfile=go_test.mod -race -v -p=1 ./... --failfast -vet=off -tags=internal_testing; fi
|
||||
after_success:
|
||||
- if [[ "$TRAVIS_GO_VERSION" =~ 1.21 ]]; then $HOME/gopath/bin/goveralls -coverprofile=acc.out -service travis-ci; fi
|
||||
- if [[ "$TRAVIS_GO_VERSION" =~ 1.22 ]]; then $HOME/gopath/bin/goveralls -coverprofile=acc.out -service travis-ci; fi
|
||||
|
||||
jobs:
|
||||
include:
|
||||
- name: "Go: 1.21.x (nats-server@main)"
|
||||
go: "1.21.x"
|
||||
- name: "Go: 1.22.x (nats-server@main)"
|
||||
go: "1.22.x"
|
||||
before_script:
|
||||
- go get -modfile go_test.mod github.com/nats-io/nats-server/v2@main
|
||||
allow_failures:
|
||||
- name: "Go: 1.21.x (nats-server@main)"
|
||||
- name: "Go: 1.22.x (nats-server@main)"
|
||||
|
||||
+48
-13
@@ -1,6 +1,8 @@
|
||||
# Contributing
|
||||
|
||||
Thanks for your interest in contributing! This document contains `nats-io/nats.go` specific contributing details. If you are a first-time contributor, please refer to the general [NATS Contributor Guide](https://nats.io/contributing/) to get a comprehensive overview of contributing to the NATS project.
|
||||
Thanks for your interest in contributing! This document contains `nats-io/nats.go` specific contributing details. If you
|
||||
are a first-time contributor, please refer to the general [NATS Contributor Guide](https://nats.io/contributing/) to get
|
||||
a comprehensive overview of contributing to the NATS project.
|
||||
|
||||
## Getting started
|
||||
|
||||
@@ -10,36 +12,69 @@ There are three general ways you can contribute to this repo:
|
||||
- Reporting a bug or regression
|
||||
- Contributing changes to the source code
|
||||
|
||||
For the first two, refer to the [GitHub Issues](https://github.com/nats-io/nats.go/issues/new/choose) which guides you through the available options along with the needed information to collect.
|
||||
For the first two, refer to the [GitHub Issues](https://github.com/nats-io/nats.go/issues/new/choose) which guides you
|
||||
through the available options along with the needed information to collect.
|
||||
|
||||
## Contributing changes
|
||||
|
||||
_Prior to opening a pull request, it is recommended to open an issue first to ensure the maintainers can review intended changes. Exceptions to this rule include fixing non-functional source such as code comments, documentation or other supporting files._
|
||||
_Prior to opening a pull request, it is recommended to open an issue first to ensure the maintainers can review intended
|
||||
changes. Exceptions to this rule include fixing non-functional source such as code comments, documentation or other
|
||||
supporting files._
|
||||
|
||||
Proposing source code changes is done through GitHub's standard pull request workflow.
|
||||
|
||||
If your branch is a work-in-progress then please start by creating your pull requests as draft, by clicking the down-arrow next to the `Create pull request` button and instead selecting `Create draft pull request`.
|
||||
If your branch is a work-in-progress then please start by creating your pull requests as draft, by clicking the
|
||||
down-arrow next to the `Create pull request` button and instead selecting `Create draft pull request`.
|
||||
|
||||
This will defer the automatic process of requesting a review from the NATS team and significantly reduces noise until you are ready. Once you are happy, you can click the `Ready for review` button.
|
||||
This will defer the automatic process of requesting a review from the NATS team and significantly reduces noise until
|
||||
you are ready. Once you are happy, you can click the `Ready for review` button.
|
||||
|
||||
### Guidelines
|
||||
|
||||
A good pull request includes:
|
||||
|
||||
- A high-level description of the changes, including links to any issues that are related by adding comments like `Resolves #NNN` to your description. See [Linking a Pull Request to an Issue](https://docs.github.com/en/issues/tracking-your-work-with-issues/linking-a-pull-request-to-an-issue) for more information.
|
||||
- An up-to-date parent commit. Please make sure you are pulling in the latest `main` branch and rebasing your work on top of it, i.e. `git rebase main`.
|
||||
- Unit tests where appropriate. Bug fixes will benefit from the addition of regression tests. New features will not be accepted without suitable test coverage!
|
||||
- No more commits than necessary. Sometimes having multiple commits is useful for telling a story or isolating changes from one another, but please squash down any unnecessary commits that may just be for clean-up, comments or small changes.
|
||||
- No additional external dependencies that aren't absolutely essential. Please do everything you can to avoid pulling in additional libraries/dependencies into `go.mod` as we will be very critical of these.
|
||||
- A high-level description of the changes, including links to any issues that are related by adding comments
|
||||
like `Resolves #NNN` to your description.
|
||||
See [Linking a Pull Request to an Issue](https://docs.github.com/en/issues/tracking-your-work-with-issues/linking-a-pull-request-to-an-issue)
|
||||
for more information.
|
||||
- An up-to-date parent commit. Please make sure you are pulling in the latest `main` branch and rebasing your work on
|
||||
top of it, i.e. `git rebase main`.
|
||||
- Unit tests where appropriate. Bug fixes will benefit from the addition of regression tests. New features will not be
|
||||
accepted without suitable test coverage!
|
||||
- No more commits than necessary. Sometimes having multiple commits is useful for telling a story or isolating changes
|
||||
from one another, but please squash down any unnecessary commits that may just be for clean-up, comments or small
|
||||
changes.
|
||||
- No additional external dependencies that aren't absolutely essential. Please do everything you can to avoid pulling in
|
||||
additional libraries/dependencies into `go.mod` as we will be very critical of these.
|
||||
|
||||
### Sign-off
|
||||
|
||||
In order to accept a contribution, you will first need to certify that the contribution is your original work and that you license the work to the project under the [Apache-2.0 license](https://github.com/nats-io/nats.go/blob/main/LICENSE).
|
||||
In order to accept a contribution, you will first need to certify that the contribution is your original work and that
|
||||
you license the work to the project under
|
||||
the [Apache-2.0 license](https://github.com/nats-io/nats.go/blob/main/LICENSE).
|
||||
|
||||
This is done by using `Signed-off-by` statements, which should appear in **both** your commit messages and your PR description. Please note that we can only accept sign-offs under a legal name. Nicknames and aliases are not permitted.
|
||||
This is done by using `Signed-off-by` statements, which should appear in **both** your commit messages and your PR
|
||||
description. Please note that we can only accept sign-offs under a legal name. Nicknames and aliases are not permitted.
|
||||
|
||||
To perform a sign-off with `git`, use `git commit -s` (or `--signoff`).
|
||||
|
||||
## Get help
|
||||
|
||||
If you have questions about the contribution process, please start a [GitHub discussion](https://github.com/nats-io/nats.go/discussions), join the [NATS Slack](https://slack.nats.io/), or send your question to the [NATS Google Group](https://groups.google.com/forum/#!forum/natsio).
|
||||
If you have questions about the contribution process, please start
|
||||
a [GitHub discussion](https://github.com/nats-io/nats.go/discussions), join the [NATS Slack](https://slack.nats.io/), or
|
||||
send your question to the [NATS Google Group](https://groups.google.com/forum/#!forum/natsio).
|
||||
|
||||
## Testing
|
||||
|
||||
You should use `go_test.mod` to manage your testing dependencies. Please use the following command to update your
|
||||
dependencies and avoid changing the main `go.mod` in a PR:
|
||||
|
||||
```shell
|
||||
go mod tidy -modfile=go_test.mod
|
||||
```
|
||||
|
||||
To the tests you can pass `-modfile=go_test.mod` flag to `go test` or instead you can also set `GOFLAGS="-modfile=go_test.mod"` as an environment variable:
|
||||
|
||||
```shell
|
||||
go test ./... -modfile=go_test.mod
|
||||
```
|
||||
|
||||
+1
-1
@@ -31,7 +31,7 @@ When using or transitioning to Go modules support:
|
||||
```bash
|
||||
# Go client latest or explicit version
|
||||
go get github.com/nats-io/nats.go/@latest
|
||||
go get github.com/nats-io/nats.go/@v1.33.1
|
||||
go get github.com/nats-io/nats.go/@v1.34.1
|
||||
|
||||
# For latest NATS Server, add /v2 at the end
|
||||
go get github.com/nats-io/nats-server/v2
|
||||
|
||||
+6
-5
@@ -4,11 +4,12 @@ go 1.19
|
||||
|
||||
require (
|
||||
github.com/golang/protobuf v1.4.2
|
||||
github.com/klauspost/compress v1.17.4
|
||||
github.com/nats-io/nats-server/v2 v2.10.9
|
||||
github.com/klauspost/compress v1.17.6
|
||||
github.com/nats-io/jwt v1.2.2
|
||||
github.com/nats-io/nats-server/v2 v2.10.11
|
||||
github.com/nats-io/nkeys v0.4.7
|
||||
github.com/nats-io/nuid v1.0.1
|
||||
go.uber.org/goleak v1.2.1
|
||||
go.uber.org/goleak v1.3.0
|
||||
golang.org/x/text v0.14.0
|
||||
google.golang.org/protobuf v1.23.0
|
||||
)
|
||||
@@ -16,7 +17,7 @@ require (
|
||||
require (
|
||||
github.com/minio/highwayhash v1.0.2 // indirect
|
||||
github.com/nats-io/jwt/v2 v2.5.3 // indirect
|
||||
golang.org/x/crypto v0.18.0 // indirect
|
||||
golang.org/x/sys v0.16.0 // indirect
|
||||
golang.org/x/crypto v0.19.0 // indirect
|
||||
golang.org/x/sys v0.17.0 // indirect
|
||||
golang.org/x/time v0.5.0 // indirect
|
||||
)
|
||||
|
||||
+19
-10
@@ -10,27 +10,36 @@ github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMyw
|
||||
github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
|
||||
github.com/google/go-cmp v0.4.0 h1:xsAVV57WRhGj6kEIi8ReJzQlHHqcBYCElAvkovg3B/4=
|
||||
github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/klauspost/compress v1.17.4 h1:Ej5ixsIri7BrIjBkRZLTo6ghwrEtHFk7ijlczPW4fZ4=
|
||||
github.com/klauspost/compress v1.17.4/go.mod h1:/dCuZOvVtNoHsyb+cuJD3itjs3NbnF6KH9zAO4BDxPM=
|
||||
github.com/klauspost/compress v1.17.6 h1:60eq2E/jlfwQXtvZEeBUYADs+BwKBWURIY+Gj2eRGjI=
|
||||
github.com/klauspost/compress v1.17.6/go.mod h1:/dCuZOvVtNoHsyb+cuJD3itjs3NbnF6KH9zAO4BDxPM=
|
||||
github.com/minio/highwayhash v1.0.2 h1:Aak5U0nElisjDCfPSG79Tgzkn2gl66NxOMspRrKnA/g=
|
||||
github.com/minio/highwayhash v1.0.2/go.mod h1:BQskDq+xkJ12lmlUUi7U0M5Swg3EWR+dLTk+kldvVxY=
|
||||
github.com/nats-io/jwt v1.2.2 h1:w3GMTO969dFg+UOKTmmyuu7IGdusK+7Ytlt//OYH/uU=
|
||||
github.com/nats-io/jwt v1.2.2/go.mod h1:/xX356yQA6LuXI9xWW7mZNpxgF2mBmGecH+Fj34sP5Q=
|
||||
github.com/nats-io/jwt/v2 v2.5.3 h1:/9SWvzc6hTfamcgXJ3uYRpgj+QuY2aLNqRiqrKcrpEo=
|
||||
github.com/nats-io/jwt/v2 v2.5.3/go.mod h1:iysuPemFcc7p4IoYots3IuELSI4EDe9Y0bQMe+I3Bf4=
|
||||
github.com/nats-io/nats-server/v2 v2.10.9 h1:VEW43Zz+p+9lARtiPM9ctd6ckun+92ZT2T17HWtwiFI=
|
||||
github.com/nats-io/nats-server/v2 v2.10.9/go.mod h1:oorGiV9j3BOLLO3ejQe+U7pfAGyPo+ppD7rpgNF6KTQ=
|
||||
github.com/nats-io/nats-server/v2 v2.10.11 h1:yKUiLVincZISpo3A4YljJQ+HfLltGAgoNNJl99KL8I0=
|
||||
github.com/nats-io/nats-server/v2 v2.10.11/go.mod h1:dXtOqVWzbMTEj+tUyC/itXjJhW37xh0tUBrTAlqAfx8=
|
||||
github.com/nats-io/nkeys v0.2.0/go.mod h1:XdZpAbhgyyODYqjTawOnIOI7VlbKSarI9Gfy1tqEu/s=
|
||||
github.com/nats-io/nkeys v0.4.7 h1:RwNJbbIdYCoClSDNY7QVKZlyb/wfT6ugvFCiKy6vDvI=
|
||||
github.com/nats-io/nkeys v0.4.7/go.mod h1:kqXRgRDPlGy7nGaEDMuYzmiJCIAAWDK0IMBtDmGD0nc=
|
||||
github.com/nats-io/nuid v1.0.1 h1:5iA8DT8V7q8WK2EScv2padNa/rTESc1KdnPw4TC2paw=
|
||||
github.com/nats-io/nuid v1.0.1/go.mod h1:19wcPz3Ph3q0Jbyiqsd0kePYG7A95tJPxeL+1OSON2c=
|
||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||
github.com/stretchr/testify v1.8.0 h1:pSgiaMZlXftHpm5L7V1+rVB+AZJydKsMxsQBIJw4PKk=
|
||||
go.uber.org/goleak v1.2.1 h1:NBol2c7O1ZokfZ0LEU9K6Whx/KnwvepVetCUhtKja4A=
|
||||
go.uber.org/goleak v1.2.1/go.mod h1:qlT2yGI9QafXHhZZLxlSuNsMw3FFLxBr+tBRlmO1xH4=
|
||||
golang.org/x/crypto v0.18.0 h1:PGVlW0xEltQnzFZ55hkuX5+KLyrMYhHld1YHO4AKcdc=
|
||||
golang.org/x/crypto v0.18.0/go.mod h1:R0j02AL6hcrfOiy9T4ZYp/rcWeMxM3L6QYxlOuEG1mg=
|
||||
go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto=
|
||||
go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE=
|
||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||
golang.org/x/crypto v0.0.0-20200323165209-0ec3e9974c59/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||
golang.org/x/crypto v0.19.0 h1:ENy+Az/9Y1vSrlrvBSyna3PITt4tiZLf7sgCjZBX7Wo=
|
||||
golang.org/x/crypto v0.19.0/go.mod h1:Iy9bg/ha4yyC70EfRS8jz+B6ybOBKMaSxLj6P6oBDfU=
|
||||
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||
golang.org/x/sys v0.0.0-20190130150945-aca44879d564/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.16.0 h1:xWw16ngr6ZMtmxDyKyIgsE93KNKz5HKmMa3b8ALHidU=
|
||||
golang.org/x/sys v0.16.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.17.0 h1:25cE3gD+tdBA7lp7QfhuV+rJiE9YXTcS3VG1SqssI/Y=
|
||||
golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ=
|
||||
golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
|
||||
golang.org/x/time v0.5.0 h1:o7cqy6amK/52YcAKIPlM3a+Fpj35zvRj2TP+e1xFSfk=
|
||||
|
||||
+36
-4
@@ -712,10 +712,20 @@ func (js *js) resetPendingAcksOnReconnect() {
|
||||
return
|
||||
}
|
||||
js.mu.Lock()
|
||||
for _, paf := range js.pafs {
|
||||
errCb := js.opts.aecb
|
||||
for id, paf := range js.pafs {
|
||||
paf.err = ErrDisconnected
|
||||
if paf.errCh != nil {
|
||||
paf.errCh <- paf.err
|
||||
}
|
||||
if errCb != nil {
|
||||
// clear reply subject so that new one is created on republish
|
||||
js.mu.Unlock()
|
||||
errCb(js, paf.msg, ErrDisconnected)
|
||||
js.mu.Lock()
|
||||
}
|
||||
delete(js.pafs, id)
|
||||
}
|
||||
js.pafs = nil
|
||||
if js.dch != nil {
|
||||
close(js.dch)
|
||||
js.dch = nil
|
||||
@@ -2861,7 +2871,14 @@ func (sub *Subscription) Fetch(batch int, opts ...PullOpt) ([]*Msg, error) {
|
||||
}
|
||||
var hbTimer *time.Timer
|
||||
var hbErr error
|
||||
if err == nil && len(msgs) < batch {
|
||||
sub.mu.Lock()
|
||||
subClosed := sub.closed || sub.draining
|
||||
sub.mu.Unlock()
|
||||
if subClosed {
|
||||
err = errors.Join(ErrBadSubscription, ErrSubscriptionClosed)
|
||||
}
|
||||
hbLock := sync.Mutex{}
|
||||
if err == nil && len(msgs) < batch && !subClosed {
|
||||
// For batch real size of 1, it does not make sense to set no_wait in
|
||||
// the request.
|
||||
noWait := batch-len(msgs) > 1
|
||||
@@ -2903,7 +2920,9 @@ func (sub *Subscription) Fetch(batch int, opts ...PullOpt) ([]*Msg, error) {
|
||||
if o.hb > 0 {
|
||||
if hbTimer == nil {
|
||||
hbTimer = time.AfterFunc(2*o.hb, func() {
|
||||
hbLock.Lock()
|
||||
hbErr = ErrNoHeartbeat
|
||||
hbLock.Unlock()
|
||||
cancel()
|
||||
})
|
||||
} else {
|
||||
@@ -2945,6 +2964,8 @@ func (sub *Subscription) Fetch(batch int, opts ...PullOpt) ([]*Msg, error) {
|
||||
}
|
||||
// If there is at least a message added to msgs, then need to return OK and no error
|
||||
if err != nil && len(msgs) == 0 {
|
||||
hbLock.Lock()
|
||||
defer hbLock.Unlock()
|
||||
if hbErr != nil {
|
||||
return nil, hbErr
|
||||
}
|
||||
@@ -3129,8 +3150,14 @@ func (sub *Subscription) FetchBatch(batch int, opts ...PullOpt) (MessageBatch, e
|
||||
result.msgs <- msg
|
||||
}
|
||||
}
|
||||
if len(result.msgs) == batch || result.err != nil {
|
||||
sub.mu.Lock()
|
||||
subClosed := sub.closed || sub.draining
|
||||
sub.mu.Unlock()
|
||||
if len(result.msgs) == batch || result.err != nil || subClosed {
|
||||
close(result.msgs)
|
||||
if subClosed && len(result.msgs) == 0 {
|
||||
return nil, errors.Join(ErrBadSubscription, ErrSubscriptionClosed)
|
||||
}
|
||||
result.done <- struct{}{}
|
||||
return result, nil
|
||||
}
|
||||
@@ -3169,9 +3196,12 @@ func (sub *Subscription) FetchBatch(batch int, opts ...PullOpt) (MessageBatch, e
|
||||
}
|
||||
var hbTimer *time.Timer
|
||||
var hbErr error
|
||||
hbLock := sync.Mutex{}
|
||||
if o.hb > 0 {
|
||||
hbTimer = time.AfterFunc(2*o.hb, func() {
|
||||
hbLock.Lock()
|
||||
hbErr = ErrNoHeartbeat
|
||||
hbLock.Unlock()
|
||||
cancel()
|
||||
})
|
||||
}
|
||||
@@ -3207,11 +3237,13 @@ func (sub *Subscription) FetchBatch(batch int, opts ...PullOpt) (MessageBatch, e
|
||||
}
|
||||
}
|
||||
if err != nil {
|
||||
hbLock.Lock()
|
||||
if hbErr != nil {
|
||||
result.err = hbErr
|
||||
} else {
|
||||
result.err = o.checkCtxErr(err)
|
||||
}
|
||||
hbLock.Unlock()
|
||||
}
|
||||
close(result.msgs)
|
||||
result.done <- struct{}{}
|
||||
|
||||
+4
-1
@@ -51,7 +51,7 @@ var (
|
||||
// ErrStreamSourceMultipleSubjectTransformsNotSupported is returned when the connected nats-server version does not support setting
|
||||
// the stream sources. If this error is returned when executing AddStream(), the stream with invalid
|
||||
// configuration was already created in the server.
|
||||
ErrStreamSourceMultipleSubjectTransformsNotSupported JetStreamError = &jsError{message: "stream sourceing with multiple subject transforms not supported by nats-server"}
|
||||
ErrStreamSourceMultipleSubjectTransformsNotSupported JetStreamError = &jsError{message: "stream sourcing with multiple subject transforms not supported by nats-server"}
|
||||
|
||||
// ErrConsumerNotFound is an error returned when consumer with given name does not exist.
|
||||
ErrConsumerNotFound JetStreamError = &jsError{apiErr: &APIError{ErrorCode: JSErrCodeConsumerNotFound, Description: "consumer not found", Code: 404}}
|
||||
@@ -141,6 +141,9 @@ var (
|
||||
// ErrNoHeartbeat is returned when no heartbeat is received from server when sending requests with pull consumer.
|
||||
ErrNoHeartbeat JetStreamError = &jsError{message: "no heartbeat received"}
|
||||
|
||||
// ErrSubscriptionClosed is returned when attempting to send pull request to a closed subscription
|
||||
ErrSubscriptionClosed JetStreamError = &jsError{message: "subscription closed"}
|
||||
|
||||
// DEPRECATED: ErrInvalidDurableName is no longer returned and will be removed in future releases.
|
||||
// Use ErrInvalidConsumerName instead.
|
||||
ErrInvalidDurableName = errors.New("nats: invalid durable name")
|
||||
|
||||
+230
-69
@@ -1,4 +1,4 @@
|
||||
// Copyright 2012-2023 The NATS Authors
|
||||
// Copyright 2012-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
|
||||
@@ -47,7 +47,7 @@ import (
|
||||
|
||||
// Default Constants
|
||||
const (
|
||||
Version = "1.33.1"
|
||||
Version = "1.34.1"
|
||||
DefaultURL = "nats://127.0.0.1:4222"
|
||||
DefaultPort = 4222
|
||||
DefaultMaxReconnect = 60
|
||||
@@ -90,55 +90,56 @@ const (
|
||||
|
||||
// Errors
|
||||
var (
|
||||
ErrConnectionClosed = errors.New("nats: connection closed")
|
||||
ErrConnectionDraining = errors.New("nats: connection draining")
|
||||
ErrDrainTimeout = errors.New("nats: draining connection timed out")
|
||||
ErrConnectionReconnecting = errors.New("nats: connection reconnecting")
|
||||
ErrSecureConnRequired = errors.New("nats: secure connection required")
|
||||
ErrSecureConnWanted = errors.New("nats: secure connection not available")
|
||||
ErrBadSubscription = errors.New("nats: invalid subscription")
|
||||
ErrTypeSubscription = errors.New("nats: invalid subscription type")
|
||||
ErrBadSubject = errors.New("nats: invalid subject")
|
||||
ErrBadQueueName = errors.New("nats: invalid queue name")
|
||||
ErrSlowConsumer = errors.New("nats: slow consumer, messages dropped")
|
||||
ErrTimeout = errors.New("nats: timeout")
|
||||
ErrBadTimeout = errors.New("nats: timeout invalid")
|
||||
ErrAuthorization = errors.New("nats: authorization violation")
|
||||
ErrAuthExpired = errors.New("nats: authentication expired")
|
||||
ErrAuthRevoked = errors.New("nats: authentication revoked")
|
||||
ErrAccountAuthExpired = errors.New("nats: account authentication expired")
|
||||
ErrNoServers = errors.New("nats: no servers available for connection")
|
||||
ErrJsonParse = errors.New("nats: connect message, json parse error")
|
||||
ErrChanArg = errors.New("nats: argument needs to be a channel type")
|
||||
ErrMaxPayload = errors.New("nats: maximum payload exceeded")
|
||||
ErrMaxMessages = errors.New("nats: maximum messages delivered")
|
||||
ErrSyncSubRequired = errors.New("nats: illegal call on an async subscription")
|
||||
ErrMultipleTLSConfigs = errors.New("nats: multiple tls.Configs not allowed")
|
||||
ErrNoInfoReceived = errors.New("nats: protocol exception, INFO not received")
|
||||
ErrReconnectBufExceeded = errors.New("nats: outbound buffer limit exceeded")
|
||||
ErrInvalidConnection = errors.New("nats: invalid connection")
|
||||
ErrInvalidMsg = errors.New("nats: invalid message or message nil")
|
||||
ErrInvalidArg = errors.New("nats: invalid argument")
|
||||
ErrInvalidContext = errors.New("nats: invalid context")
|
||||
ErrNoDeadlineContext = errors.New("nats: context requires a deadline")
|
||||
ErrNoEchoNotSupported = errors.New("nats: no echo option not supported by this server")
|
||||
ErrClientIDNotSupported = errors.New("nats: client ID not supported by this server")
|
||||
ErrUserButNoSigCB = errors.New("nats: user callback defined without a signature handler")
|
||||
ErrNkeyButNoSigCB = errors.New("nats: nkey defined without a signature handler")
|
||||
ErrNoUserCB = errors.New("nats: user callback not defined")
|
||||
ErrNkeyAndUser = errors.New("nats: user callback and nkey defined")
|
||||
ErrNkeysNotSupported = errors.New("nats: nkeys not supported by the server")
|
||||
ErrStaleConnection = errors.New("nats: " + STALE_CONNECTION)
|
||||
ErrTokenAlreadySet = errors.New("nats: token and token handler both set")
|
||||
ErrMsgNotBound = errors.New("nats: message is not bound to subscription/connection")
|
||||
ErrMsgNoReply = errors.New("nats: message does not have a reply")
|
||||
ErrClientIPNotSupported = errors.New("nats: client IP not supported by this server")
|
||||
ErrDisconnected = errors.New("nats: server is disconnected")
|
||||
ErrHeadersNotSupported = errors.New("nats: headers not supported by this server")
|
||||
ErrBadHeaderMsg = errors.New("nats: message could not decode headers")
|
||||
ErrNoResponders = errors.New("nats: no responders available for request")
|
||||
ErrMaxConnectionsExceeded = errors.New("nats: server maximum connections exceeded")
|
||||
ErrConnectionNotTLS = errors.New("nats: connection is not tls")
|
||||
ErrConnectionClosed = errors.New("nats: connection closed")
|
||||
ErrConnectionDraining = errors.New("nats: connection draining")
|
||||
ErrDrainTimeout = errors.New("nats: draining connection timed out")
|
||||
ErrConnectionReconnecting = errors.New("nats: connection reconnecting")
|
||||
ErrSecureConnRequired = errors.New("nats: secure connection required")
|
||||
ErrSecureConnWanted = errors.New("nats: secure connection not available")
|
||||
ErrBadSubscription = errors.New("nats: invalid subscription")
|
||||
ErrTypeSubscription = errors.New("nats: invalid subscription type")
|
||||
ErrBadSubject = errors.New("nats: invalid subject")
|
||||
ErrBadQueueName = errors.New("nats: invalid queue name")
|
||||
ErrSlowConsumer = errors.New("nats: slow consumer, messages dropped")
|
||||
ErrTimeout = errors.New("nats: timeout")
|
||||
ErrBadTimeout = errors.New("nats: timeout invalid")
|
||||
ErrAuthorization = errors.New("nats: authorization violation")
|
||||
ErrAuthExpired = errors.New("nats: authentication expired")
|
||||
ErrAuthRevoked = errors.New("nats: authentication revoked")
|
||||
ErrAccountAuthExpired = errors.New("nats: account authentication expired")
|
||||
ErrNoServers = errors.New("nats: no servers available for connection")
|
||||
ErrJsonParse = errors.New("nats: connect message, json parse error")
|
||||
ErrChanArg = errors.New("nats: argument needs to be a channel type")
|
||||
ErrMaxPayload = errors.New("nats: maximum payload exceeded")
|
||||
ErrMaxMessages = errors.New("nats: maximum messages delivered")
|
||||
ErrSyncSubRequired = errors.New("nats: illegal call on an async subscription")
|
||||
ErrMultipleTLSConfigs = errors.New("nats: multiple tls.Configs not allowed")
|
||||
ErrClientCertOrRootCAsRequired = errors.New("nats: at least one of certCB or rootCAsCB must be set")
|
||||
ErrNoInfoReceived = errors.New("nats: protocol exception, INFO not received")
|
||||
ErrReconnectBufExceeded = errors.New("nats: outbound buffer limit exceeded")
|
||||
ErrInvalidConnection = errors.New("nats: invalid connection")
|
||||
ErrInvalidMsg = errors.New("nats: invalid message or message nil")
|
||||
ErrInvalidArg = errors.New("nats: invalid argument")
|
||||
ErrInvalidContext = errors.New("nats: invalid context")
|
||||
ErrNoDeadlineContext = errors.New("nats: context requires a deadline")
|
||||
ErrNoEchoNotSupported = errors.New("nats: no echo option not supported by this server")
|
||||
ErrClientIDNotSupported = errors.New("nats: client ID not supported by this server")
|
||||
ErrUserButNoSigCB = errors.New("nats: user callback defined without a signature handler")
|
||||
ErrNkeyButNoSigCB = errors.New("nats: nkey defined without a signature handler")
|
||||
ErrNoUserCB = errors.New("nats: user callback not defined")
|
||||
ErrNkeyAndUser = errors.New("nats: user callback and nkey defined")
|
||||
ErrNkeysNotSupported = errors.New("nats: nkeys not supported by the server")
|
||||
ErrStaleConnection = errors.New("nats: " + STALE_CONNECTION)
|
||||
ErrTokenAlreadySet = errors.New("nats: token and token handler both set")
|
||||
ErrMsgNotBound = errors.New("nats: message is not bound to subscription/connection")
|
||||
ErrMsgNoReply = errors.New("nats: message does not have a reply")
|
||||
ErrClientIPNotSupported = errors.New("nats: client IP not supported by this server")
|
||||
ErrDisconnected = errors.New("nats: server is disconnected")
|
||||
ErrHeadersNotSupported = errors.New("nats: headers not supported by this server")
|
||||
ErrBadHeaderMsg = errors.New("nats: message could not decode headers")
|
||||
ErrNoResponders = errors.New("nats: no responders available for request")
|
||||
ErrMaxConnectionsExceeded = errors.New("nats: server maximum connections exceeded")
|
||||
ErrConnectionNotTLS = errors.New("nats: connection is not tls")
|
||||
)
|
||||
|
||||
// GetDefaultOptions returns default configuration options for the client.
|
||||
@@ -565,7 +566,6 @@ type Conn struct {
|
||||
respSub string // The wildcard subject
|
||||
respSubPrefix string // the wildcard prefix including trailing .
|
||||
respSubLen int // the length of the wildcard prefix excluding trailing .
|
||||
respScanf string // The scanf template to extract mux token
|
||||
respMux *Subscription // A single response subscription
|
||||
respMap map[string]chan *Msg // Request map for the response msg channels
|
||||
respRand *rand.Rand // Used for generating suffix
|
||||
@@ -607,14 +607,17 @@ type Subscription struct {
|
||||
// For holding information about a JetStream consumer.
|
||||
jsi *jsSub
|
||||
|
||||
delivered uint64
|
||||
max uint64
|
||||
conn *Conn
|
||||
mcb MsgHandler
|
||||
mch chan *Msg
|
||||
closed bool
|
||||
sc bool
|
||||
connClosed bool
|
||||
delivered uint64
|
||||
max uint64
|
||||
conn *Conn
|
||||
mcb MsgHandler
|
||||
mch chan *Msg
|
||||
closed bool
|
||||
sc bool
|
||||
connClosed bool
|
||||
draining bool
|
||||
status SubStatus
|
||||
statListeners map[chan SubStatus][]SubStatus
|
||||
|
||||
// Type of Subscription
|
||||
typ SubscriptionType
|
||||
@@ -635,6 +638,30 @@ type Subscription struct {
|
||||
dropped int
|
||||
}
|
||||
|
||||
// Status represents the state of the connection.
|
||||
type SubStatus int
|
||||
|
||||
const (
|
||||
SubscriptionActive = SubStatus(iota)
|
||||
SubscriptionDraining
|
||||
SubscriptionClosed
|
||||
SubscriptionSlowConsumer
|
||||
)
|
||||
|
||||
func (s SubStatus) String() string {
|
||||
switch s {
|
||||
case SubscriptionActive:
|
||||
return "Active"
|
||||
case SubscriptionDraining:
|
||||
return "Draining"
|
||||
case SubscriptionClosed:
|
||||
return "Closed"
|
||||
case SubscriptionSlowConsumer:
|
||||
return "SlowConsumer"
|
||||
}
|
||||
return "unknown status"
|
||||
}
|
||||
|
||||
// Msg represents a message delivered by NATS. This structure is used
|
||||
// by Subscribers and PublishMsg().
|
||||
//
|
||||
@@ -864,6 +891,40 @@ func Secure(tls ...*tls.Config) Option {
|
||||
}
|
||||
}
|
||||
|
||||
// ClientTLSConfig is an Option to set the TLS configuration for secure
|
||||
// connections. It can be used to e.g. set TLS config with cert and root CAs
|
||||
// from memory. For simple use case of loading cert and CAs from file,
|
||||
// ClientCert and RootCAs options are more convenient.
|
||||
// If Secure is not already set this will set it as well.
|
||||
func ClientTLSConfig(certCB TLSCertHandler, rootCAsCB RootCAsHandler) Option {
|
||||
return func(o *Options) error {
|
||||
o.Secure = true
|
||||
|
||||
if certCB == nil && rootCAsCB == nil {
|
||||
return ErrClientCertOrRootCAsRequired
|
||||
}
|
||||
|
||||
// Smoke test the callbacks to fail early
|
||||
// if they are not valid.
|
||||
if certCB != nil {
|
||||
if _, err := certCB(); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
if rootCAsCB != nil {
|
||||
if _, err := rootCAsCB(); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
if o.TLSConfig == nil {
|
||||
o.TLSConfig = &tls.Config{MinVersion: tls.VersionTLS12}
|
||||
}
|
||||
o.TLSCertCB = certCB
|
||||
o.RootCAsCB = rootCAsCB
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
// RootCAs is a helper option to provide the RootCAs pool from a list of filenames.
|
||||
// If Secure is not already set this will set it as well.
|
||||
func RootCAs(file ...string) Option {
|
||||
@@ -3257,6 +3318,9 @@ func (nc *Conn) processMsg(data []byte) {
|
||||
}
|
||||
|
||||
// Clear any SlowConsumer status.
|
||||
if sub.sc {
|
||||
sub.changeSubStatus(SubscriptionActive)
|
||||
}
|
||||
sub.sc = false
|
||||
sub.mu.Unlock()
|
||||
|
||||
@@ -3280,8 +3344,9 @@ slowConsumer:
|
||||
sub.pMsgs--
|
||||
sub.pBytes -= len(m.Data)
|
||||
}
|
||||
sub.mu.Unlock()
|
||||
if sc {
|
||||
sub.changeSubStatus(SubscriptionSlowConsumer)
|
||||
sub.mu.Unlock()
|
||||
// Now we need connection's lock and we may end-up in the situation
|
||||
// that we were trying to avoid, except that in this case, the client
|
||||
// is already experiencing client-side slow consumer situation.
|
||||
@@ -3291,6 +3356,8 @@ slowConsumer:
|
||||
nc.ach.push(func() { nc.Opts.AsyncErrorCB(nc, sub, ErrSlowConsumer) })
|
||||
}
|
||||
nc.mu.Unlock()
|
||||
} else {
|
||||
sub.mu.Unlock()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3903,7 +3970,6 @@ func (nc *Conn) createNewRequestAndSend(subj string, hdr, data []byte) (chan *Ms
|
||||
nc.mu.Unlock()
|
||||
return nil, token, err
|
||||
}
|
||||
nc.respScanf = strings.Replace(nc.respSub, "*", "%s", -1)
|
||||
nc.respMux = s
|
||||
}
|
||||
nc.mu.Unlock()
|
||||
@@ -4084,16 +4150,14 @@ func (nc *Conn) NewRespInbox() string {
|
||||
}
|
||||
|
||||
// respToken will return the last token of a literal response inbox
|
||||
// which we use for the message channel lookup. This needs to do a
|
||||
// scan to protect itself against the server changing the subject.
|
||||
// which we use for the message channel lookup. This needs to verify the subject
|
||||
// prefix matches to protect itself against the server changing the subject.
|
||||
// Lock should be held.
|
||||
func (nc *Conn) respToken(respInbox string) string {
|
||||
var token string
|
||||
n, err := fmt.Sscanf(respInbox, nc.respScanf, &token)
|
||||
if err != nil || n != 1 {
|
||||
return ""
|
||||
if token, found := strings.CutPrefix(respInbox, nc.respSubPrefix); found {
|
||||
return token
|
||||
}
|
||||
return token
|
||||
return ""
|
||||
}
|
||||
|
||||
// Subscribe will express interest in the given subject. The subject
|
||||
@@ -4263,6 +4327,7 @@ func (nc *Conn) subscribeLocked(subj, queue string, cb MsgHandler, ch chan *Msg,
|
||||
nc.kickFlusher()
|
||||
}
|
||||
|
||||
sub.changeSubStatus(SubscriptionActive)
|
||||
return sub, nil
|
||||
}
|
||||
|
||||
@@ -4306,6 +4371,7 @@ func (nc *Conn) removeSub(s *Subscription) {
|
||||
}
|
||||
// Mark as invalid
|
||||
s.closed = true
|
||||
s.changeSubStatus(SubscriptionClosed)
|
||||
if s.pCond != nil {
|
||||
s.pCond.Broadcast()
|
||||
}
|
||||
@@ -4375,6 +4441,91 @@ func (s *Subscription) Drain() error {
|
||||
return conn.unsubscribe(s, 0, true)
|
||||
}
|
||||
|
||||
// IsDraining returns a boolean indicating whether the subscription
|
||||
// is being drained.
|
||||
// This will return false if the subscription has already been closed.
|
||||
func (s *Subscription) IsDraining() bool {
|
||||
if s == nil {
|
||||
return false
|
||||
}
|
||||
s.mu.Lock()
|
||||
defer s.mu.Unlock()
|
||||
return s.draining
|
||||
}
|
||||
|
||||
// StatusChanged returns a channel on which given list of subscription status
|
||||
// changes will be sent. If no status is provided, all status changes will be sent.
|
||||
// Available statuses are SubscriptionActive, SubscriptionDraining, SubscriptionClosed,
|
||||
// and SubscriptionSlowConsumer.
|
||||
// The returned channel will be closed when the subscription is closed.
|
||||
func (s *Subscription) StatusChanged(statuses ...SubStatus) <-chan SubStatus {
|
||||
if len(statuses) == 0 {
|
||||
statuses = []SubStatus{SubscriptionActive, SubscriptionDraining, SubscriptionClosed, SubscriptionSlowConsumer}
|
||||
}
|
||||
ch := make(chan SubStatus, 10)
|
||||
for _, status := range statuses {
|
||||
s.registerStatusChangeListener(status, ch)
|
||||
// initial status
|
||||
if status == s.status {
|
||||
ch <- status
|
||||
}
|
||||
}
|
||||
return ch
|
||||
}
|
||||
|
||||
// registerStatusChangeListener registers a channel waiting for a specific status change event.
|
||||
// Status change events are non-blocking - if no receiver is waiting for the status change,
|
||||
// it will not be sent on the channel. Closed channels are ignored.
|
||||
func (s *Subscription) registerStatusChangeListener(status SubStatus, ch chan SubStatus) {
|
||||
s.mu.Lock()
|
||||
defer s.mu.Unlock()
|
||||
if s.statListeners == nil {
|
||||
s.statListeners = make(map[chan SubStatus][]SubStatus)
|
||||
}
|
||||
if _, ok := s.statListeners[ch]; !ok {
|
||||
s.statListeners[ch] = make([]SubStatus, 0)
|
||||
}
|
||||
s.statListeners[ch] = append(s.statListeners[ch], status)
|
||||
}
|
||||
|
||||
// sendStatusEvent sends subscription status event to all channels.
|
||||
// If there is no listener, sendStatusEvent
|
||||
// will not block. Lock should be held entering.
|
||||
func (s *Subscription) sendStatusEvent(status SubStatus) {
|
||||
for ch, statuses := range s.statListeners {
|
||||
if !containsStatus(statuses, status) {
|
||||
continue
|
||||
}
|
||||
// only send event if someone's listening
|
||||
select {
|
||||
case ch <- status:
|
||||
default:
|
||||
}
|
||||
if status == SubscriptionClosed {
|
||||
close(ch)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func containsStatus(statuses []SubStatus, status SubStatus) bool {
|
||||
for _, s := range statuses {
|
||||
if s == status {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// changeSubStatus changes subscription status and sends events
|
||||
// to all listeners. Lock should be held entering.
|
||||
func (s *Subscription) changeSubStatus(status SubStatus) {
|
||||
if s == nil {
|
||||
return
|
||||
}
|
||||
s.sendStatusEvent(status)
|
||||
s.status = status
|
||||
}
|
||||
|
||||
// Unsubscribe will remove interest in the given subject.
|
||||
//
|
||||
// For a JetStream subscription, if the library has created the JetStream
|
||||
@@ -4413,6 +4564,11 @@ func (s *Subscription) Unsubscribe() error {
|
||||
// checkDrained will watch for a subscription to be fully drained
|
||||
// and then remove it.
|
||||
func (nc *Conn) checkDrained(sub *Subscription) {
|
||||
defer func() {
|
||||
sub.mu.Lock()
|
||||
defer sub.mu.Unlock()
|
||||
sub.draining = false
|
||||
}()
|
||||
if nc == nil || sub == nil {
|
||||
return
|
||||
}
|
||||
@@ -4522,6 +4678,10 @@ func (nc *Conn) unsubscribe(sub *Subscription, max int, drainMode bool) error {
|
||||
}
|
||||
|
||||
if drainMode {
|
||||
s.mu.Lock()
|
||||
s.draining = true
|
||||
sub.changeSubStatus(SubscriptionDraining)
|
||||
s.mu.Unlock()
|
||||
go nc.checkDrained(sub)
|
||||
}
|
||||
|
||||
@@ -4624,6 +4784,7 @@ func (s *Subscription) validateNextMsgState(pullSubInternal bool) error {
|
||||
return ErrSyncSubRequired
|
||||
}
|
||||
if s.sc {
|
||||
s.changeSubStatus(SubscriptionActive)
|
||||
s.sc = false
|
||||
return ErrSlowConsumer
|
||||
}
|
||||
|
||||
+9
-3
@@ -155,7 +155,7 @@ type ObjectStoreConfig struct {
|
||||
Metadata map[string]string `json:"metadata,omitempty"`
|
||||
// Enable underlying stream compression.
|
||||
// NOTE: Compression is supported for nats-server 2.10.0+
|
||||
Compression bool
|
||||
Compression bool `json:"compression,omitempty"`
|
||||
}
|
||||
|
||||
type ObjectStoreStatus interface {
|
||||
@@ -694,7 +694,12 @@ func (obs *obs) Get(name string, opts ...GetObjectOpt) (ObjectResult, error) {
|
||||
}
|
||||
|
||||
chunkSubj := fmt.Sprintf(objChunksPreTmpl, obs.name, info.NUID)
|
||||
_, err = obs.js.Subscribe(chunkSubj, processChunk, OrderedConsumer())
|
||||
streamName := fmt.Sprintf(objNameTmpl, obs.name)
|
||||
subscribeOpts := []SubOpt{
|
||||
OrderedConsumer(),
|
||||
BindStream(streamName),
|
||||
}
|
||||
_, err = obs.js.Subscribe(chunkSubj, processChunk, subscribeOpts...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -1110,7 +1115,8 @@ func (obs *obs) Watch(opts ...WatchOpt) (ObjectWatcher, error) {
|
||||
}
|
||||
|
||||
// Used ordered consumer to deliver results.
|
||||
subOpts := []SubOpt{OrderedConsumer()}
|
||||
streamName := fmt.Sprintf(objNameTmpl, obs.name)
|
||||
subOpts := []SubOpt{OrderedConsumer(), BindStream(streamName)}
|
||||
if !o.includeHistory {
|
||||
subOpts = append(subOpts, DeliverLastPerSubject())
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user