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:
dependabot[bot]
2024-04-23 09:30:37 +02:00
committed by Ralf Haferkamp
parent 38699ca1e1
commit 628b4bdbea
53 changed files with 2027 additions and 1260 deletions
+1 -1
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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(&lt, &retError)
tk, v := unwrapValue(v, &lt)
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
View File
@@ -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(&lt, &retError)
tk, v := unwrapValue(v, &lt)
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
View File
File diff suppressed because it is too large Load Diff
+25 -29
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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())
}