diff --git a/go.mod b/go.mod index 862804cc2e..f21feacd41 100644 --- a/go.mod +++ b/go.mod @@ -63,7 +63,7 @@ require ( github.com/mitchellh/mapstructure v1.5.0 github.com/mna/pigeon v1.2.1 github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826 - github.com/nats-io/nats-server/v2 v2.10.14 + github.com/nats-io/nats-server/v2 v2.10.15 github.com/nats-io/nats.go v1.34.1 github.com/oklog/run v1.1.0 github.com/olekukonko/tablewriter v0.0.5 @@ -282,7 +282,7 @@ require ( github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect github.com/modern-go/reflect2 v1.0.2 // indirect github.com/mschoch/smat v0.2.0 // indirect - github.com/nats-io/jwt/v2 v2.5.5 // indirect + github.com/nats-io/jwt/v2 v2.5.6 // indirect github.com/nats-io/nkeys v0.4.7 // indirect github.com/nats-io/nuid v1.0.1 // indirect github.com/nxadm/tail v1.4.8 // indirect diff --git a/go.sum b/go.sum index fe6b013e20..2f02c1e08a 100644 --- a/go.sum +++ b/go.sum @@ -1755,10 +1755,10 @@ github.com/mschoch/smat v0.2.0/go.mod h1:kc9mz7DoBKqDyiRL7VZN8KvXQMWeTaVnttLRXOl github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= github.com/mwitkow/go-conntrack v0.0.0-20190716064945-2f068394615f/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= github.com/namedotcom/go v0.0.0-20180403034216-08470befbe04/go.mod h1:5sN+Lt1CaY4wsPvgQH/jsuJi4XO2ssZbdsIizr4CVC8= -github.com/nats-io/jwt/v2 v2.5.5 h1:ROfXb50elFq5c9+1ztaUbdlrArNFl2+fQWP6B8HGEq4= -github.com/nats-io/jwt/v2 v2.5.5/go.mod h1:ZdWS1nZa6WMZfFwwgpEaqBV8EPGVgOTDHN/wTbz0Y5A= -github.com/nats-io/nats-server/v2 v2.10.14 h1:98gPJFOAO2vLdM0gogh8GAiHghwErrSLhugIqzRC+tk= -github.com/nats-io/nats-server/v2 v2.10.14/go.mod h1:a0TwOVBJZz6Hwv7JH2E4ONdpyFk9do0C18TEwxnHdRk= +github.com/nats-io/jwt/v2 v2.5.6 h1:Cp618+z4q042sWqHiSoIHFT08OZtAskui0hTmRfmGGQ= +github.com/nats-io/jwt/v2 v2.5.6/go.mod h1:ZdWS1nZa6WMZfFwwgpEaqBV8EPGVgOTDHN/wTbz0Y5A= +github.com/nats-io/nats-server/v2 v2.10.15 h1:O/l+ZT91ltMiiRJKjWLQJcGg7ypzjlb/bC5bFIRVw3M= +github.com/nats-io/nats-server/v2 v2.10.15/go.mod h1:ul+pGt5I7e4U+nI09ZFDG4vqM+6Ce2Tou7UbVSnLiIw= github.com/nats-io/nats.go v1.34.1 h1:syWey5xaNHZgicYBemv0nohUPPmaLteiBEUT6Q5+F/4= github.com/nats-io/nats.go v1.34.1/go.mod h1:Ubdu4Nh9exXdSz0RVWRFBbRfrbSxOYd26oF0wkWclB8= github.com/nats-io/nkeys v0.4.7 h1:RwNJbbIdYCoClSDNY7QVKZlyb/wfT6ugvFCiKy6vDvI= diff --git a/vendor/github.com/nats-io/jwt/v2/signingkeys.go b/vendor/github.com/nats-io/jwt/v2/signingkeys.go index 67af18e1ff..a997cbb1c0 100644 --- a/vendor/github.com/nats-io/jwt/v2/signingkeys.go +++ b/vendor/github.com/nats-io/jwt/v2/signingkeys.go @@ -1,5 +1,5 @@ /* - * Copyright 2020 The NATS Authors + * Copyright 2020-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 @@ -67,10 +67,11 @@ func (t *ScopeType) UnmarshalJSON(b []byte) error { } type UserScope struct { - Kind ScopeType `json:"kind"` - Key string `json:"key"` - Role string `json:"role"` - Template UserPermissionLimits `json:"template"` + Kind ScopeType `json:"kind"` + Key string `json:"key"` + Role string `json:"role"` + Template UserPermissionLimits `json:"template"` + Description string `json:"description"` } func NewUserScope() *UserScope { diff --git a/vendor/github.com/nats-io/nats-server/v2/conf/lex.go b/vendor/github.com/nats-io/nats-server/v2/conf/lex.go index 013b883866..278fab0406 100644 --- a/vendor/github.com/nats-io/nats-server/v2/conf/lex.go +++ b/vendor/github.com/nats-io/nats-server/v2/conf/lex.go @@ -1,4 +1,4 @@ -// Copyright 2013-2018 The NATS Authors +// Copyright 2013-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 @@ -156,7 +156,6 @@ func (lx *lexer) pop() stateFn { func (lx *lexer) emit(typ itemType) { val := strings.Join(lx.stringParts, "") + lx.input[lx.start:lx.pos] - // Position of item in line where it started. pos := lx.pos - lx.ilstart - len(val) lx.items <- item{typ, val, lx.line, pos} @@ -263,7 +262,8 @@ func lexTop(lx *lexer) stateFn { switch r { case topOptStart: - return lexSkip(lx, lexTop) + lx.push(lexTop) + return lexSkip(lx, lexBlockStart) case commentHashStart: lx.push(lexTop) return lexCommentStart @@ -318,6 +318,105 @@ func lexTopValueEnd(lx *lexer) stateFn { "comment or EOF, but got '%v' instead.", r) } +func lexBlockStart(lx *lexer) stateFn { + r := lx.next() + if unicode.IsSpace(r) { + return lexSkip(lx, lexBlockStart) + } + + switch r { + case topOptStart: + lx.push(lexBlockEnd) + return lexSkip(lx, lexBlockStart) + case topOptTerm: + lx.ignore() + return lx.pop() + case commentHashStart: + lx.push(lexBlockEnd) + return lexCommentStart + case commentSlashStart: + rn := lx.next() + if rn == commentSlashStart { + lx.push(lexBlockEnd) + return lexCommentStart + } + lx.backup() + fallthrough + case eof: + if lx.pos > lx.start { + return lx.errorf("Unexpected EOF.") + } + lx.emit(itemEOF) + return nil + } + + // At this point, the only valid item can be a key, so we back up + // and let the key lexer do the rest. + lx.backup() + lx.push(lexBlockValueEnd) + return lexKeyStart +} + +// lexBlockValueEnd is entered whenever a block-level value has been consumed. +// It must see only whitespace, and will turn back to lexBlockStart upon a new line. +// If it sees EOF, it will quit the lexer successfully. +func lexBlockValueEnd(lx *lexer) stateFn { + r := lx.next() + switch { + case r == commentHashStart: + // a comment will read to a new line for us. + lx.push(lexBlockValueEnd) + return lexCommentStart + case r == commentSlashStart: + rn := lx.next() + if rn == commentSlashStart { + lx.push(lexBlockValueEnd) + return lexCommentStart + } + lx.backup() + fallthrough + case isWhitespace(r): + return lexBlockValueEnd + case isNL(r) || r == optValTerm || r == topOptValTerm: + lx.ignore() + return lexBlockStart + case r == topOptTerm: + lx.backup() + return lexBlockEnd + } + return lx.errorf("Expected a block-level value to end with a new line, "+ + "comment or EOF, but got '%v' instead.", r) +} + +// lexBlockEnd is entered whenever a block-level value has been consumed. +// It must see only whitespace, and will turn back to lexTop upon a "}". +func lexBlockEnd(lx *lexer) stateFn { + r := lx.next() + switch { + case r == commentHashStart: + // a comment will read to a new line for us. + lx.push(lexBlockStart) + return lexCommentStart + case r == commentSlashStart: + rn := lx.next() + if rn == commentSlashStart { + lx.push(lexBlockStart) + return lexCommentStart + } + lx.backup() + fallthrough + case isNL(r) || isWhitespace(r): + return lexBlockEnd + case r == optValTerm || r == topOptValTerm: + lx.ignore() + return lexBlockStart + case r == topOptTerm: + lx.ignore() + return lx.pop() + } + return lx.errorf("Expected a block-level to end with a '}', but got '%v' instead.", r) +} + // lexKeyStart consumes a key name up until the first non-whitespace character. // lexKeyStart will ignore whitespace. It will also eat enclosing quotes. func lexKeyStart(lx *lexer) stateFn { diff --git a/vendor/github.com/nats-io/nats-server/v2/server/accounts.go b/vendor/github.com/nats-io/nats-server/v2/server/accounts.go index 83959f2f97..2f80b60621 100644 --- a/vendor/github.com/nats-io/nats-server/v2/server/accounts.go +++ b/vendor/github.com/nats-io/nats-server/v2/server/accounts.go @@ -82,7 +82,7 @@ type Account struct { js *jsAccount jsLimits map[string]JetStreamAccountLimits limits - expired bool + expired atomic.Bool incomplete bool signingKeys map[string]jwt.Scope extAuth *jwt.ExternalAuthorization @@ -759,7 +759,7 @@ func (a *Account) selectMappedSubject(dest string) (string, bool) { return dest, false } - a.mu.Lock() + a.mu.RLock() // In case we have to tokenize for subset matching. tsa := [32]string{} tts := tsa[:0] @@ -790,7 +790,7 @@ func (a *Account) selectMappedSubject(dest string) (string, bool) { } if m == nil { - a.mu.Unlock() + a.mu.RUnlock() return dest, false } @@ -829,7 +829,7 @@ func (a *Account) selectMappedSubject(dest string) (string, bool) { } } - a.mu.Unlock() + a.mu.RUnlock() return ndest, true } @@ -2189,7 +2189,7 @@ const ( // This is where all service export responses are handled. func (a *Account) processServiceImportResponse(sub *subscription, c *client, _ *Account, subject, reply string, msg []byte) { a.mu.RLock() - if a.expired || len(a.exports.responses) == 0 { + if a.expired.Load() || len(a.exports.responses) == 0 { a.mu.RUnlock() return } @@ -2664,7 +2664,7 @@ func (a *Account) getWildcardServiceExport(from string) *serviceExport { // These are import stream specific versions for when an activation expires. func (a *Account) streamActivationExpired(exportAcc *Account, subject string) { a.mu.RLock() - if a.expired || a.imports.streams == nil { + if a.expired.Load() || a.imports.streams == nil { a.mu.RUnlock() return } @@ -2699,7 +2699,7 @@ func (a *Account) streamActivationExpired(exportAcc *Account, subject string) { // These are import service specific versions for when an activation expires. func (a *Account) serviceActivationExpired(subject string) { a.mu.RLock() - if a.expired || a.imports.services == nil { + if a.expired.Load() || a.imports.services == nil { a.mu.RUnlock() return } @@ -2960,18 +2960,13 @@ func (a *Account) checkServiceImportAuthorizedNoLock(account *Account, subject s // IsExpired returns expiration status. func (a *Account) IsExpired() bool { - a.mu.RLock() - exp := a.expired - a.mu.RUnlock() - return exp + return a.expired.Load() } // Called when an account has expired. func (a *Account) expiredTimeout() { // Mark expired first. - a.mu.Lock() - a.expired = true - a.mu.Unlock() + a.expired.Store(true) // Collect the clients and expire them. cs := a.getClients() @@ -3016,17 +3011,17 @@ func (a *Account) checkExpiration(claims *jwt.ClaimsData) { a.clearExpirationTimer() if claims.Expires == 0 { - a.expired = false + a.expired.Store(false) return } tn := time.Now().Unix() if claims.Expires <= tn { - a.expired = true + a.expired.Store(true) return } expiresAt := time.Duration(claims.Expires - tn) a.setExpirationTimer(expiresAt * time.Second) - a.expired = false + a.expired.Store(false) } // hasIssuer returns true if the issuer matches the account @@ -3072,9 +3067,9 @@ func (s *Server) SetAccountResolver(ar AccountResolver) { // AccountResolver returns the registered account resolver. func (s *Server) AccountResolver() AccountResolver { - s.mu.Lock() + s.mu.RLock() ar := s.accResolver - s.mu.Unlock() + s.mu.RUnlock() return ar } diff --git a/vendor/github.com/nats-io/nats-server/v2/server/client.go b/vendor/github.com/nats-io/nats-server/v2/server/client.go index cd24cc8b44..7171375e44 100644 --- a/vendor/github.com/nats-io/nats-server/v2/server/client.go +++ b/vendor/github.com/nats-io/nats-server/v2/server/client.go @@ -474,8 +474,8 @@ func (rcf readCacheFlag) isSet(c readCacheFlag) bool { } const ( - defaultMaxPerAccountCacheSize = 4096 - defaultPrunePerAccountCacheSize = 256 + defaultMaxPerAccountCacheSize = 8192 + defaultPrunePerAccountCacheSize = 1024 defaultClosedSubsCheckInterval = 5 * time.Minute ) @@ -2349,24 +2349,11 @@ func (c *client) generateClientInfoJSON(info Info) []byte { info.MaxPayload = c.mpay if c.isWebsocket() { info.ClientConnectURLs = info.WSConnectURLs - if c.srv != nil { // Otherwise lame duck info can panic - c.srv.websocket.mu.RLock() - info.TLSAvailable = c.srv.websocket.tls - if c.srv.websocket.tls && c.srv.websocket.server != nil { - if tc := c.srv.websocket.server.TLSConfig; tc != nil { - info.TLSRequired = !tc.InsecureSkipVerify - } - } - if c.srv.websocket.listener != nil { - laddr := c.srv.websocket.listener.Addr().String() - if h, p, err := net.SplitHostPort(laddr); err == nil { - if p, err := strconv.Atoi(p); err == nil { - info.Host = h - info.Port = p - } - } - } - c.srv.websocket.mu.RUnlock() + // Otherwise lame duck info can panic + if c.srv != nil { + ws := &c.srv.websocket + info.TLSAvailable, info.TLSRequired = ws.tls, ws.tls + info.Host, info.Port = ws.host, ws.port } } info.WSConnectURLs = nil @@ -4909,6 +4896,23 @@ func adjustPingInterval(kind int, d time.Duration) time.Duration { return d } +// This is used when a connection cannot yet start to send PINGs because +// the remote would not be able to handle them (case of compression, +// or outbound gateway, etc...), but we still want to close the connection +// if the timer has not been reset by the time we reach the time equivalent +// to have sent the max number of pings. +// +// Lock should be held +func (c *client) watchForStaleConnection(pingInterval time.Duration, pingMax int) { + c.ping.tmr = time.AfterFunc(pingInterval*time.Duration(pingMax+1), func() { + c.mu.Lock() + c.Debugf("Stale Client Connection - Closing") + c.enqueueProto([]byte(fmt.Sprintf(errProto, "Stale Connection"))) + c.mu.Unlock() + c.closeConnection(StaleConnection) + }) +} + // Lock should be held func (c *client) setPingTimer() { if c.srv == nil { @@ -5436,13 +5440,13 @@ func (c *client) getAccAndResultFromCache() (*Account, *SublistResult) { // Match against the account sublist. r = sl.Match(string(c.pa.subject)) - // Store in our cache - c.in.pacache[string(c.pa.pacache)] = &perAccountCache{acc, r, atomic.LoadUint64(&sl.genid)} - // Check if we need to prune. - if len(c.in.pacache) > maxPerAccountCacheSize { + if len(c.in.pacache) >= maxPerAccountCacheSize { c.prunePerAccountCache() } + + // Store in our cache,make sure to do so after we prune. + c.in.pacache[string(c.pa.pacache)] = &perAccountCache{acc, r, atomic.LoadUint64(&sl.genid)} } return acc, r } diff --git a/vendor/github.com/nats-io/nats-server/v2/server/const.go b/vendor/github.com/nats-io/nats-server/v2/server/const.go index 33e47159ee..5e20f301e2 100644 --- a/vendor/github.com/nats-io/nats-server/v2/server/const.go +++ b/vendor/github.com/nats-io/nats-server/v2/server/const.go @@ -41,7 +41,7 @@ var ( const ( // VERSION is the current version for the server. - VERSION = "2.10.14" + VERSION = "2.10.15" // PROTO is the currently supported protocol. // 0 was the original diff --git a/vendor/github.com/nats-io/nats-server/v2/server/consumer.go b/vendor/github.com/nats-io/nats-server/v2/server/consumer.go index adc81d4b30..d75f6bb66a 100644 --- a/vendor/github.com/nats-io/nats-server/v2/server/consumer.go +++ b/vendor/github.com/nats-io/nats-server/v2/server/consumer.go @@ -345,6 +345,7 @@ type consumer struct { rdq []uint64 rdqi avl.SequenceSet rdc map[uint64]uint64 + replies map[uint64]string maxdc uint64 waiting *waitQueue cfg ConsumerConfig @@ -423,8 +424,7 @@ const ( // JsDeleteWaitTimeDefault is the default amount of time we will wait for non-durable // consumers to be in an inactive state before deleting them. JsDeleteWaitTimeDefault = 5 * time.Second - // JsFlowControlMaxPending specifies default pending bytes during flow control that can be - // outstanding. + // JsFlowControlMaxPending specifies default pending bytes during flow control that can be outstanding. JsFlowControlMaxPending = 32 * 1024 * 1024 // JsDefaultMaxAckPending is set for consumers with explicit ack that do not set the max ack pending. JsDefaultMaxAckPending = 1000 @@ -1143,23 +1143,19 @@ func (o *consumer) clearNode() { // IsLeader will return if we are the current leader. func (o *consumer) IsLeader() bool { - o.mu.RLock() - defer o.mu.RUnlock() return o.isLeader() } // Lock should be held. func (o *consumer) isLeader() bool { - if o.node != nil { - return o.node.Leader() - } - return true + return o.leader.Load() } func (o *consumer) setLeader(isLeader bool) { o.mu.RLock() mset, closed := o.mset, o.closed movingToClustered := o.node != nil && o.pch == nil + movingToNonClustered := o.node == nil && o.pch != nil wasLeader := o.leader.Swap(isLeader) o.mu.RUnlock() @@ -1183,6 +1179,17 @@ func (o *consumer) setLeader(isLeader bool) { } } o.mu.Unlock() + } else if movingToNonClustered { + // We are moving from clustered to non-clustered now. + // Set pch to nil so if we scale back up we will recreate the loopAndForward from above. + o.mu.Lock() + pch := o.pch + o.pch = nil + select { + case pch <- struct{}{}: + default: + } + o.mu.Unlock() } return } @@ -1191,9 +1198,6 @@ func (o *consumer) setLeader(isLeader bool) { s, jsa, stream, lseq := mset.srv, mset.jsa, mset.cfg.Name, mset.lseq mset.mu.RUnlock() - // Register as a leader with our parent stream. - mset.setConsumerAsLeader(o) - o.mu.Lock() o.rdq = nil o.rdqi.Empty() @@ -1365,13 +1369,10 @@ func (o *consumer) setLeader(isLeader bool) { // If we were the leader make sure to drain queued up acks. if wasLeader { o.ackMsgs.drain() + // Also remove any pending replies since we should not be the one to respond at this point. + o.replies = nil } o.mu.Unlock() - - // Unregister as a leader with our parent stream. - if mset != nil { - mset.removeConsumerAsLeader(o) - } } } @@ -1480,15 +1481,15 @@ func (o *consumer) setCreatedTime(created time.Time) { // that, but in the absence of local interest and presence of gateways or service imports we need // to check those as well. func (o *consumer) hasDeliveryInterest(localInterest bool) bool { - o.mu.Lock() + o.mu.RLock() mset := o.mset if mset == nil { - o.mu.Unlock() + o.mu.RUnlock() return false } acc := o.acc deliver := o.cfg.DeliverSubject - o.mu.Unlock() + o.mu.RUnlock() if localInterest { return true @@ -1920,6 +1921,13 @@ func (o *consumer) updateConfig(cfg *ConsumerConfig) error { // Allowed but considered no-op, [Description, SampleFrequency, MaxWaiting, HeadersOnly] o.cfg = *cfg + // Cleanup messages that lost interest. + if o.retention == InterestPolicy { + o.mu.Unlock() + o.cleanupNoInterestMessages(o.mset, false) + o.mu.Lock() + } + // Re-calculate num pending on update. o.streamNumPending() @@ -1962,9 +1970,9 @@ func configsEqualSansDelivery(a, b ConsumerConfig) bool { // Helper to send a reply to an ack. func (o *consumer) sendAckReply(subj string) { - o.mu.Lock() - defer o.mu.Unlock() - o.sendAdvisory(subj, nil) + o.mu.RLock() + defer o.mu.RUnlock() + o.outq.sendMsg(subj, nil) } type jsAckMsg struct { @@ -2022,9 +2030,11 @@ func (o *consumer) processAck(subject, reply string, hdr int, rmsg []byte) { switch { case len(msg) == 0, bytes.Equal(msg, AckAck), bytes.Equal(msg, AckOK): - o.processAckMsg(sseq, dseq, dc, true) + o.processAckMsg(sseq, dseq, dc, reply, true) + // We handle replies for acks in updateAcks + skipAckReply = true case bytes.HasPrefix(msg, AckNext): - o.processAckMsg(sseq, dseq, dc, true) + o.processAckMsg(sseq, dseq, dc, _EMPTY_, true) o.processNextMsgRequest(reply, msg[len(AckNext):]) skipAckReply = true case bytes.HasPrefix(msg, AckNak): @@ -2036,7 +2046,9 @@ func (o *consumer) processAck(subject, reply string, hdr int, rmsg []byte) { if buf := msg[len(AckTerm):]; len(buf) > 0 { reason = string(bytes.TrimSpace(buf)) } - o.processTerm(sseq, dseq, dc, reason) + o.processTerm(sseq, dseq, dc, reason, reply) + // We handle replies for acks in updateAcks + skipAckReply = true } // Ack the ack if requested. @@ -2071,6 +2083,13 @@ func (o *consumer) updateSkipped(seq uint64) { } func (o *consumer) loopAndForwardProposals(qch chan struct{}) { + // On exit make sure we nil out pch. + defer func() { + o.mu.Lock() + o.pch = nil + o.mu.Unlock() + }() + o.mu.RLock() node, pch := o.node, o.pch o.mu.RUnlock() @@ -2081,7 +2100,7 @@ func (o *consumer) loopAndForwardProposals(qch chan struct{}) { forwardProposals := func() error { o.mu.Lock() - if o.node != node || node.State() != Leader { + if o.node == nil || o.node.State() != Leader { o.mu.Unlock() return errors.New("no longer leader") } @@ -2168,8 +2187,17 @@ func (o *consumer) updateDelivered(dseq, sseq, dc uint64, ts int64) { o.ldt = time.Now() } +// Used to remember a pending ack reply in a replicated consumer. // Lock should be held. -func (o *consumer) updateAcks(dseq, sseq uint64) { +func (o *consumer) addAckReply(sseq uint64, reply string) { + if o.replies == nil { + o.replies = make(map[uint64]string) + } + o.replies[sseq] = reply +} + +// Lock should be held. +func (o *consumer) updateAcks(dseq, sseq uint64, reply string) { if o.node != nil { // Inline for now, use variable compression. var b [2*binary.MaxVarintLen64 + 1]byte @@ -2178,8 +2206,15 @@ func (o *consumer) updateAcks(dseq, sseq uint64) { n += binary.PutUvarint(b[n:], dseq) n += binary.PutUvarint(b[n:], sseq) o.propose(b[:n]) + if reply != _EMPTY_ { + o.addAckReply(sseq, reply) + } } else if o.store != nil { o.store.UpdateAcks(dseq, sseq) + if reply != _EMPTY_ { + // Already locked so send direct. + o.outq.sendMsg(reply, nil) + } } // Update activity. o.lat = time.Now() @@ -2369,9 +2404,9 @@ func (o *consumer) processNak(sseq, dseq, dc uint64, nak []byte) { } // Process a TERM -func (o *consumer) processTerm(sseq, dseq, dc uint64, reason string) { +func (o *consumer) processTerm(sseq, dseq, dc uint64, reason, reply string) { // Treat like an ack to suppress redelivery. - o.processAckMsg(sseq, dseq, dc, false) + o.processAckMsg(sseq, dseq, dc, reply, false) o.mu.Lock() defer o.mu.Unlock() @@ -2474,6 +2509,7 @@ func (o *consumer) applyState(state *ConsumerState) { // 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 + // If normal is lower than this just use that. if o.cfg.AckWait < delay { delay = o.ackWait(0) @@ -2699,7 +2735,7 @@ func (o *consumer) sampleAck(sseq, dseq, dc uint64) { o.sendAdvisory(o.ackEventT, j) } -func (o *consumer) processAckMsg(sseq, dseq, dc uint64, doSample bool) { +func (o *consumer) processAckMsg(sseq, dseq, dc uint64, reply string, doSample bool) { o.mu.Lock() if o.closed { o.mu.Unlock() @@ -2739,12 +2775,12 @@ func (o *consumer) processAckMsg(sseq, dseq, dc uint64, doSample bool) { } } } - // If nothing left set to current delivered. + // If nothing left set consumer to current delivered. + // Do not update stream. if len(o.pending) == 0 { - o.adflr, o.asflr = o.dseq-1, o.sseq-1 + o.adflr = o.dseq - 1 } } - // We do these regardless. delete(o.rdc, sseq) o.removeFromRedeliverQueue(sseq) case AckAll: @@ -2770,7 +2806,7 @@ func (o *consumer) processAckMsg(sseq, dseq, dc uint64, doSample bool) { } // Update underlying store. - o.updateAcks(dseq, sseq) + o.updateAcks(dseq, sseq, reply) clustered := o.node != nil @@ -3356,15 +3392,9 @@ func (o *consumer) incDeliveryCount(sseq uint64) uint64 { // Lock should be held. func (o *consumer) decDeliveryCount(sseq uint64) { if o.rdc == nil { - return - } - if dc, ok := o.rdc[sseq]; ok { - if dc == 1 { - delete(o.rdc, sseq) - } else { - o.rdc[sseq] -= 1 - } + o.rdc = make(map[uint64]uint64) } + o.rdc[sseq] -= 1 } // send a delivery exceeded advisory. @@ -3512,21 +3542,23 @@ func (o *consumer) getNextMsg() (*jsPubMsg, uint64, error) { // Grab next message applicable to us. // We will unlock here in case lots of contention, e.g. WQ. + filters, subjf, fseq := o.filters, o.subjf, o.sseq 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) + if filters != nil { + sm, sseq, err = store.LoadNextMsgMulti(filters, fseq, &pmsg.StoreMsg) + } else if subjf != nil { // Means single filtered subject since o.filters means > 1. + filter, wc := subjf[0].subject, subjf[0].hasWildcard + sm, sseq, err = store.LoadNextMsg(filter, wc, fseq, &pmsg.StoreMsg) } else { // No filter here. - sm, sseq, err = store.LoadNextMsg(_EMPTY_, false, o.sseq, &pmsg.StoreMsg) + sm, sseq, err = store.LoadNextMsg(_EMPTY_, false, fseq, &pmsg.StoreMsg) } if sm == nil { pmsg.returnToPool() pmsg = nil } + // Re-acquire lock. o.mu.Lock() // Check if we should move our o.sseq. if sseq >= o.sseq { @@ -3666,7 +3698,7 @@ func (o *consumer) checkAckFloor() { o.mu.RUnlock() // If it was pending for us, get rid of it. if isPending { - o.processTerm(seq, p.Sequence, rdc, ackTermLimitsReason) + o.processTerm(seq, p.Sequence, rdc, ackTermLimitsReason, _EMPTY_) } } } else if numPending > 0 { @@ -3691,7 +3723,7 @@ func (o *consumer) checkAckFloor() { for i := 0; i < len(toTerm); i += 3 { seq, dseq, rdc := toTerm[i], toTerm[i+1], toTerm[i+2] - o.processTerm(seq, dseq, rdc, ackTermLimitsReason) + o.processTerm(seq, dseq, rdc, ackTermLimitsReason, _EMPTY_) } } @@ -3705,9 +3737,12 @@ func (o *consumer) checkAckFloor() { } // If we are here, and this should be rare, we still are off with our ack floor. + // We will make sure we are not doing un-necessary work here if only off by a bit + // since this could be normal for a high activity wq or stream. // We will set it explicitly to 1 behind our current lowest in pending, or if // pending is empty, to our current delivered -1. - if o.asflr < ss.FirstSeq-1 { + const minOffThreshold = 50 + if o.asflr < ss.FirstSeq-minOffThreshold { var psseq, pdseq uint64 for seq, p := range o.pending { if psseq == 0 || seq < psseq { @@ -3719,7 +3754,7 @@ func (o *consumer) checkAckFloor() { psseq, pdseq = o.sseq-1, o.dseq-1 // If still not adjusted. if psseq < ss.FirstSeq-1 { - psseq, pdseq = ss.FirstSeq-1, ss.FirstSeq-1 + psseq = ss.FirstSeq - 1 } } else { // Since this was set via the pending, we should not include @@ -3735,6 +3770,7 @@ func (o *consumer) processInboundAcks(qch chan struct{}) { o.mu.RLock() s, mset := o.srv, o.mset hasInactiveThresh := o.cfg.InactiveThreshold > 0 + o.mu.RUnlock() if s == nil || mset == nil { @@ -3873,7 +3909,7 @@ func (o *consumer) loopAndGatherMsgs(qch chan struct{}) { o.mu.Lock() // consumer is closed when mset is set to nil. - if o.mset == nil { + if o.closed || o.mset == nil { o.mu.Unlock() return } @@ -3948,9 +3984,17 @@ func (o *consumer) loopAndGatherMsgs(qch chan struct{}) { } } else { // We will redo this one as long as this is not a redelivery. - if dc == 1 { + // Need to also test that this is not going backwards since if + // we fail to deliver we can end up here from rdq but we do not + // want to decrement o.sseq if that is the case. + if dc == 1 && pmsg.seq == o.sseq-1 { o.sseq-- o.npc++ + } else if !o.onRedeliverQueue(pmsg.seq) { + // We are not on the rdq so decrement the delivery count + // and add it back. + o.decDeliveryCount(pmsg.seq) + o.addToRedeliverQueue(pmsg.seq) } pmsg.returnToPool() goto waitForMsgs @@ -3995,7 +4039,8 @@ func (o *consumer) loopAndGatherMsgs(qch chan struct{}) { // If given request fulfilled batch size, but there are still pending bytes, send information about it. if wrn <= 0 && wrb > 0 { - o.outq.send(newJSPubMsg(dsubj, _EMPTY_, _EMPTY_, fmt.Appendf(nil, JsPullRequestRemainingBytesT, JSPullRequestPendingMsgs, wrn, JSPullRequestPendingBytes, wrb), nil, nil, 0)) + msg := fmt.Appendf(nil, JsPullRequestRemainingBytesT, JSPullRequestPendingMsgs, wrn, JSPullRequestPendingBytes, wrb) + o.outq.send(newJSPubMsg(dsubj, _EMPTY_, _EMPTY_, msg, nil, nil, 0)) } // Reset our idle heartbeat timer if set. if hb != nil { @@ -4260,7 +4305,7 @@ func (o *consumer) deliverMsg(dsubj, ackReply string, pmsg *jsPubMsg, dc uint64, if o.node == nil || o.cfg.Direct { mset.ackq.push(seq) } else { - o.updateAcks(dseq, seq) + o.updateAcks(dseq, seq, _EMPTY_) } } } @@ -4386,6 +4431,9 @@ func (o *consumer) didNotDeliver(seq uint64, subj string) { o.active = false checkDeliveryInterest = true } else if o.pending != nil { + // Good chance we did not deliver because no interest so force a check. + o.processWaiting(false) + // If it is still there credit it back. o.creditWaitingRequest(subj) // pull mode and we have pending. if _, ok := o.pending[seq]; ok { @@ -4394,7 +4442,9 @@ func (o *consumer) didNotDeliver(seq uint64, subj string) { // we know it was not delivered if !o.onRedeliverQueue(seq) { o.addToRedeliverQueue(seq) - o.signalNewMessages() + if !o.waiting.isEmpty() { + o.signalNewMessages() + } } } } @@ -4523,6 +4573,10 @@ func (o *consumer) checkPending() { if len(o.cfg.BackOff) > 0 { // This is ok even if o.rdc is nil, we would get dc == 0, which is what we want. dc := int(o.rdc[seq]) + if dc < 0 { + // Prevent consumer backoff from going backwards. + dc = 0 + } // This will be the index for the next backoff, will set to last element if needed. nbi := dc + 1 if dc+1 >= len(o.cfg.BackOff) { @@ -5075,7 +5129,6 @@ func (o *consumer) stopWithFlags(dflag, sdflag, doSignal, advisory bool) error { if dflag { ca = o.ca } - sigSubs := o.sigSubs js := o.js o.mu.Unlock() @@ -5092,45 +5145,15 @@ func (o *consumer) stopWithFlags(dflag, sdflag, doSignal, advisory bool) error { var rp RetentionPolicy if mset != nil { - if len(sigSubs) > 0 { - mset.removeConsumerAsLeader(o) - } mset.mu.Lock() mset.removeConsumer(o) rp = mset.cfg.Retention mset.mu.Unlock() } - // We need to optionally remove all messages since we are interest based retention. - // We will do this consistently on all replicas. Note that if in clustered mode the - // non-leader consumers will need to restore state first. + // Cleanup messages that lost interest. if dflag && rp == InterestPolicy { - state := mset.state() - stop := state.LastSeq - o.mu.Lock() - if !o.isLeader() { - o.readStoredState(stop) - } - start := o.asflr - o.mu.Unlock() - // Make sure we start at worst with first sequence in the stream. - if start < state.FirstSeq { - start = state.FirstSeq - } - - var rmseqs []uint64 - mset.mu.Lock() - for seq := start; seq <= stop; seq++ { - if mset.noInterest(seq, o) { - rmseqs = append(rmseqs, seq) - } - } - mset.mu.Unlock() - - // These can be removed. - for _, seq := range rmseqs { - mset.store.RemoveMsg(seq) - } + o.cleanupNoInterestMessages(mset, true) } // Cluster cleanup. @@ -5173,6 +5196,46 @@ func (o *consumer) stopWithFlags(dflag, sdflag, doSignal, advisory bool) error { return err } +// We need to optionally remove all messages since we are interest based retention. +// We will do this consistently on all replicas. Note that if in clustered mode the non-leader +// consumers will need to restore state first. +// ignoreInterest marks whether the consumer should be ignored when determining interest. +// No lock held on entry. +func (o *consumer) cleanupNoInterestMessages(mset *stream, ignoreInterest bool) { + state := mset.state() + stop := state.LastSeq + o.mu.Lock() + if !o.isLeader() { + o.readStoredState(stop) + } + start := o.asflr + o.mu.Unlock() + // Make sure we start at worst with first sequence in the stream. + if start < state.FirstSeq { + start = state.FirstSeq + } + + // Consumer's interests are ignored by default. If we should not ignore interest, unset. + co := o + if !ignoreInterest { + co = nil + } + + var rmseqs []uint64 + mset.mu.Lock() + for seq := start; seq <= stop; seq++ { + if mset.noInterest(seq, co) { + rmseqs = append(rmseqs, seq) + } + } + mset.mu.Unlock() + + // These can be removed. + for _, seq := range rmseqs { + mset.store.RemoveMsg(seq) + } +} + // Check that we do not form a cycle by delivering to a delivery subject // that is part of the interest group. func deliveryFormsCycle(cfg *StreamConfig, deliverySubject string) bool { @@ -5227,7 +5290,7 @@ func (o *consumer) decStreamPending(sseq uint64, subj string) { if wasPending { // 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) + go o.processTerm(sseq, p.Sequence, rdc, ackTermUnackedLimitsReason, _EMPTY_) } } @@ -5263,18 +5326,24 @@ func (o *consumer) signalSubs() []*subscription { } // This is what will be called when our parent stream wants to kick us regarding a new message. -// We know that we are the leader and that this subject matches us by how the parent handles registering -// us with the signaling sublist. +// We know that this subject matches us by how the parent handles registering us with the signaling sublist, +// but we must check if we are leader. // We do need the sequence of the message however and we use the msg as the encoded seq. func (o *consumer) processStreamSignal(_ *subscription, _ *client, _ *Account, subject, _ string, seqb []byte) { - var le = binary.LittleEndian - seq := le.Uint64(seqb) - + // We can get called here now when not leader, so bail fast + // and without acquiring any locks. + if !o.leader.Load() { + return + } o.mu.Lock() defer o.mu.Unlock() if o.mset == nil { return } + + var le = binary.LittleEndian + seq := le.Uint64(seqb) + if seq > o.npf { o.npc++ } diff --git a/vendor/github.com/nats-io/nats-server/v2/server/events.go b/vendor/github.com/nats-io/nats-server/v2/server/events.go index ed3330f4c3..3e257eab77 100644 --- a/vendor/github.com/nats-io/nats-server/v2/server/events.go +++ b/vendor/github.com/nats-io/nats-server/v2/server/events.go @@ -1120,6 +1120,10 @@ func (s *Server) initEventTracking() { optz := &ProfilezEventOptions{} s.zReq(c, reply, hdr, msg, &optz.EventFilterOptions, optz, func() (any, error) { return s.profilez(&optz.ProfilezOptions), nil }) }, + "EXPVARZ": func(sub *subscription, c *client, _ *Account, subject, reply string, hdr, msg []byte) { + optz := &ExpvarzEventOptions{} + s.zReq(c, reply, hdr, msg, &optz.EventFilterOptions, optz, func() (any, error) { return s.expvarz(optz), nil }) + }, } for name, req := range monSrvc { subject = fmt.Sprintf(serverDirectReqSubj, s.info.ID, name) @@ -1788,6 +1792,11 @@ type ProfilezEventOptions struct { EventFilterOptions } +// In the context of system events, ExpvarzEventOptions are options passed to Expvarz +type ExpvarzEventOptions struct { + EventFilterOptions +} + // returns true if the request does NOT apply to this server and can be ignored. // DO NOT hold the server lock when func (s *Server) filterRequest(fOpts *EventFilterOptions) bool { diff --git a/vendor/github.com/nats-io/nats-server/v2/server/filestore.go b/vendor/github.com/nats-io/nats-server/v2/server/filestore.go index 8c296ae016..a71237c61e 100644 --- a/vendor/github.com/nats-io/nats-server/v2/server/filestore.go +++ b/vendor/github.com/nats-io/nats-server/v2/server/filestore.go @@ -52,6 +52,8 @@ type FileStoreConfig struct { BlockSize uint64 // CacheExpire is how long with no activity until we expire the cache. CacheExpire time.Duration + // SubjectStateExpire is how long with no activity until we expire a msg block's subject state. + SubjectStateExpire time.Duration // SyncInterval is how often we sync to disk in the background. SyncInterval time.Duration // SyncAlways is when the stream should sync all data writes. @@ -182,7 +184,6 @@ type fileStore struct { adml int hh hash.Hash64 qch chan struct{} - fch chan struct{} fsld chan struct{} cmu sync.RWMutex cfs []ConsumerStore @@ -218,11 +219,13 @@ type msgBlock struct { lwts int64 llts int64 lrts int64 + lsts int64 llseq uint64 hh hash.Hash64 cache *cache cloads uint64 cexp time.Duration + fexp time.Duration ctmr *time.Timer werr error dmap avl.SequenceSet @@ -297,6 +300,8 @@ const ( defaultSyncInterval = 2 * time.Minute // default idle timeout to close FDs. closeFDsIdle = 30 * time.Second + // default expiration time for mb.fss when idle. + defaultFssExpiration = 10 * time.Second // coalesceMinimum coalesceMinimum = 16 * 1024 // maxFlushWait is maximum we will wait to gather messages to flush. @@ -360,6 +365,9 @@ func newFileStoreWithCreated(fcfg FileStoreConfig, cfg StreamConfig, created tim if fcfg.CacheExpire == 0 { fcfg.CacheExpire = defaultCacheBufferExpiration } + if fcfg.SubjectStateExpire == 0 { + fcfg.SubjectStateExpire = defaultFssExpiration + } if fcfg.SyncInterval == 0 { fcfg.SyncInterval = defaultSyncInterval } @@ -390,7 +398,6 @@ func newFileStoreWithCreated(fcfg FileStoreConfig, cfg StreamConfig, created tim prf: prf, oldprf: oldprf, qch: make(chan struct{}), - fch: make(chan struct{}, 1), fsld: make(chan struct{}), srv: fcfg.srv, } @@ -451,7 +458,6 @@ func newFileStoreWithCreated(fcfg FileStoreConfig, cfg StreamConfig, created tim } // Since we recovered here, make sure to kick ourselves to write out our stream state. fs.dirty++ - defer fs.kickFlushStateLoop() } // Also make sure we get rid of old idx and fss files on return. @@ -525,8 +531,8 @@ func newFileStoreWithCreated(fcfg FileStoreConfig, cfg StreamConfig, created tim // Setup our sync timer. fs.setSyncTimer() - // Spin up the go routine that will write out or full state stream index. - go fs.flushStreamStateLoop(fs.fch, fs.qch, fs.fsld) + // Spin up the go routine that will write out our full state stream index. + go fs.flushStreamStateLoop(fs.qch, fs.fsld) return fs, nil } @@ -548,6 +554,13 @@ func (fs *fileStore) unlockAllMsgBlocks() { } func (fs *fileStore) UpdateConfig(cfg *StreamConfig) error { + start := time.Now() + defer func() { + if took := time.Since(start); took > time.Minute { + fs.warn("UpdateConfig took %v", took.Round(time.Millisecond)) + } + }() + if fs.isClosed() { return ErrStoreClosed } @@ -873,7 +886,14 @@ func (fs *fileStore) noTrackSubjects() bool { // Will init the basics for a message block. func (fs *fileStore) initMsgBlock(index uint32) *msgBlock { - mb := &msgBlock{fs: fs, index: index, cexp: fs.fcfg.CacheExpire, noTrack: fs.noTrackSubjects(), syncAlways: fs.fcfg.SyncAlways} + mb := &msgBlock{ + fs: fs, + index: index, + cexp: fs.fcfg.CacheExpire, + fexp: fs.fcfg.SubjectStateExpire, + noTrack: fs.noTrackSubjects(), + syncAlways: fs.fcfg.SyncAlways, + } mdir := filepath.Join(fs.fcfg.StoreDir, msgDir) mb.mfn = filepath.Join(mdir, fmt.Sprintf(blkScan, index)) @@ -955,16 +975,6 @@ func (mb *msgBlock) ensureLastChecksumLoaded() { copy(mb.lchk[0:], mb.lastChecksum()) } -// Perform a recover but do not update PSIM. -// Lock should be held. -func (fs *fileStore) recoverMsgBlockNoSubjectUpdates(index uint32) (*msgBlock, error) { - psim, tsl := fs.psim, fs.tsl - fs.psim = nil - mb, err := fs.recoverMsgBlock(index) - fs.psim, fs.tsl = psim, tsl - return mb, err -} - // Lock held on entry func (fs *fileStore) recoverMsgBlock(index uint32) (*msgBlock, error) { mb := fs.initMsgBlock(index) @@ -1002,6 +1012,7 @@ func (fs *fileStore) recoverMsgBlock(index uint32) (*msgBlock, error) { file.Close() // Read our index file. Use this as source of truth if possible. + // This not applicable in >= 2.10 servers. Here for upgrade paths from < 2.10. if err := mb.readIndexInfo(); err == nil { // Quick sanity check here. // Note this only checks that the message blk file is not newer then this file, or is empty and we expect empty. @@ -1733,13 +1744,13 @@ func (fs *fileStore) recoverFullState() (rerr error) { // Reverse update of tracking state for this mb, will add new state in below. mstate.Msgs -= mb.msgs mstate.Bytes -= mb.bytes - if nmb, err := fs.recoverMsgBlockNoSubjectUpdates(mb.index); err != nil && !os.IsNotExist(err) { + if nmb, err := fs.recoverMsgBlock(mb.index); err != nil && !os.IsNotExist(err) { fs.warn("Stream state could not recover last msg block") os.Remove(fn) return errCorruptState } else if nmb != nil { fs.adjustAccounting(mb, nmb) - updateTrackingState(&mstate, mb) + updateTrackingState(&mstate, nmb) } } @@ -1796,10 +1807,26 @@ func (fs *fileStore) adjustAccounting(mb, nmb *msgBlock) { } nmb.ensurePerSubjectInfoLoaded() + var smv StoreMsg + + // Need to walk previous messages and undo psim stats. + // We already undid msgs and bytes accounting. + for seq, lseq := atomic.LoadUint64(&mb.first.seq), atomic.LoadUint64(&mb.last.seq); seq <= lseq; seq++ { + // Lookup the message. If an error will be deleted, so can skip. + sm, err := nmb.cacheLookup(seq, &smv) + if err != nil { + continue + } + if len(sm.subj) > 0 && fs.psim != nil { + if info, ok := fs.psim.Find(stringToBytes(sm.subj)); ok { + info.total-- + } + } + } + // Walk only new messages and update accounting at fs level. Any messages that should have // triggered limits exceeded will be handled after the recovery and prior to the stream // being available to the system. - var smv StoreMsg for seq, lseq := atomic.LoadUint64(&mb.last.seq)+1, atomic.LoadUint64(&nmb.last.seq); seq <= lseq; seq++ { // Lookup the message. If an error will be deleted, so can skip. sm, err := nmb.cacheLookup(seq, &smv) @@ -1809,17 +1836,6 @@ func (fs *fileStore) adjustAccounting(mb, nmb *msgBlock) { // Since we found it we just need to adjust fs totals and psim. fs.state.Msgs++ fs.state.Bytes += fileStoreMsgSize(sm.subj, sm.hdr, sm.msg) - if len(sm.subj) > 0 && fs.psim != nil { - if info, ok := fs.psim.Find(stringToBytes(sm.subj)); ok { - info.total++ - if nmb.index > info.lblk { - info.lblk = nmb.index - } - } else { - fs.psim.Insert(stringToBytes(sm.subj), psi{total: 1, fblk: nmb.index, lblk: nmb.index}) - fs.tsl += len(sm.subj) - } - } } // Now check to see if we had a higher first for the recovered state mb vs nmb. @@ -2191,7 +2207,6 @@ func (fs *fileStore) expireMsgsOnRecover() { // If we purged anything, make sure we kick flush state loop. if purged > 0 { fs.dirty++ - fs.kickFlushStateLoop() } } @@ -2273,7 +2288,7 @@ func (mb *msgBlock) firstMatchingMulti(sl *Sublist, start uint64, sm *StoreMsg) } expireOk := seq == lseq && mb.llseq == seq - if r := sl.Match(fsm.subj); len(r.psubs) > 0 { + if sl.HasInterest(fsm.subj) { return fsm, expireOk, nil } // If we are here we did not match, so put the llseq back. @@ -2283,6 +2298,7 @@ func (mb *msgBlock) firstMatchingMulti(sl *Sublist, start uint64, sm *StoreMsg) } // Find the first matching message. +// fs lock should be held. func (mb *msgBlock) firstMatching(filter string, wc bool, start uint64, sm *StoreMsg) (*StoreMsg, bool, error) { mb.mu.Lock() defer mb.mu.Unlock() @@ -2295,6 +2311,8 @@ func (mb *msgBlock) firstMatching(filter string, wc bool, start uint64, sm *Stor mb.loadMsgsWithLock() didLoad = true } + // Mark fss activity. + mb.lsts = time.Now().UnixNano() // If we only have 1 subject currently and it matches our filter we can also set isAll. if !isAll && len(mb.fss) == 1 { @@ -2319,8 +2337,16 @@ func (mb *msgBlock) firstMatching(filter string, wc bool, start uint64, sm *Stor return isSubsetMatchTokenized(tts, fts) } } - // 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)) + + subjs := mb.fs.cfg.Subjects + // If isAll or our single filter matches the filter arg do linear scan. + doLinearScan := isAll || (wc && len(subjs) == 1 && subjs[0] == filter) + // If we do not think we should do a linear scan check how many fss we + // would need to scan vs the full range of the linear walk. Optimize for + // 25th quantile of a match in a linear walk. Filter should be a wildcard. + if !doLinearScan && wc { + doLinearScan = len(mb.fss)*4 > int(lseq-fseq) + } if !doLinearScan { // If we have a wildcard match against all tracked subjects we know about. @@ -2360,7 +2386,7 @@ func (mb *msgBlock) firstMatching(filter string, wc bool, start uint64, sm *Stor // 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. - if len(subs) > int(lseq-fseq) { + if !doLinearScan && len(subs) > int(lseq-fseq) { doLinearScan = true } @@ -2653,6 +2679,8 @@ func (fs *fileStore) SubjectsState(subject string) map[string]SimpleState { mb.loadMsgsWithLock() shouldExpire = true } + // Mark fss activity. + mb.lsts = time.Now().UnixNano() for subj, ss := range mb.fss { if subject == _EMPTY_ || subject == fwcs || subjectIsSubsetMatch(subj, subject) { if ss.firstNeedsUpdate { @@ -2832,6 +2860,9 @@ func (fs *fileStore) NumPending(sseq uint64, filter string, lastPerSubject bool) mb.loadMsgsWithLock() shouldExpire = true } + // Mark fss activity. + mb.lsts = time.Now().UnixNano() + var havePartial bool for subj, ss := range mb.fss { if isMatch(subj) { @@ -2847,6 +2878,7 @@ func (fs *fileStore) NumPending(sseq uint64, filter string, lastPerSubject bool) } } } + // See if we need to scan msgs here. if havePartial { // Make sure we have the cache loaded. @@ -2920,6 +2952,9 @@ func (fs *fileStore) NumPending(sseq uint64, filter string, lastPerSubject bool) mb.loadMsgsWithLock() shouldExpire = true } + // Mark fss activity. + mb.lsts = time.Now().UnixNano() + for subj, ss := range mb.fss { if isMatch(subj) { adjust += ss.Msgs @@ -3088,10 +3123,6 @@ func (fs *fileStore) newMsgBlockForWrite() (*msgBlock, error) { // Add to our list of blocks and mark as last. fs.addMsgBlock(mb) - if fs.dirty > 0 { - fs.kickFlushStateLoop() - } - return mb, nil } @@ -3458,7 +3489,7 @@ func (fs *fileStore) rebuildFirst() { // Optimized helper function to return first sequence. // subj will always be publish subject here, meaning non-wildcard. // We assume a fast check that this subj even exists already happened. -// Lock should be held. +// Write lock should be held. func (fs *fileStore) firstSeqForSubj(subj string) (uint64, error) { if len(fs.blks) == 0 { return 0, nil @@ -3475,16 +3506,25 @@ func (fs *fileStore) firstSeqForSubj(subj string) (uint64, error) { if mb == nil { continue } + // If we need to load msgs here and we need to walk multiple blocks this + // could tie up the upper fs lock, so release while dealing with the block. + fs.mu.Unlock() + mb.mu.Lock() var shouldExpire bool if mb.fssNotLoaded() { // Make sure we have fss loaded. if err := mb.loadMsgsWithLock(); err != nil { mb.mu.Unlock() + // Re-acquire fs lock + fs.mu.Lock() return 0, err } shouldExpire = true } + // Mark fss activity. + mb.lsts = time.Now().UnixNano() + if ss := mb.fss[subj]; ss != nil { // Adjust first if it was not where we thought it should be. if i != start { @@ -3496,6 +3536,8 @@ func (fs *fileStore) firstSeqForSubj(subj string) (uint64, error) { mb.recalculateFirstForSubj(subj, ss.First, ss) } mb.mu.Unlock() + // Re-acquire fs lock + fs.mu.Lock() return ss.First, nil } // If we did not find it and we loaded this msgBlock try to expire as long as not the last. @@ -3504,6 +3546,8 @@ func (fs *fileStore) firstSeqForSubj(subj string) (uint64, error) { mb.tryForceExpireCacheLocked() } mb.mu.Unlock() + // Re-acquire fs lock + fs.mu.Lock() } return 0, nil } @@ -3547,6 +3591,13 @@ func (fs *fileStore) enforceBytesLimit() { // will most likely only be the last one, so can take a more conservative approach. // Lock should be held. func (fs *fileStore) enforceMsgPerSubjectLimit(fireCallback bool) { + start := time.Now() + defer func() { + if took := time.Since(start); took > time.Minute { + fs.warn("enforceMsgPerSubjectLimit took %v", took.Round(time.Millisecond)) + } + }() + maxMsgsPer := uint64(fs.cfg.MaxMsgsPer) // We may want to suppress callbacks from remove during this process @@ -3828,7 +3879,6 @@ func (fs *fileStore) removeMsg(seq uint64, secure, viaLimits, needFSLock bool) ( // TODO(dlc) - This should not be inline, should kick the sync routine. if mb.rbytes > compactMinimum && mb.bytes*2 < mb.rbytes && !isLastBlock { mb.compact() - fs.kickFlushStateLoop() } } @@ -3933,8 +3983,9 @@ func (mb *msgBlock) compact() { if !isDeleted(seq) { // Check for tombstones. if seq&tbit != 0 { - // If we are last mb we should consider to keep these unless the tombstone reflects a seq in this mb. - if mb == mb.fs.lmb && seq < fseq { + seq = seq &^ tbit + // If this entry is for a lower seq than ours then keep around. + if seq < fseq { nbuf = append(nbuf, buf[index:index+rl]...) } } else { @@ -3990,6 +4041,9 @@ func (mb *msgBlock) compact() { return } + // Make sure to sync + mb.needSync = true + // Capture the updated rbytes. mb.rbytes = uint64(len(nbuf)) @@ -4419,9 +4473,16 @@ func (mb *msgBlock) clearCacheAndOffset() { // Lock should be held. func (mb *msgBlock) clearCache() { - if mb.ctmr != nil && mb.fss == nil { - mb.ctmr.Stop() - mb.ctmr = nil + if mb.ctmr != nil { + tsla := mb.sinceLastActivity() + if mb.fss == nil || tsla > mb.fexp { + // Force + mb.fss = nil + mb.ctmr.Stop() + mb.ctmr = nil + } else { + mb.resetCacheExpireTimer(mb.fexp - tsla) + } } if mb.cache == nil { @@ -4486,7 +4547,7 @@ func (mb *msgBlock) tryExpireWriteCache() []byte { // Lock should be held. func (mb *msgBlock) expireCacheLocked() { - if mb.cache == nil { + if mb.cache == nil && mb.fss == nil { if mb.ctmr != nil { mb.ctmr.Stop() mb.ctmr = nil @@ -4694,6 +4755,8 @@ func (mb *msgBlock) writeMsgRecord(rl, seq uint64, subj string, mhdr, msg []byte if err := mb.ensurePerSubjectInfoLoaded(); err != nil { return err } + // Mark fss activity. + mb.lsts = time.Now().UnixNano() if ss := mb.fss[subj]; ss != nil { ss.Msgs++ ss.Last = seq @@ -5094,7 +5157,6 @@ func (fs *fileStore) syncBlocks() { } blks := append([]*msgBlock(nil), fs.blks...) lmb := fs.lmb - syncInterval := fs.fcfg.SyncInterval fs.mu.RUnlock() var markDirty bool @@ -5109,11 +5171,6 @@ func (fs *fileStore) syncBlocks() { if mb.mfd != nil && mb.sinceLastWriteActivity() > closeFDsIdle { mb.dirtyCloseWithRemove(false) } - // Check our fss subject metadata. - // If we have no activity within sync interval remove. - if mb.fssLoaded() && mb.sinceLastActivity() > syncInterval { - mb.fss = nil - } // Check if we should compact here as well. // Do not compact last mb. @@ -5308,6 +5365,8 @@ func (mb *msgBlock) indexCacheBuf(buf []byte) error { mb.fss = make(map[string]*SimpleState) popFss = true } + // Mark fss activity. + mb.lsts = time.Now().UnixNano() lbuf := uint32(len(buf)) var seq uint64 @@ -5580,12 +5639,6 @@ func (mb *msgBlock) fssNotLoaded() bool { return mb.fss == nil && !mb.noTrack } -// Report if we have our fss loaded. -// Lock should be held. -func (mb *msgBlock) fssLoaded() bool { - return mb.fss != nil -} - // Wrap openBlock for the gated semaphore processing. // Lock should be held func (mb *msgBlock) openBlock() (*os.File, error) { @@ -5706,7 +5759,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) + mb.fs.warn("loadBlock error: %v", err) if err == errNoBlkData { if ld, _, err := mb.rebuildStateLocked(); err != nil && ld != nil { // Rebuild fs state too. @@ -5812,6 +5865,7 @@ var ( errUnknownCipher = errors.New("unknown cipher") errNoMainKey = errors.New("encrypted store encountered with no main key") errNoBlkData = errors.New("message block data missing") + errStateTooBig = errors.New("store state too big for optional write") ) const ( @@ -6077,6 +6131,9 @@ func (fs *fileStore) loadLast(subj string, sm *StoreMsg) (lsm *StoreMsg, err err mb.mu.Unlock() return nil, err } + // Mark fss activity. + mb.lsts = time.Now().UnixNano() + var l uint64 // Optimize if subject is not a wildcard. if !wc { @@ -6129,7 +6186,7 @@ func (fs *fileStore) LoadNextMsgMulti(sl *Sublist, start uint64, smp *StoreMsg) if fs.closed { return nil, 0, ErrStoreClosed } - if fs.state.Msgs == 0 { + if fs.state.Msgs == 0 || start > fs.state.LastSeq { return nil, fs.state.LastSeq, ErrStoreEOF } if start < fs.state.FirstSeq { @@ -6163,7 +6220,7 @@ func (fs *fileStore) LoadNextMsg(filter string, wc bool, start uint64, sm *Store if fs.closed { return nil, 0, ErrStoreClosed } - if fs.state.Msgs == 0 { + if fs.state.Msgs == 0 || start > fs.state.LastSeq { return nil, fs.state.LastSeq, ErrStoreEOF } if start < fs.state.FirstSeq { @@ -6325,6 +6382,9 @@ func (mb *msgBlock) sinceLastActivity() time.Duration { if mb.llts > last { last = mb.llts } + if mb.lsts > last { + last = mb.lsts + } return time.Since(time.Unix(0, last).UTC()) } @@ -6633,8 +6693,6 @@ func (fs *fileStore) PurgeEx(subject string, sequence, keep uint64) (purged uint cb := fs.scb fs.mu.Unlock() - fs.kickFlushStateLoop() - if cb != nil { cb(-int64(purged), -int64(bytes), 0, _EMPTY_) } @@ -6827,6 +6885,9 @@ func (fs *fileStore) Compact(seq uint64) (uint64, error) { if smb != fs.lmb { smb.dirtyCloseWithRemove(true) deleted++ + } else { + // Make sure to sync changes. + smb.needSync = true } // Update fs first here as well. fs.state.FirstSeq = atomic.LoadUint64(&smb.last.seq) + 1 @@ -6908,6 +6969,10 @@ SKIP: purged = fs.state.Msgs } fs.state.Msgs -= purged + if fs.state.Msgs == 0 { + fs.state.FirstSeq = fs.state.LastSeq + 1 + fs.state.FirstTime = time.Time{} + } if bytes > fs.state.Bytes { bytes = fs.state.Bytes @@ -6915,7 +6980,6 @@ SKIP: fs.state.Bytes -= bytes fs.dirty++ - fs.kickFlushStateLoop() cb := fs.scb fs.mu.Unlock() @@ -6969,7 +7033,6 @@ func (fs *fileStore) reset() error { // If we purged anything, make sure we kick flush state loop. if purged > 0 { fs.dirty++ - fs.kickFlushStateLoop() } fs.mu.Unlock() @@ -7056,7 +7119,6 @@ func (fs *fileStore) Truncate(seq uint64) error { fs.resetGlobalPerSubjectInfo() fs.dirty++ - fs.kickFlushStateLoop() cb := fs.scb fs.mu.Unlock() @@ -7143,8 +7205,6 @@ func (mb *msgBlock) dirtyCloseWithRemove(remove bool) { mb.ctmr.Stop() mb.ctmr = nil } - // Clear any tracking by subject. - mb.fss = nil // Close cache mb.clearCacheAndOffset() // Quit our loops. @@ -7157,6 +7217,8 @@ func (mb *msgBlock) dirtyCloseWithRemove(remove bool) { mb.mfd = nil } if remove { + // Clear any tracking by subject if we are removing. + mb.fss = nil if mb.mfn != _EMPTY_ { os.Remove(mb.mfn) mb.mfn = _EMPTY_ @@ -7164,8 +7226,6 @@ func (mb *msgBlock) dirtyCloseWithRemove(remove bool) { if mb.kfn != _EMPTY_ { os.Remove(mb.kfn) } - // Since we are removing a block kick the state flusher. - mb.fs.kickFlushStateLoop() } } @@ -7310,6 +7370,8 @@ func (mb *msgBlock) generatePerSubjectInfo() error { if len(mb.fss) > 0 { // Make sure we run the cache expire timer. mb.llts = time.Now().UnixNano() + // Mark fss activity. + mb.lsts = time.Now().UnixNano() mb.startCacheExpireTimer() } return nil @@ -7319,6 +7381,10 @@ func (mb *msgBlock) generatePerSubjectInfo() error { // Lock should be held func (mb *msgBlock) ensurePerSubjectInfoLoaded() error { if mb.fss != nil || mb.noTrack { + if mb.fss != nil { + // Mark fss activity. + mb.lsts = time.Now().UnixNano() + } return nil } if mb.msgs == 0 { @@ -7481,53 +7547,26 @@ const ( fullStateVersion = uint8(1) ) -// This go routine runs and receives kicks to write out our full stream state index. -// This will get kicked when we create a new block or when we delete a block in general. -// This is also called during Stop(). -func (fs *fileStore) flushStreamStateLoop(fch, qch, done chan struct{}) { - // Make sure we do not try to write these out too fast. - const writeThreshold = time.Minute - lastWrite := time.Time{} - - // We will use these to complete the full state write while not doing them too fast. - var dt *time.Timer - var dtc <-chan time.Time - +// This go routine periodically writes out our full stream state index. +func (fs *fileStore) flushStreamStateLoop(qch, done chan struct{}) { + // Signal we are done on exit. defer close(done) + // Make sure we do not try to write these out too fast. + const writeThreshold = 2 * time.Minute + t := time.NewTicker(writeThreshold) + defer t.Stop() + for { select { - case <-fch: - if elapsed := time.Since(lastWrite); elapsed > writeThreshold { - fs.writeFullState() - lastWrite = time.Now() - if dt != nil { - dt.Stop() - dt, dtc = nil, nil - } - } else if dtc == nil { - fireIn := time.Until(lastWrite.Add(writeThreshold)) - if fireIn < 0 { - fireIn = 100 * time.Millisecond - } - dt = time.NewTimer(fireIn) - dtc = dt.C - } - case <-dtc: + case <-t.C: fs.writeFullState() - lastWrite = time.Now() - dt, dtc = nil, nil case <-qch: return } } } -// Kick the flusher. -func (fs *fileStore) kickFlushStateLoop() { - kickFlusher(fs.fch) -} - // Helper since unixnano of zero time undefined. func timestampNormalized(t time.Time) int64 { if t.IsZero() { @@ -7536,6 +7575,17 @@ func timestampNormalized(t time.Time) int64 { return t.UnixNano() } +// writeFullState will proceed to write the full meta state iff not complex and time consuming. +// Since this is for quick recovery it is optional and should not block/stall normal operations. +func (fs *fileStore) writeFullState() error { + return fs._writeFullState(false) +} + +// forceWriteFullState will proceed to write the full meta state. This should only be called by stop() +func (fs *fileStore) forceWriteFullState() error { + return fs._writeFullState(true) +} + // This will write the full binary state for the stream. // This plus everything new since last hash will be the total recovered state. // This state dump will have the following. @@ -7543,13 +7593,34 @@ func timestampNormalized(t time.Time) int64 { // 2. PSIM - Per Subject Index Map - Tracks first and last blocks with subjects present. // 3. MBs - Index, Bytes, First and Last Sequence and Timestamps, and the deleted map (avl.seqset). // 4. Last block index and hash of record inclusive to this stream state. -func (fs *fileStore) writeFullState() error { +func (fs *fileStore) _writeFullState(force bool) error { + start := time.Now() fs.mu.Lock() if fs.closed || fs.dirty == 0 { fs.mu.Unlock() return nil } + // For calculating size and checking time costs for non forced calls. + numSubjects := fs.numSubjects() + + // If we are not being forced to write out our state, check the complexity for time costs as to not + // block or stall normal operations. + // We will base off of number of subjects and interior deletes. A very large number of msg blocks could also + // be used, but for next server version will redo all meta handling to be disk based. So this is temporary. + if !force { + const numThreshold = 1_000_000 + // Calculate interior deletes. + var numDeleted int + if fs.state.LastSeq > fs.state.FirstSeq { + numDeleted = int((fs.state.LastSeq - fs.state.FirstSeq + 1) - fs.state.Msgs) + } + if numSubjects > numThreshold || numDeleted > numThreshold { + fs.mu.Unlock() + return errStateTooBig + } + } + // We track this through subsequent runs to get an avg per blk used for subsequent runs. avgDmapLen := fs.adml // If first time through could be 0 @@ -7557,11 +7628,8 @@ func (fs *fileStore) writeFullState() error { avgDmapLen = 1024 } - // For calculating size. - numSubjects := fs.psim.Size() - // Calculate and estimate of the uper bound on the size to avoid multiple allocations. - sz := 2 + // Magic and Version + sz := hdrLen + // Magic and Version (binary.MaxVarintLen64 * 6) + // FS data binary.MaxVarintLen64 + fs.tsl + // NumSubjects + total subject length numSubjects*(binary.MaxVarintLen64*4) + // psi record @@ -7575,7 +7643,7 @@ func (fs *fileStore) writeFullState() error { if sz <= ssz { var _buf [ssz]byte - buf, sz = _buf[0:2:ssz], ssz + buf, sz = _buf[0:hdrLen:ssz], ssz } else { buf = make([]byte, hdrLen, sz) } @@ -7684,8 +7752,6 @@ func (fs *fileStore) writeFullState() error { fs.warn("Stream state encountered internal inconsistency on write") // Rebuild our fs state from the mb state. fs.rebuildState(nil) - // Make sure to reprocess. - fs.kickFlushStateLoop() return errCorruptState } @@ -7693,32 +7759,23 @@ func (fs *fileStore) writeFullState() error { fs.debug("WriteFullState reallocated from %d to %d", sz, cap(buf)) } - // Write to a tmp file and rename. - const tmpPre = streamStreamStateFile + tsep - f, err := os.CreateTemp(filepath.Join(fs.fcfg.StoreDir, msgDir), tmpPre) - if err != nil { - return err - } - tmpName := f.Name() - defer os.Remove(tmpName) - if _, err = f.Write(buf); err == nil && fs.fcfg.SyncAlways { - f.Sync() - } - f.Close() - if err != nil { - return err + // Only warn about construction time since file write not holding any locks. + if took := time.Since(start); took > time.Minute { + fs.warn("WriteFullState took %v (%d bytes)", took.Round(time.Millisecond), len(buf)) } - // Rename into position under our lock, clear prior dirty pending on success. - fs.mu.Lock() - if !fs.closed { - if err := os.Rename(tmpName, fn); err != nil { - fs.mu.Unlock() - return err - } + // Write our update index.db + // Protect with dios. + <-dios + err := os.WriteFile(fn, buf, defaultFilePerms) + dios <- struct{}{} + + // Update dirty if successful. + if err == nil { + fs.mu.Lock() fs.dirty -= priorDirty + fs.mu.Unlock() } - fs.mu.Unlock() return nil } @@ -7760,7 +7817,7 @@ func (fs *fileStore) stop(writeState bool) error { fs.mu.Unlock() <-fsld // Write full state if needed. If not dirty this is a no-op. - fs.writeFullState() + fs.forceWriteFullState() fs.mu.Lock() } diff --git a/vendor/github.com/nats-io/nats-server/v2/server/gateway.go b/vendor/github.com/nats-io/nats-server/v2/server/gateway.go index f3a8ae8633..6c46eea889 100644 --- a/vendor/github.com/nats-io/nats-server/v2/server/gateway.go +++ b/vendor/github.com/nats-io/nats-server/v2/server/gateway.go @@ -907,6 +907,15 @@ func (s *Server) createGateway(cfg *gatewayCfg, url *url.URL, conn net.Conn) { c.Debugf("TLS version %s, cipher suite %s", tlsVersion(cs.Version), tlsCipher(cs.CipherSuite)) } + // For outbound, we can't set the normal ping timer yet since the other + // side would fail with a parse error should it receive anything but the + // CONNECT protocol as the first protocol. We still want to make sure + // that the connection is not stale until the first INFO from the remote + // is received. + if solicit { + c.watchForStaleConnection(adjustPingInterval(GATEWAY, opts.PingInterval), opts.MaxPingsOut) + } + c.mu.Unlock() // Announce ourselves again to new connections. diff --git a/vendor/github.com/nats-io/nats-server/v2/server/jetstream_cluster.go b/vendor/github.com/nats-io/nats-server/v2/server/jetstream_cluster.go index 58fb0f7e97..880f9346f2 100644 --- a/vendor/github.com/nats-io/nats-server/v2/server/jetstream_cluster.go +++ b/vendor/github.com/nats-io/nats-server/v2/server/jetstream_cluster.go @@ -47,8 +47,8 @@ type jetStreamCluster struct { // concurrent requests that would otherwise be accepted. // We also record the group for the stream. This is needed since if we have // concurrent requests for same account and stream we need to let it process to get - // a response but they need to be same group, peers etc. - inflight map[string]map[string]*raftGroup + // a response but they need to be same group, peers etc. and sync subjects. + inflight map[string]map[string]*inflightInfo // Signals meta-leader should check the stream assignments. streamsCheck bool // Server. @@ -70,6 +70,12 @@ type jetStreamCluster struct { qch chan struct{} } +// Used to track inflight stream add requests to properly re-use same group and sync subject. +type inflightInfo struct { + rg *raftGroup + sync string +} + // Used to guide placement of streams and meta controllers in clustered JetStream. type Placement struct { Cluster string `json:"cluster,omitempty"` @@ -1576,6 +1582,7 @@ func (js *jetStream) applyMetaSnapshot(buf []byte, ru *recoveryUpdates, isRecove } } } + // Now walk the ones to check and process consumers. var caAdd, caDel []*consumerAssignment for _, sa := range saChk { @@ -2356,11 +2363,18 @@ func (js *jetStream) monitorStream(mset *stream, sa *streamAssignment, sendSnaps } defer stopDirectMonitoring() + // For checking interest state if applicable. + var cist *time.Ticker + var cistc <-chan time.Time + if mset != nil && mset.isInterestRetention() { // 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() + // Setup a periodic check here. + cist = time.NewTicker(30 * time.Second) + cistc = cist.C } // This is triggered during a scale up from R1 to clustered mode. We need the new followers to catchup, @@ -2484,6 +2498,9 @@ func (js *jetStream) monitorStream(mset *stream, sa *streamAssignment, sendSnaps } } + case <-cistc: + mset.checkInterestState() + case <-datc: if mset == nil || isRecovering { continue @@ -2645,6 +2662,11 @@ func (js *jetStream) monitorStream(mset *stream, sa *streamAssignment, sendSnaps uch = mset.updateC() // Also update our mqch mqch = mset.monitorQuitC() + // Setup a periodic check here if we are interest based as well. + if mset.isInterestRetention() { + cist = time.NewTicker(30 * time.Second) + cistc = cist.C + } } } if err != nil { @@ -3549,7 +3571,6 @@ func (js *jetStream) processClusterUpdateStream(acc *Account, osa, sa *streamAss var needsSetLeader bool if !alreadyRunning && numReplicas > 1 { if needsNode { - mset.setLeader(false) js.createRaftGroup(acc.GetName(), rg, storage, pprofLabels{ "type": "stream", "account": mset.accName(), @@ -3569,10 +3590,14 @@ func (js *jetStream) processClusterUpdateStream(acc *Account, osa, sa *streamAss } else if numReplicas == 1 && alreadyRunning { // We downgraded to R1. Make sure we cleanup the raft node and the stream monitor. mset.removeNode() - // Make sure we are leader now that we are R1. - needsSetLeader = true // In case we need to shutdown the cluster specific subs, etc. - mset.setLeader(false) + mset.mu.Lock() + // Stop responding to sync requests. + mset.stopClusterSubs() + // Clear catchup state + mset.clearAllCatchupPeers() + mset.mu.Unlock() + // Remove from meta layer. js.mu.Lock() rg.node = nil js.mu.Unlock() @@ -4761,9 +4786,9 @@ func (js *jetStream) monitorConsumer(o *consumer, ca *consumerAssignment) { o.checkStateForInterestStream() // Do a snapshot. doSnapshot(true) - // Synchronize followers to our state. Only send out if we have state. + // Synchronize followers to our state. Only send out if we have state and nothing pending. if n != nil { - if _, _, applied := n.Progress(); applied > 0 { + if _, _, applied := n.Progress(); applied > 0 && aq.len() == 0 { if snap, err := o.store.EncodedState(); err == nil { n.SendSnapshot(snap) } @@ -4986,6 +5011,13 @@ var errConsumerClosed = errors.New("consumer closed") func (o *consumer) processReplicatedAck(dseq, sseq uint64) error { o.mu.Lock() + // Update activity. + o.lat = time.Now() + + // Do actual ack update to store. + // Always do this to have it recorded. + o.store.UpdateAcks(dseq, sseq) + mset := o.mset if o.closed || mset == nil { o.mu.Unlock() @@ -4996,11 +5028,11 @@ func (o *consumer) processReplicatedAck(dseq, sseq uint64) error { return errStreamClosed } - // Update activity. - o.lat = time.Now() - - // Do actual ack update to store. - o.store.UpdateAcks(dseq, sseq) + // Check if we have a reply that was requested. + if reply := o.replies[sseq]; reply != _EMPTY_ { + o.outq.sendMsg(reply, nil) + delete(o.replies, sseq) + } if o.retention == LimitsPolicy { o.mu.Unlock() @@ -5481,7 +5513,7 @@ func (js *jetStream) processLeaderChange(isLeader bool) { for acc, asa := range cc.streams { for _, sa := range asa { if sa.Sync == _EMPTY_ { - s.Warnf("Stream assigment corrupt for stream '%s > %s'", acc, sa.Config.Name) + s.Warnf("Stream assignment corrupt for stream '%s > %s'", acc, sa.Config.Name) nsa := &streamAssignment{Group: sa.Group, Config: sa.Config, Subject: sa.Subject, Reply: sa.Reply, Client: sa.Client} nsa.Sync = syncSubjForStream() cc.meta.Propose(encodeUpdateStreamAssignment(nsa)) @@ -5985,6 +6017,7 @@ func (s *Server) jsClusteredStreamRequest(ci *ClientInfo, acc *Account, subject, var self *streamAssignment var rg *raftGroup + var syncSubject string // Capture if we have existing assignment first. if osa := js.streamAssignment(acc.Name, cfg.Name); osa != nil { @@ -5994,7 +6027,7 @@ func (s *Server) jsClusteredStreamRequest(ci *ClientInfo, acc *Account, subject, return } // This is an equal assignment. - self, rg = osa, osa.Group + self, rg, syncSubject = osa, osa.Group, osa.Sync } if cfg.Sealed { @@ -6018,19 +6051,22 @@ func (s *Server) jsClusteredStreamRequest(ci *ClientInfo, acc *Account, subject, return } + // Make sure inflight is setup properly. + if cc.inflight == nil { + cc.inflight = make(map[string]map[string]*inflightInfo) + } + streams, ok := cc.inflight[acc.Name] + if !ok { + streams = make(map[string]*inflightInfo) + cc.inflight[acc.Name] = streams + } + // Raft group selection and placement. if rg == nil { // Check inflight before proposing in case we have an existing inflight proposal. - if cc.inflight == nil { - cc.inflight = make(map[string]map[string]*raftGroup) - } - streams, ok := cc.inflight[acc.Name] - if !ok { - streams = make(map[string]*raftGroup) - cc.inflight[acc.Name] = streams - } else if existing, ok := streams[cfg.Name]; ok { - // We have existing for same stream. Re-use same group. - rg = existing + if existing, ok := streams[cfg.Name]; ok { + // We have existing for same stream. Re-use same group and syncSubject. + rg, syncSubject = existing.rg, existing.sync } } // Create a new one here if needed. @@ -6046,14 +6082,17 @@ func (s *Server) jsClusteredStreamRequest(ci *ClientInfo, acc *Account, subject, rg.setPreferred() } + if syncSubject == _EMPTY_ { + syncSubject = syncSubjForStream() + } // Sync subject for post snapshot sync. - sa := &streamAssignment{Group: rg, Sync: syncSubjForStream(), Config: cfg, Subject: subject, Reply: reply, Client: ci, Created: time.Now().UTC()} + sa := &streamAssignment{Group: rg, Sync: syncSubject, Config: cfg, Subject: subject, Reply: reply, Client: ci, Created: time.Now().UTC()} if err := cc.meta.Propose(encodeAddStreamAssignment(sa)); err == nil { // On success, add this as an inflight proposal so we can apply limits // on concurrent create requests while this stream assignment has // possibly not been processed yet. if streams, ok := cc.inflight[acc.Name]; ok { - streams[cfg.Name] = rg + streams[cfg.Name] = &inflightInfo{rg, syncSubject} } } } @@ -7468,7 +7507,7 @@ func encodeStreamMsg(subject, reply string, hdr, msg []byte, lseq uint64, ts int // Threshold for compression. // TODO(dlc) - Eventually make configurable. -const compressThreshold = 256 +const compressThreshold = 8192 // 8k // If allowed and contents over the threshold we will compress. func encodeStreamMsgAllowCompress(subject, reply string, hdr, msg []byte, lseq uint64, ts int64, compressOK bool) []byte { @@ -7625,7 +7664,7 @@ func (mset *stream) processClusteredInboundMsg(subject, reply string, hdr, msg [ s, js, jsa, st, r, tierName, outq, node := mset.srv, mset.js, mset.jsa, mset.cfg.Storage, mset.cfg.Replicas, mset.tier, mset.outq, mset.node 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 + isLeader, isSealed, compressOK := mset.isLeader(), mset.cfg.Sealed, mset.compressOK mset.mu.RUnlock() // This should not happen but possible now that we allow scale up, and scale down where this could trigger. @@ -7813,7 +7852,7 @@ func (mset *stream) processClusteredInboundMsg(subject, reply string, hdr, msg [ } } - esm := encodeStreamMsgAllowCompress(subject, reply, hdr, msg, mset.clseq, time.Now().UnixNano(), mset.compressOK) + esm := encodeStreamMsgAllowCompress(subject, reply, hdr, msg, mset.clseq, time.Now().UnixNano(), compressOK) // Do proposal. err := node.Propose(esm) if err == nil { @@ -7854,7 +7893,13 @@ type streamSyncRequest struct { } // Given a stream state that represents a snapshot, calculate the sync request based on our current state. +// Stream lock must be held. func (mset *stream) calculateSyncRequest(state *StreamState, snap *StreamReplicatedState) *streamSyncRequest { + // Shouldn't happen, but consequences are pretty bad if we have the lock held and + // our caller tries to take the lock again on panic defer, as in processSnapshot. + if state == nil || snap == nil || mset.node == nil { + return nil + } // Quick check if we are already caught up. if state.LastSeq >= snap.LastSeq { return nil @@ -7982,7 +8027,7 @@ func (mset *stream) isCurrent() bool { } // Maximum requests for the whole server that can be in flight at the same time. -const maxConcurrentSyncRequests = 16 +const maxConcurrentSyncRequests = 32 var ( errCatchupCorruptSnapshot = errors.New("corrupt stream snapshot detected") @@ -7990,6 +8035,7 @@ var ( errCatchupStreamStopped = errors.New("stream has been stopped") // when a catchup is terminated due to the stream going away. errCatchupBadMsg = errors.New("bad catchup msg") errCatchupWrongSeqForSkip = errors.New("wrong sequence for skipped msg") + errCatchupTooManyRetries = errors.New("catchup failed, too many retries") ) // Process a stream snapshot. @@ -8054,6 +8100,13 @@ func (mset *stream) processSnapshot(snap *StreamReplicatedState) (e error) { o.mu.Unlock() } mset.mu.Unlock() + + // If we are interest based make sure to check our ack floor state. + // We will delay a bit to allow consumer states to also catchup. + if mset.isInterestRetention() { + fire := time.Duration(rand.Intn(10)+5) * time.Second + time.AfterFunc(fire, mset.checkInterestState) + } }() var releaseSem bool @@ -8098,7 +8151,7 @@ RETRY: numRetries++ if numRetries >= maxRetries { // Force a hard reset here. - return errFirstSequenceMismatch + return errCatchupTooManyRetries } // Block here if we have too many requests in flight. @@ -8175,7 +8228,6 @@ RETRY: // Check for eof signaling. if len(msg) == 0 { msgsQ.recycle(&mrecs) - mset.checkInterestState() return nil } if _, err := mset.processCatchupMsg(msg); err == nil { diff --git a/vendor/github.com/nats-io/nats-server/v2/server/memstore.go b/vendor/github.com/nats-io/nats-server/v2/server/memstore.go index baf7e0f824..3da696bcc6 100644 --- a/vendor/github.com/nats-io/nats-server/v2/server/memstore.go +++ b/vendor/github.com/nats-io/nats-server/v2/server/memstore.go @@ -1012,7 +1012,7 @@ func (ms *memStore) LoadNextMsgMulti(sl *Sublist, start uint64, smp *StoreMsg) ( if !ok { continue } - if r := sl.Match(sm.subj); len(r.psubs) > 0 { + if sl.HasInterest(sm.subj) { if smp == nil { smp = new(StoreMsg) } diff --git a/vendor/github.com/nats-io/nats-server/v2/server/monitor.go b/vendor/github.com/nats-io/nats-server/v2/server/monitor.go index 3da0930de2..11cd864573 100644 --- a/vendor/github.com/nats-io/nats-server/v2/server/monitor.go +++ b/vendor/github.com/nats-io/nats-server/v2/server/monitor.go @@ -1,4 +1,4 @@ -// Copyright 2013-2023 The NATS Authors +// Copyright 2013-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 @@ -20,6 +20,7 @@ import ( "crypto/x509" "encoding/hex" "encoding/json" + "expvar" "fmt" "net" "net/http" @@ -2202,9 +2203,9 @@ func (s *Server) Leafz(opts *LeafzOptions) (*Leafz, error) { } s.mu.Unlock() - var leafnodes []*LeafInfo + leafnodes := make([]*LeafInfo, 0, len(lconns)) + if len(lconns) > 0 { - leafnodes = make([]*LeafInfo, 0, len(lconns)) for _, ln := range lconns { ln.mu.Lock() lni := &LeafInfo{ @@ -2231,6 +2232,7 @@ func (s *Server) Leafz(opts *LeafzOptions) (*Leafz, error) { leafnodes = append(leafnodes, lni) } } + return &Leafz{ ID: s.ID(), Now: time.Now().UTC(), @@ -2695,7 +2697,7 @@ func (s *Server) accountInfo(accName string) (*AccountInfo, error) { accName, a.updated.UTC(), isSys, - a.expired, + a.expired.Load(), !a.incomplete, a.js != nil, a.numLocalLeafNodes(), @@ -3671,6 +3673,30 @@ func (s *Server) healthz(opts *HealthzOptions) *HealthStatus { return health } +type ExpvarzStatus struct { + Memstats json.RawMessage `json:"memstats"` + Cmdline json.RawMessage `json:"cmdline"` +} + +func (s *Server) expvarz(_ *ExpvarzEventOptions) *ExpvarzStatus { + var stat ExpvarzStatus + + const memStatsKey = "memstats" + const cmdLineKey = "cmdline" + + expvar.Do(func(v expvar.KeyValue) { + switch v.Key { + case memStatsKey: + stat.Memstats = json.RawMessage(v.Value.String()) + + case cmdLineKey: + stat.Cmdline = json.RawMessage(v.Value.String()) + } + }) + + return &stat +} + type ProfilezStatus struct { Profile []byte `json:"profile"` Error string `json:"error"` diff --git a/vendor/github.com/nats-io/nats-server/v2/server/raft.go b/vendor/github.com/nats-io/nats-server/v2/server/raft.go index 83157f6e69..975ebd068b 100644 --- a/vendor/github.com/nats-io/nats-server/v2/server/raft.go +++ b/vendor/github.com/nats-io/nats-server/v2/server/raft.go @@ -1,4 +1,4 @@ -// Copyright 2020-2023 The NATS Authors +// Copyright 2020-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 @@ -150,6 +150,7 @@ type raft struct { pae map[uint64]*appendEntry // Pending append entries elect *time.Timer // Election timer, normally accessed via electTimer + etlr time.Time // Election timer last reset time, for unit tests only active time.Time // Last activity time, i.e. for heartbeats llqrt time.Time // Last quorum lost time lsut time.Time // Last scale-up time @@ -469,6 +470,11 @@ func (s *Server) startRaftNode(accName string, cfg *RaftConfig, labels pprofLabe } } } + } else if n.pterm == 0 && n.pindex == 0 { + // We have recovered no state, either through our WAL or snapshots, + // so inherit from term from our tav.idx file and pindex from our last sequence. + n.pterm = n.term + n.pindex = state.LastSeq } // Make sure to track ourselves. @@ -1766,6 +1772,7 @@ func (n *raft) resetElectionTimeoutWithLock() { // Lock should be held. func (n *raft) resetElect(et time.Duration) { + n.etlr = time.Now() if n.elect == nil { n.elect = time.NewTimer(et) } else { @@ -2342,31 +2349,38 @@ func (n *raft) runAsLeader() { n.resp.recycle(&ars) case <-n.prop.ch: const maxBatch = 256 * 1024 + const maxEntries = 512 var entries []*Entry - es := n.prop.pop() - sz := 0 - for i, b := range es { + es, sz := n.prop.pop(), 0 + for _, b := range es { if b.Type == EntryRemovePeer { n.doRemovePeerAsLeader(string(b.Data)) } entries = append(entries, b) - sz += len(b.Data) + 1 - if i != len(es)-1 && sz < maxBatch && len(entries) < math.MaxUint16 { - continue - } - n.sendAppendEntry(entries) - // If this is us sending out a leadership transfer stepdown inline here. if b.Type == EntryLeaderTransfer { + // Send out what we have and switch to follower. + n.sendAppendEntry(entries) n.prop.recycle(&es) n.debug("Stepping down due to leadership transfer") n.switchToFollower(noLeader) return } + // Increment size. + sz += len(b.Data) + 1 + // If below thresholds go ahead and send. + if sz < maxBatch && len(entries) < maxEntries { + continue + } + n.sendAppendEntry(entries) + // Reset our sz and entries. // We need to re-create `entries` because there is a reference // to it in the node's pae map. - entries = nil + sz, entries = 0, nil + } + if len(entries) > 0 { + n.sendAppendEntry(entries) } n.prop.recycle(&es) @@ -2411,9 +2425,9 @@ func (n *raft) Quorum() bool { n.RLock() defer n.RUnlock() - now, nc := time.Now().UnixNano(), 1 - for _, peer := range n.peers { - if now-peer.ts < int64(lostQuorumInterval) { + now, nc := time.Now().UnixNano(), 0 + for id, peer := range n.peers { + if id == n.id || time.Duration(now-peer.ts) < lostQuorumInterval { nc++ if nc >= n.qn { return true @@ -2435,9 +2449,9 @@ func (n *raft) lostQuorumLocked() bool { return false } - now, nc := time.Now().UnixNano(), 1 - for _, peer := range n.peers { - if now-peer.ts < int64(lostQuorumInterval) { + now, nc := time.Now().UnixNano(), 0 + for id, peer := range n.peers { + if id == n.id || time.Duration(now-peer.ts) < lostQuorumInterval { nc++ if nc >= n.qn { return false @@ -2682,15 +2696,11 @@ func (n *raft) applyCommit(index uint64) error { n.debug("Ignoring apply commit for %d, already processed", index) return nil } - original := n.commit - n.commit = index if n.State() == Leader { delete(n.acks, index) } - var fpae bool - ae := n.pae[index] if ae == nil { var state StreamState @@ -2708,15 +2718,14 @@ func (n *raft) applyCommit(index uint64) error { // Reset and cancel any catchup. n.resetWAL() n.cancelCatchup() - } else { - n.commit = original } return errEntryLoadFailed } } else { - fpae = true + defer delete(n.pae, index) } + n.commit = index ae.buf = nil var committed []*Entry @@ -2791,9 +2800,6 @@ func (n *raft) applyCommit(index uint64) error { committed = append(committed, e) } } - if fpae { - delete(n.pae, index) - } // Pass to the upper layers if we have normal entries. It is // entirely possible that 'committed' might be an empty slice here, // which will happen if we've processed updates inline (like peer @@ -3121,9 +3127,13 @@ func (n *raft) processAppendEntry(ae *appendEntry, sub *subscription) { if n.State() == Leader { // If we are the same we should step down to break the tie. if ae.term >= n.term { - n.term = ae.term - n.vote = noVote - n.writeTermVote() + // If the append entry term is newer than the current term, erase our + // vote. + if ae.term > n.term { + n.term = ae.term + n.vote = noVote + n.writeTermVote() + } n.debug("Received append entry from another leader, stepping down to %q", ae.leader) n.stepdown.push(ae.leader) } else { @@ -3142,13 +3152,18 @@ func (n *raft) processAppendEntry(ae *appendEntry, sub *subscription) { // another node has taken on the leader role already, so we should convert // to a follower of that node instead. if n.State() == Candidate { - n.debug("Received append entry in candidate state from %q, converting to follower", ae.leader) - if n.term < ae.term { - n.term = ae.term - n.vote = noVote - n.writeTermVote() + // Ignore old terms, otherwise we might end up stepping down incorrectly. + if ae.term >= n.term { + // If the append entry term is newer than the current term, erase our + // vote. + if ae.term > n.term { + n.term = ae.term + n.vote = noVote + n.writeTermVote() + } + n.debug("Received append entry in candidate state from %q, converting to follower", ae.leader) + n.stepdown.push(ae.leader) } - n.stepdown.push(ae.leader) } // Catching up state. @@ -3258,6 +3273,15 @@ func (n *raft) processAppendEntry(ae *appendEntry, sub *subscription) { // so make sure this is a snapshot entry. If it is not start the catchup process again since it // means we may have missed additional messages. if catchingUp { + // This means we already entered into a catchup state but what the leader sent us did not match what we expected. + // Snapshots and peerstate will always be together when a leader is catching us up in this fashion. + if len(ae.entries) != 2 || ae.entries[0].Type != EntrySnapshot || ae.entries[1].Type != EntryPeerState { + n.warn("Expected first catchup entry to be a snapshot and peerstate, will retry") + n.cancelCatchup() + n.Unlock() + return + } + // Check if only our terms do not match here. if ae.pindex == n.pindex { // Make sure pterms match and we take on the leader's. @@ -3267,14 +3291,6 @@ func (n *raft) processAppendEntry(ae *appendEntry, sub *subscription) { n.Unlock() return } - // This means we already entered into a catchup state but what the leader sent us did not match what we expected. - // Snapshots and peerstate will always be together when a leader is catching us up in this fashion. - if len(ae.entries) != 2 || ae.entries[0].Type != EntrySnapshot || ae.entries[1].Type != EntryPeerState { - n.warn("Expected first catchup entry to be a snapshot and peerstate, will retry") - n.cancelCatchup() - n.Unlock() - return - } if ps, err := decodePeerState(ae.entries[1].Data); err == nil { n.processPeerState(ps) @@ -3739,11 +3755,11 @@ func (n *raft) readTermVote() (term uint64, voted string, err error) { if err != nil { return 0, noVote, err } - if len(buf) < termVoteLen { - return 0, noVote, nil - } var le = binary.LittleEndian term = le.Uint64(buf[0:]) + if len(buf) < termVoteLen { + return term, noVote, nil + } voted = string(buf[8:]) return term, voted, nil } @@ -3809,6 +3825,8 @@ func (n *raft) writeTermVote() { // Stamp latest and write the term & vote file. n.wtv = b if err := writeTermVote(n.sd, n.wtv); err != nil && !n.isClosed() { + // Clear wtv since we failed. + n.wtv = nil n.setWriteErrLocked(err) n.warn("Error writing term and vote file for %q: %v", n.group, err) } @@ -3875,7 +3893,6 @@ func (n *raft) processVoteRequest(vr *voteRequest) error { } n.Lock() - n.resetElectionTimeout() vresp := &voteResponse{n.term, n.id, false} defer n.debug("Sending a voteResponse %+v -> %q", vresp, vr.reply) @@ -3907,6 +3924,7 @@ func (n *raft) processVoteRequest(vr *voteRequest) error { n.term = vr.term n.vote = vr.candidate n.writeTermVote() + n.resetElectionTimeout() } else { if vr.term >= n.term && n.vote == noVote { n.term = vr.term @@ -3996,25 +4014,27 @@ func (n *raft) updateLeadChange(isLeader bool) { // Lock should be held. func (n *raft) switchState(state RaftState) { - if n.State() == Closed { + pstate := n.State() + if pstate == Closed { return } // Reset the election timer. n.resetElectionTimeout() + // Set our state. + n.state.Store(int32(state)) - if n.State() == Leader && state != Leader { + if pstate == Leader && state != Leader { n.updateLeadChange(false) // Drain the response queue. n.resp.drain() - } else if state == Leader && n.State() != Leader { + } else if state == Leader && pstate != Leader { if len(n.pae) > 0 { n.pae = make(map[uint64]*appendEntry) } n.updateLeadChange(true) } - n.state.Store(int32(state)) n.writeTermVote() } diff --git a/vendor/github.com/nats-io/nats-server/v2/server/route.go b/vendor/github.com/nats-io/nats-server/v2/server/route.go index 2a08588d77..0341f79868 100644 --- a/vendor/github.com/nats-io/nats-server/v2/server/route.go +++ b/vendor/github.com/nats-io/nats-server/v2/server/route.go @@ -1398,7 +1398,7 @@ func (c *client) processRemoteSub(argo []byte, hasOrigin bool) (err error) { var isNew bool if acc, isNew = srv.LookupOrRegisterAccount(accountName); isNew { acc.mu.Lock() - acc.expired = true + acc.expired.Store(true) acc.incomplete = true acc.mu.Unlock() } @@ -1788,13 +1788,7 @@ func (s *Server) createRoute(conn net.Conn, rURL *url.URL, accName string) *clie if opts.Cluster.MaxPingsOut > 0 { pingMax = opts.MaxPingsOut } - c.ping.tmr = time.AfterFunc(pingInterval*time.Duration(pingMax+1), func() { - c.mu.Lock() - c.Debugf("Stale Client Connection - Closing") - c.enqueueProto([]byte(fmt.Sprintf(errProto, "Stale Connection"))) - c.mu.Unlock() - c.closeConnection(StaleConnection) - }) + c.watchForStaleConnection(adjustPingInterval(ROUTER, pingInterval), pingMax) } else { // Set the Ping timer c.setFirstPingTimer() diff --git a/vendor/github.com/nats-io/nats-server/v2/server/server.go b/vendor/github.com/nats-io/nats-server/v2/server/server.go index be5cfc31ba..89e9c7de23 100644 --- a/vendor/github.com/nats-io/nats-server/v2/server/server.go +++ b/vendor/github.com/nats-io/nats-server/v2/server/server.go @@ -2478,12 +2478,7 @@ func (s *Server) Shutdown() { } // Kick websocket server - if s.websocket.server != nil { - doneExpected++ - s.websocket.server.Close() - s.websocket.server = nil - s.websocket.listener = nil - } + doneExpected += s.closeWebsocketServer() // Kick MQTT accept loop if s.mqtt.listener != nil { @@ -2571,6 +2566,24 @@ func (s *Server) Shutdown() { close(s.shutdownComplete) } +// Close the websocket server if running. If so, returns 1, else 0. +// Server lock held on entry. +func (s *Server) closeWebsocketServer() int { + ws := &s.websocket + ws.mu.Lock() + hs := ws.server + if hs != nil { + ws.server = nil + ws.listener = nil + } + ws.mu.Unlock() + if hs != nil { + hs.Close() + return 1 + } + return 0 +} + // WaitForShutdown will block until the server has been fully shutdown. func (s *Server) WaitForShutdown() { <-s.shutdownComplete @@ -4090,12 +4103,7 @@ func (s *Server) lameDuckMode() { expected := 1 s.listener.Close() s.listener = nil - if s.websocket.server != nil { - expected++ - s.websocket.server.Close() - s.websocket.server = nil - s.websocket.listener = nil - } + expected += s.closeWebsocketServer() s.ldmCh = make(chan bool, expected) opts := s.getOpts() gp := opts.LameDuckGracePeriod @@ -4396,8 +4404,10 @@ func (s *Server) changeRateLimitLogInterval(d time.Duration) { // DisconnectClientByID disconnects a client by connection ID func (s *Server) DisconnectClientByID(id uint64) error { - client := s.clients[id] - if client != nil { + if s == nil { + return ErrServerNotRunning + } + if client := s.getClient(id); client != nil { client.closeConnection(Kicked) return nil } @@ -4406,23 +4416,27 @@ func (s *Server) DisconnectClientByID(id uint64) error { // LDMClientByID sends a Lame Duck Mode info message to a client by connection ID func (s *Server) LDMClientByID(id uint64) error { + if s == nil { + return ErrServerNotRunning + } + s.mu.RLock() + c := s.clients[id] + if c == nil { + s.mu.RUnlock() + return errors.New("no such client id") + } info := s.copyInfo() info.LameDuckMode = true - - c := s.clients[id] - if c != nil { - c.mu.Lock() - defer c.mu.Unlock() - if c.opts.Protocol >= ClientProtoInfo && - c.flags.isSet(firstPongSent) { - // sendInfo takes care of checking if the connection is still - // valid or not, so don't duplicate tests here. - c.Debugf("sending Lame Duck Mode info to client") - c.enqueueProto(c.generateClientInfoJSON(info)) - return nil - } else { - return errors.New("ClientProtoInfo < ClientOps.Protocol or first pong not sent") - } + s.mu.RUnlock() + c.mu.Lock() + defer c.mu.Unlock() + if c.opts.Protocol >= ClientProtoInfo && c.flags.isSet(firstPongSent) { + // sendInfo takes care of checking if the connection is still + // valid or not, so don't duplicate tests here. + c.Debugf("Sending Lame Duck Mode info to client") + c.enqueueProto(c.generateClientInfoJSON(info)) + return nil + } else { + return errors.New("client does not support Lame Duck Mode or is not ready to receive the notification") } - return errors.New("no such client id") } diff --git a/vendor/github.com/nats-io/nats-server/v2/server/store.go b/vendor/github.com/nats-io/nats-server/v2/server/store.go index 2bd87e6b11..235452a30f 100644 --- a/vendor/github.com/nats-io/nats-server/v2/server/store.go +++ b/vendor/github.com/nats-io/nats-server/v2/server/store.go @@ -721,7 +721,7 @@ func isOutOfSpaceErr(err error) bool { var errFirstSequenceMismatch = errors.New("first sequence mismatch") func isClusterResetErr(err error) bool { - return err == errLastSeqMismatch || err == ErrStoreEOF || err == errFirstSequenceMismatch + return err == errLastSeqMismatch || err == ErrStoreEOF || err == errFirstSequenceMismatch || err == errCatchupTooManyRetries } // Copy all fields. diff --git a/vendor/github.com/nats-io/nats-server/v2/server/stream.go b/vendor/github.com/nats-io/nats-server/v2/server/stream.go index f35f7098a0..518fb6599d 100644 --- a/vendor/github.com/nats-io/nats-server/v2/server/stream.go +++ b/vendor/github.com/nats-io/nats-server/v2/server/stream.go @@ -213,50 +213,56 @@ type ExternalStream struct { // Stream is a jetstream stream of messages. When we receive a message internally destined // for a Stream we will direct link from the client to this structure. type stream struct { - mu sync.RWMutex - js *jetStream - jsa *jsAccount - acc *Account - srv *Server - client *client - sysc *client - sid atomic.Uint64 - pubAck []byte - outq *jsOutQ - msgs *ipQueue[*inMsg] - gets *ipQueue[*directGetReq] - store StreamStore - ackq *ipQueue[uint64] - lseq uint64 - lmsgId string - consumers map[string]*consumer - numFilter int // number of filtered consumers - cfg StreamConfig - created time.Time - stype StorageType - tier string - ddmap map[string]*ddentry - ddarr []*ddentry - ddindex int - ddtmr *time.Timer - qch chan struct{} - mqch chan struct{} - active bool - ddloaded bool - closed atomic.Bool + mu sync.RWMutex // Read/write lock for the stream. + js *jetStream // The internal *jetStream for the account. + jsa *jsAccount // The JetStream account-level information. + acc *Account // The account this stream is defined in. + srv *Server // The server we are running in. + client *client // The internal JetStream client. + sysc *client // The internal JetStream system client. + + // The current last subscription ID for the subscriptions through `client`. + // Those subscriptions are for the subjects filters being listened to and captured by the stream. + sid atomic.Uint64 + + pubAck []byte // The template (prefix) to generate the pubAck responses for this stream quickly. + outq *jsOutQ // Queue of *jsPubMsg for sending messages. + msgs *ipQueue[*inMsg] // Intra-process queue for the ingress of messages. + gets *ipQueue[*directGetReq] // Intra-process queue for the direct get requests. + store StreamStore // The storage for this stream. + ackq *ipQueue[uint64] // Intra-process queue for acks. + lseq uint64 // The sequence number of the last message stored in the stream. + lmsgId string // The de-duplication message ID of the last message stored in the stream. + consumers map[string]*consumer // The consumers for this stream. + numFilter int // The number of filtered consumers. + cfg StreamConfig // The stream's config. + created time.Time // Time the stream was created. + stype StorageType // The storage type. + tier string // The tier is the number of replicas for the stream (e.g. "R1" or "R3"). + ddmap map[string]*ddentry // The dedupe map. + ddarr []*ddentry // The dedupe array. + ddindex int // The dedupe index. + ddtmr *time.Timer // The dedupe timer. + qch chan struct{} // The quit channel. + mqch chan struct{} // The monitor's quit channel. + active bool // Indicates that there are active internal subscriptions (for the subject filters) + // and/or mirror/sources consumers are scheduled to be established or already started. + ddloaded bool // set to true when the deduplication structures are been built. + closed atomic.Bool // Set to true when stop() is called on the stream. // Mirror mirror *sourceInfo // Sources sources map[string]*sourceInfo - sourceRetries map[string]*time.Timer + sourceSetupSchedules map[string]*time.Timer sourcesConsumerSetup *time.Timer + smsgs *ipQueue[*inMsg] // Intra-process queue for all incoming sourced messages. // Indicates we have direct consumers. directs int - // For input subject transform + // For input subject transform. itr *subjectTransform // For republishing. @@ -264,10 +270,10 @@ type stream struct { // For processing consumers without main stream lock. clsMu sync.RWMutex - cList []*consumer - sch chan struct{} - sigq *ipQueue[*cMsg] - csl *Sublist // Consumer Sublist + cList []*consumer // Consumer list. + sch chan struct{} // Channel to signal consumers. + sigq *ipQueue[*cMsg] // Intra-process queue for the messages to signal to the consumers. + csl *Sublist // Consumer subscription list. // For non limits policy streams when they process an ack before the actual msg. // Can happen in stretch clusters, multi-cloud, or during catchup for a restarted server. @@ -275,51 +281,56 @@ type stream struct { // TODO(dlc) - Hide everything below behind two pointers. // Clustered mode. - sa *streamAssignment - node RaftNode - catchup atomic.Bool - syncSub *subscription - infoSub *subscription - clMu sync.Mutex - clseq uint64 - clfs uint64 - inflight map[uint64]uint64 - leader string - lqsent time.Time - catchups map[string]uint64 - uch chan struct{} - compressOK bool - inMonitor bool + sa *streamAssignment // What the meta controller uses to assign streams to peers. + node RaftNode // Our RAFT node for the stream's group. + catchup atomic.Bool // Used to signal we are in catchup mode. + catchups map[string]uint64 // The number of messages that need to be caught per peer. + syncSub *subscription // Internal subscription for sync messages (on "$JSC.SYNC"). + infoSub *subscription // Internal subscription for stream info requests. + clMu sync.Mutex // The mutex for clseq and clfs. + clseq uint64 // The current last seq being proposed to the NRG layer. + clfs uint64 // The count (offset) of the number of failed NRG sequences used to compute clseq. + inflight map[uint64]uint64 // Inflight message sizes per clseq. + lqsent time.Time // The time at which the last lost quorum advisory was sent. Used to rate limit. + uch chan struct{} // The channel to signal updates to the monitor routine. + compressOK bool // True if we can do message compression in RAFT and catchup logic + inMonitor bool // True if the monitor routine has been started. // Direct get subscription. directSub *subscription lastBySub *subscription - monitorWg sync.WaitGroup + monitorWg sync.WaitGroup // Wait group for the monitor routine. } type sourceInfo struct { - name string - iname string - cname string - sub *subscription - dsub *subscription + name string // The name of the stream being sourced. + iname string // The unique index name of this particular source. + cname string // The name of the current consumer for this source. + sub *subscription // The subscription to the consumer. + + // (mirrors only) The subscription to the direct get request subject for + // the source stream's name on the `_sys_` queue group. + dsub *subscription + + // (mirrors only) The subscription to the direct get last per subject request subject for + // the source stream's name on the `_sys_` queue group. lbsub *subscription - msgs *ipQueue[*inMsg] - sseq uint64 - dseq uint64 - start time.Time - lag uint64 - err *ApiError - fails int - last time.Time - lreq time.Time - qch chan struct{} - sip bool // setup in progress - wg sync.WaitGroup - sf string // subject filter - sfs []string // subject filters - trs []*subjectTransform // subject transforms + + msgs *ipQueue[*inMsg] // Intra-process queue for incoming messages. + sseq uint64 // Last stream message sequence number seen from the source. + dseq uint64 // Last delivery (i.e. consumer's) sequence number. + lag uint64 // 0 or number of messages pending (as last reported by the consumer) - 1. + err *ApiError // The API error that caused the last consumer setup to fail. + fails int // The number of times trying to setup the consumer failed. + last atomic.Int64 // Time the consumer was created or of last message it received. + lreq time.Time // The last time setupMirrorConsumer/setupSourceConsumer was called. + qch chan struct{} // Quit channel. + sip bool // Setup in progress. + wg sync.WaitGroup // WaitGroup for the consumer's go routine. + sf string // The subject filter. + sfs []string // The subject filters. + trs []*subjectTransform // The subject transforms. } // For mirrors and direct get @@ -365,9 +376,9 @@ const ( // Dedupe entry type ddentry struct { - id string - seq uint64 - ts int64 + id string // The unique message ID provided by the client. + seq uint64 // The sequence number of the message. + ts int64 // The timestamp of the message. } // Replicas Range @@ -691,8 +702,9 @@ func (a *Account) addStreamWithAssignment(config *StreamConfig, fsConfig *FileSt return mset, nil } -// Composes the index name. Contains the stream name, subject filter, and transform destination -// when the stream is external we will use additional information in case the (external) stream names are the same. +// Composes the index name. Contains the stream name, subject filter, and transform destination +// when the stream is external we will use the api prefix as part of the index name +// (as the same stream name could be used in multiple JS domains) func (ssi *StreamSource) composeIName() string { var iName = ssi.Name @@ -829,8 +841,13 @@ func (mset *stream) setLeader(isLeader bool) error { // Make sure we are listening for sync requests. // TODO(dlc) - Original design was that all in sync members of the group would do DQ. mset.startClusterSubs() - // Setup subscriptions + + // Setup subscriptions if we were not already the leader. if err := mset.subscribeToStream(); err != nil { + if mset.isClustered() { + // Stepdown since we have an error. + mset.node.StepDown() + } mset.mu.Unlock() return err } @@ -847,12 +864,6 @@ func (mset *stream) setLeader(isLeader bool) error { // Clear catchup state mset.clearAllCatchupPeers() } - // Track group leader. - if mset.isClustered() { - mset.leader = mset.node.GroupLeader() - } else { - mset.leader = _EMPTY_ - } mset.mu.Unlock() // If we are interest based make sure to check consumers. @@ -1828,7 +1839,7 @@ func (mset *stream) updateWithAdvisory(config *StreamConfig, sendAdvisory bool) } mset.setStartingSequenceForSources(needsStartingSeqNum) for iName := range neededCopy { - mset.setSourceConsumer(iName, mset.sources[iName].sseq+1, time.Time{}) + mset.setupSourceConsumer(iName, mset.sources[iName].sseq+1, time.Time{}) } } } @@ -2091,10 +2102,10 @@ func (mset *stream) sourceInfo(si *sourceInfo) *StreamSourceInfo { ssi.SubjectTransforms = trConfigs // If we have not heard from the source, set Active to -1. - if si.last.IsZero() { + if last := si.last.Load(); last == 0 { ssi.Active = -1 } else { - ssi.Active = time.Since(si.last) + ssi.Active = time.Since(time.Unix(0, last)) } var ext *ExternalStream @@ -2119,7 +2130,12 @@ func (mset *stream) mirrorInfo() *StreamSourceInfo { return mset.sourceInfo(mset.mirror) } -const sourceHealthCheckInterval = 1 * time.Second +const ( + // Our consumer HB interval. + sourceHealthHB = 1 * time.Second + // How often we check and our stalled interval. + sourceHealthCheckInterval = 10 * time.Second +) // Will run as a Go routine to process mirror consumer messages. func (mset *stream) processMirrorMsgs(mirror *sourceInfo, ready *sync.WaitGroup) { @@ -2133,7 +2149,7 @@ func (mset *stream) processMirrorMsgs(mirror *sourceInfo, ready *sync.WaitGroup) mset.mu.Lock() msgs, qch, siqch := mirror.msgs, mset.qch, mirror.qch // Set the last seen as now so that we don't fail at the first check. - mirror.last = time.Now() + mirror.last.Store(time.Now().UnixNano()) mset.mu.Unlock() // Signal the caller that we have captured the above fields. @@ -2164,12 +2180,14 @@ func (mset *stream) processMirrorMsgs(mirror *sourceInfo, ready *sync.WaitGroup) if !mset.processInboundMirrorMsg(im) { break } + im.returnToPool() } msgs.recycle(&ims) case <-t.C: mset.mu.RLock() isLeader := mset.isLeader() - stalled := mset.mirror != nil && time.Since(mset.mirror.last) > 3*sourceHealthCheckInterval + last := time.Unix(0, mset.mirror.last.Load()) + stalled := mset.mirror != nil && time.Since(last) > sourceHealthCheckInterval mset.mu.RUnlock() // No longer leader. if !isLeader { @@ -2214,9 +2232,6 @@ func (mset *stream) processInboundMirrorMsg(m *inMsg) bool { return false } - mset.mirror.last = time.Now() - node := mset.node - // Check for heartbeats and flow control messages. if isControl { var needsRetry bool @@ -2287,20 +2302,22 @@ func (mset *stream) processInboundMirrorMsg(m *inMsg) bool { } // Do the subject transform if there's one - - for _, tr := range mset.mirror.trs { - if tr == nil { - continue - } else { - tsubj, err := tr.Match(m.subj) - if err == nil { - m.subj = tsubj - break + if len(mset.mirror.trs) > 0 { + for _, tr := range mset.mirror.trs { + if tr == nil { + continue + } else { + tsubj, err := tr.Match(m.subj) + if err == nil { + m.subj = tsubj + break + } } } } s, js, stype := mset.srv, mset.js, mset.cfg.Storage + node := mset.node mset.mu.Unlock() var err error @@ -2373,6 +2390,7 @@ func (mset *stream) retryMirrorConsumer() error { mset.mu.Lock() defer mset.mu.Unlock() mset.srv.Debugf("Retrying mirror consumer for '%s > %s'", mset.acc.Name, mset.cfg.Name) + mset.cancelMirrorConsumer() return mset.setupMirrorConsumer() } @@ -2449,6 +2467,9 @@ func (mset *stream) scheduleSetupMirrorConsumerRetry() { }) } +// How long we wait for a response from a consumer create request for a source or mirror. +var srcConsumerWaitTime = 30 * time.Second + // Setup our mirror consumer. // Lock should be held. func (mset *stream) setupMirrorConsumer() error { @@ -2510,15 +2531,16 @@ func (mset *stream) setupMirrorConsumer() error { req := &CreateConsumerRequest{ Stream: mset.cfg.Mirror.Name, Config: ConsumerConfig{ - DeliverSubject: deliverSubject, - DeliverPolicy: DeliverByStartSequence, - OptStartSeq: state.LastSeq + 1, - AckPolicy: AckNone, - AckWait: 22 * time.Hour, - MaxDeliver: 1, - Heartbeat: sourceHealthCheckInterval, - FlowControl: true, - Direct: true, + DeliverSubject: deliverSubject, + DeliverPolicy: DeliverByStartSequence, + OptStartSeq: state.LastSeq + 1, + AckPolicy: AckNone, + AckWait: 22 * time.Hour, + MaxDeliver: 1, + Heartbeat: sourceHealthHB, + FlowControl: true, + Direct: true, + InactiveThreshold: sourceHealthCheckInterval, }, } @@ -2658,7 +2680,8 @@ func (mset *stream) setupMirrorConsumer() error { msgs := mirror.msgs sub, err := mset.subscribeInternal(deliverSubject, func(sub *subscription, c *client, _ *Account, subject, reply string, rmsg []byte) { hdr, msg := c.msgParts(copyBytes(rmsg)) // Need to copy. - mset.queueInbound(msgs, subject, reply, hdr, msg) + mset.queueInbound(msgs, subject, reply, hdr, msg, nil) + mirror.last.Store(time.Now().UnixNano()) }) if err != nil { mirror.err = NewJSMirrorConsumerSetupFailedError(err, Unless(err)) @@ -2707,9 +2730,9 @@ func (mset *stream) setupMirrorConsumer() error { } mset.mu.Unlock() ready.Wait() - case <-time.After(5 * time.Second): + case <-time.After(srcConsumerWaitTime): mset.unsubscribe(crSub) - // We already waited 5 seconds, let's retry now. + // We already waited 30 seconds, let's retry now. retry = true } }() @@ -2726,35 +2749,14 @@ func (mset *stream) streamSource(iname string) *StreamSource { return nil } -func (mset *stream) retrySourceConsumer(iName string) { - mset.mu.Lock() - defer mset.mu.Unlock() - - si := mset.sources[iName] - if si == nil { - return - } - var ss = mset.streamSource(iName) - if ss != nil { - iNameMap := map[string]struct{}{ - iName: {}, - } - mset.setStartingSequenceForSources(iNameMap) - mset.retrySourceConsumerAtSeq(iName, si.sseq+1) - } -} - -// Same than setSourceConsumer but simply issue a debug statement indicating -// that there is a retry. -// // Lock should be held. -func (mset *stream) retrySourceConsumerAtSeq(iname string, seq uint64) { +func (mset *stream) retrySourceConsumerAtSeq(iName string, seq uint64) { s := mset.srv s.Debugf("Retrying source consumer for '%s > %s'", mset.acc.Name, mset.cfg.Name) - // setSourceConsumer will check that the source is still configured. - mset.setSourceConsumer(iname, seq, time.Time{}) + // setupSourceConsumer will check that the source is still configured. + mset.setupSourceConsumer(iName, seq, time.Time{}) } // Lock should be held. @@ -2790,59 +2792,66 @@ func (mset *stream) cancelSourceInfo(si *sourceInfo) { si.msgs.drain() si.msgs.unregister() } + // If we have a schedule setup go ahead and delete that. + if t := mset.sourceSetupSchedules[si.iname]; t != nil { + t.Stop() + delete(mset.sourceSetupSchedules, si.iname) + } } const sourceConsumerRetryThreshold = 2 * time.Second -// This will schedule a call to setSourceConsumer, taking into account the last -// time it was retried and determine the soonest setSourceConsumer can be called -// without tripping the sourceConsumerRetryThreshold. -// -// Lock held on entry -func (mset *stream) scheduleSetSourceConsumerRetry(si *sourceInfo, seq uint64, startTime time.Time) { - // We are trying to figure out how soon we can retry. setSourceConsumer will reject - // a retry if last was done less than "sourceConsumerRetryThreshold" ago. - next := sourceConsumerRetryThreshold - time.Since(si.lreq) - if next < 0 { - // It means that we have passed the threshold and so we are ready to go. - next = 0 +// This is the main function to call when needing to setup a new consumer for the source. +// It actually only does the scheduling of the execution of trySetupSourceConsumer in order to implement retry backoff +// and throttle the number of requests. +// Lock should be held. +func (mset *stream) setupSourceConsumer(iname string, seq uint64, startTime time.Time) { + if mset.sourceSetupSchedules == nil { + mset.sourceSetupSchedules = map[string]*time.Timer{} } - // Take into account failures here. - next += calculateRetryBackoff(si.fails) - // To make *sure* that the next request will not fail, add a bit of buffer - // and some randomness. - next += time.Duration(rand.Intn(int(10*time.Millisecond))) + 10*time.Millisecond - mset.scheduleSetSourceConsumer(si.iname, seq, next, startTime) -} - -// Simply schedules setSourceConsumer at the given delay. -// -// Lock held on entry -func (mset *stream) scheduleSetSourceConsumer(iname string, seq uint64, delay time.Duration, startTime time.Time) { - if mset.sourceRetries == nil { - mset.sourceRetries = map[string]*time.Timer{} - } - if t, ok := mset.sourceRetries[iname]; ok && !t.Stop() { - // It looks like the goroutine has started running but hasn't taken the - // stream lock yet (otherwise the map entry would be deleted). We had - // might as well let the running goroutine complete and schedule another - // timer only if it needs to. + if _, ok := mset.sourceSetupSchedules[iname]; ok { + // If there is already a timer scheduled, we don't need to do anything. return } - mset.sourceRetries[iname] = time.AfterFunc(delay, func() { + + si := mset.sources[iname] + if si == nil || si.sip { // if sourceInfo was removed or setup is in progress, nothing to do + return + } + + // First calculate the delay until the next time we can + var scheduleDelay time.Duration + + if !si.lreq.IsZero() { // it's not the very first time we are called, compute the delay + // We want to throttle here in terms of how fast we request new consumers + if sinceLast := time.Since(si.lreq); sinceLast < sourceConsumerRetryThreshold { + scheduleDelay = sourceConsumerRetryThreshold - sinceLast + } + // Is it a retry? If so, add a backoff + if si.fails > 0 { + scheduleDelay += calculateRetryBackoff(si.fails) + } + } + + // Always add some jitter + scheduleDelay += time.Duration(rand.Intn(int(100*time.Millisecond))) + 100*time.Millisecond + + // Schedule the call to trySetupSourceConsumer + mset.sourceSetupSchedules[iname] = time.AfterFunc(scheduleDelay, func() { mset.mu.Lock() defer mset.mu.Unlock() - delete(mset.sourceRetries, iname) - mset.setSourceConsumer(iname, seq, startTime) + delete(mset.sourceSetupSchedules, iname) + mset.trySetupSourceConsumer(iname, seq, startTime) }) } +// This is where we will actually try to create a new consumer for the source // Lock should be held. -func (mset *stream) setSourceConsumer(iname string, seq uint64, startTime time.Time) { - // Ignore if closed. - if mset.closed.Load() { +func (mset *stream) trySetupSourceConsumer(iname string, seq uint64, startTime time.Time) { + // Ignore if closed or not leader. + if mset.closed.Load() || !mset.isLeader() { return } @@ -2850,6 +2859,7 @@ func (mset *stream) setSourceConsumer(iname string, seq uint64, startTime time.T if si == nil { return } + // Cancel previous instance if applicable mset.cancelSourceInfo(si) @@ -2858,12 +2868,6 @@ func (mset *stream) setSourceConsumer(iname string, seq uint64, startTime time.T return } - // We want to throttle here in terms of how fast we request new consumers, - // or if the previous is still in progress. - if last := time.Since(si.lreq); last < sourceConsumerRetryThreshold || si.sip { - mset.scheduleSetSourceConsumerRetry(si, seq, startTime) - return - } si.lreq = time.Now() // Determine subjects etc. @@ -2879,13 +2883,14 @@ func (mset *stream) setSourceConsumer(iname string, seq uint64, startTime time.T req := &CreateConsumerRequest{ Stream: si.name, Config: ConsumerConfig{ - DeliverSubject: deliverSubject, - AckPolicy: AckNone, - AckWait: 22 * time.Hour, - MaxDeliver: 1, - Heartbeat: sourceHealthCheckInterval, - FlowControl: true, - Direct: true, + DeliverSubject: deliverSubject, + AckPolicy: AckNone, + AckWait: 22 * time.Hour, + MaxDeliver: 1, + Heartbeat: sourceHealthHB, + FlowControl: true, + Direct: true, + InactiveThreshold: sourceHealthCheckInterval, }, } @@ -2897,20 +2902,23 @@ func (mset *stream) setSourceConsumer(iname string, seq uint64, startTime time.T if ssi.OptStartSeq > 0 { req.Config.OptStartSeq = ssi.OptStartSeq req.Config.DeliverPolicy = DeliverByStartSequence - } else if ssi.OptStartTime != nil { - // Check to see if our configured start is before what we remember. - // Applicable on restart similar to below. - if ssi.OptStartTime.Before(si.start) { - req.Config.OptStartTime = &si.start - } else { - req.Config.OptStartTime = ssi.OptStartTime + } else { + // We have not recovered state so check that configured time is less that our first seq time. + var state StreamState + mset.store.FastState(&state) + if ssi.OptStartTime != nil { + if !state.LastTime.IsZero() && ssi.OptStartTime.Before(state.LastTime) { + req.Config.OptStartTime = &state.LastTime + } else { + req.Config.OptStartTime = ssi.OptStartTime + } + req.Config.DeliverPolicy = DeliverByStartTime + } else if state.FirstSeq > 1 && !state.LastTime.IsZero() { + req.Config.OptStartTime = &state.LastTime + req.Config.DeliverPolicy = DeliverByStartTime } - req.Config.DeliverPolicy = DeliverByStartTime - } else if !si.start.IsZero() { - // We are falling back to time based startup on a recover, but our messages are gone. e.g. purge, expired, retention policy. - req.Config.OptStartTime = &si.start - req.Config.DeliverPolicy = DeliverByStartTime } + } else { req.Config.OptStartSeq = seq req.Config.DeliverPolicy = DeliverByStartSequence @@ -2943,7 +2951,7 @@ func (mset *stream) setSourceConsumer(iname string, seq uint64, startTime time.T }) if err != nil { si.err = NewJSSourceConsumerSetupFailedError(err, Unless(err)) - mset.scheduleSetSourceConsumerRetry(si, seq, startTime) + mset.setupSourceConsumer(iname, seq, startTime) return } @@ -2990,7 +2998,7 @@ func (mset *stream) setSourceConsumer(iname string, seq uint64, startTime time.T si.fails++ // Cancel here since we can not do anything with this consumer at this point. mset.cancelSourceInfo(si) - mset.scheduleSetSourceConsumerRetry(si, seq, startTime) + mset.setupSourceConsumer(iname, seq, startTime) } else { // Clear on success. si.fails = 0 @@ -2999,13 +3007,8 @@ func (mset *stream) setSourceConsumer(iname string, seq uint64, startTime time.T mset.mu.Unlock() }() - // Wait for previous processSourceMsgs go routine to be completely done. - // If none is running, this will not block. - si.wg.Wait() - select { case ccr := <-respCh: - ready := sync.WaitGroup{} mset.mu.Lock() // Check that it has not been removed or canceled (si.sub would be nil) if si := mset.sources[iname]; si != nil { @@ -3021,14 +3024,37 @@ func (mset *stream) setSourceConsumer(iname string, seq uint64, startTime time.T mset.mu.Unlock() return } else { + // Check if our shared msg queue and go routine is running or not. + if mset.smsgs == nil { + qname := fmt.Sprintf("[ACC:%s] stream sources '%s' msgs", mset.acc.Name, mset.cfg.Name) + mset.smsgs = newIPQueue[*inMsg](mset.srv, qname) + mset.srv.startGoRoutine(func() { mset.processAllSourceMsgs() }, + pprofLabels{ + "type": "source", + "account": mset.acc.Name, + "stream": mset.cfg.Name, + }, + ) + } + // Setup actual subscription to process messages from our source. - qname := fmt.Sprintf("[ACC:%s] stream source '%s' from '%s' msgs", mset.acc.Name, mset.cfg.Name, si.name) - // Create a new queue each time - si.msgs = newIPQueue[*inMsg](mset.srv, qname) - msgs := si.msgs + if si.sseq != ccr.ConsumerInfo.Delivered.Stream { + si.sseq = ccr.ConsumerInfo.Delivered.Stream + 1 + } + // Capture consumer name. + si.cname = ccr.ConsumerInfo.Name + + // Do not set si.sseq to seq here. si.sseq will be set in processInboundSourceMsg + si.dseq = 0 + si.qch = make(chan struct{}) + // Set the last seen as now so that we don't fail at the first check. + si.last.Store(time.Now().UnixNano()) + + msgs := mset.smsgs sub, err := mset.subscribeInternal(deliverSubject, func(sub *subscription, c *client, _ *Account, subject, reply string, rmsg []byte) { hdr, msg := c.msgParts(copyBytes(rmsg)) // Need to copy. - mset.queueInbound(msgs, subject, reply, hdr, msg) + mset.queueInbound(msgs, subject, reply, hdr, msg, si) + si.last.Store(time.Now().UnixNano()) }) if err != nil { si.err = NewJSSourceConsumerSetupFailedError(err, Unless(err)) @@ -3038,95 +3064,98 @@ func (mset *stream) setSourceConsumer(iname string, seq uint64, startTime time.T } // Save our sub. si.sub = sub - - if si.sseq != ccr.ConsumerInfo.Delivered.Stream { - si.sseq = ccr.ConsumerInfo.Delivered.Stream + 1 - } - // Capture consumer name. - si.cname = ccr.ConsumerInfo.Name - // Do not set si.sseq to seq here. si.sseq will be set in processInboundSourceMsg - si.dseq = 0 - si.qch = make(chan struct{}) - si.wg.Add(1) - ready.Add(1) - if !mset.srv.startGoRoutine( - func() { mset.processSourceMsgs(si, &ready) }, - pprofLabels{ - "type": "source", - "account": mset.acc.Name, - "stream": mset.cfg.Name, - "consumer": si.cname, - }, - ) { - ready.Done() - } } } mset.mu.Unlock() - ready.Wait() - case <-time.After(5 * time.Second): + case <-time.After(srcConsumerWaitTime): mset.unsubscribe(crSub) - // We already waited 5 seconds, let's retry now. + // We already waited 30 seconds, let's retry now. retry = true } }() } -func (mset *stream) processSourceMsgs(si *sourceInfo, ready *sync.WaitGroup) { +// This will process all inbound source msgs. +// We mux them into one go routine to avoid lock contention and high cpu and thread thrashing. +// TODO(dlc) make this more then one and pin sources to one of a group. +func (mset *stream) processAllSourceMsgs() { s := mset.srv - defer func() { - si.wg.Done() - s.grWG.Done() - }() + defer s.grWG.Done() - // Grab some stream and sourceInfo values now... - mset.mu.Lock() - msgs, qch, siqch, iname := si.msgs, mset.qch, si.qch, si.iname - // Set the last seen as now so that we don't fail at the first check. - si.last = time.Now() - mset.mu.Unlock() - - // Signal the caller that we have captured the above fields. - ready.Done() + mset.mu.RLock() + msgs, qch := mset.smsgs, mset.qch + mset.mu.RUnlock() t := time.NewTicker(sourceHealthCheckInterval) defer t.Stop() + // When we detect we are no longer leader, we will cleanup. + // Should always return right after this is called. + cleanUp := func() { + mset.mu.Lock() + defer mset.mu.Unlock() + for _, si := range mset.sources { + mset.cancelSourceConsumer(si.iname) + } + mset.smsgs.drain() + mset.smsgs.unregister() + mset.smsgs = nil + } + for { select { case <-s.quitCh: return case <-qch: return - case <-siqch: - return case <-msgs.ch: ims := msgs.pop() for _, im := range ims { - if !mset.processInboundSourceMsg(si, im) { + if !mset.processInboundSourceMsg(im.si, im) { + // If we are no longer leader bail. + if !mset.IsLeader() { + cleanUp() + return + } break } + im.returnToPool() } msgs.recycle(&ims) case <-t.C: - mset.mu.RLock() - isLeader := mset.isLeader() - stalled := time.Since(si.last) > 3*sourceHealthCheckInterval - mset.mu.RUnlock() - // No longer leader. - if !isLeader { - mset.mu.Lock() - mset.cancelSourceConsumer(iname) - mset.mu.Unlock() + // If we are no longer leader bail. + if !mset.IsLeader() { + cleanUp() return } - // We are stalled. - if stalled { - mset.mu.Lock() - // We don't need to schedule here, we are going to simply - // call setSourceConsumer with the current state+1. - mset.setSourceConsumer(iname, si.sseq+1, time.Time{}) - mset.mu.Unlock() + + // Check health of all sources. + var stalled []*sourceInfo + mset.mu.RLock() + for _, si := range mset.sources { + if time.Since(time.Unix(0, si.last.Load())) > sourceHealthCheckInterval { + stalled = append(stalled, si) + } + } + numSources := len(mset.sources) + mset.mu.RUnlock() + + // This can happen on an update when no longer have sources. + if numSources == 0 { + cleanUp() + return + } + + // We do not want to block here so do in separate Go routine. + if len(stalled) > 0 { + go func() { + mset.mu.Lock() + defer mset.mu.Unlock() + for _, si := range stalled { + mset.setupSourceConsumer(si.iname, si.sseq+1, time.Time{}) + si.last.Store(time.Now().UnixNano()) + } + }() } } } @@ -3160,7 +3189,6 @@ func (mset *stream) handleFlowControl(m *inMsg) { // processInboundSourceMsg handles processing other stream messages bound for this stream. func (mset *stream) processInboundSourceMsg(si *sourceInfo, m *inMsg) bool { mset.mu.Lock() - // If we are no longer the leader cancel this subscriber. if !mset.isLeader() { mset.cancelSourceConsumer(si.iname) @@ -3176,9 +3204,6 @@ func (mset *stream) processInboundSourceMsg(si *sourceInfo, m *inMsg) bool { return false } - si.last = time.Now() - node := mset.node - // Check for heartbeats and flow control messages. if isControl { var needsRetry bool @@ -3229,6 +3254,7 @@ func (mset *stream) processInboundSourceMsg(si *sourceInfo, m *inMsg) bool { } else { si.lag = pending - 1 } + node := mset.node mset.mu.Unlock() hdr, msg := m.hdr, m.msg @@ -3236,7 +3262,6 @@ 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-") } @@ -3244,15 +3269,16 @@ func (mset *stream) processInboundSourceMsg(si *sourceInfo, m *inMsg) bool { hdr = genHeader(hdr, JSStreamSource, si.genSourceHeader(m.rply)) // Do the subject transform for the source if there's one - - for _, tr := range si.trs { - if tr == nil { - continue - } else { - tsubj, err := tr.Match(m.subj) - if err == nil { - m.subj = tsubj - break + if len(si.trs) > 0 { + for _, tr := range si.trs { + if tr == nil { + continue + } else { + tsubj, err := tr.Match(m.subj) + if err == nil { + m.subj = tsubj + break + } } } } @@ -3272,17 +3298,35 @@ func (mset *stream) processInboundSourceMsg(si *sourceInfo, m *inMsg) bool { s.DisableJetStream() } else { mset.mu.RLock() - accName, sname, iname := mset.acc.Name, mset.cfg.Name, si.iname + accName, sname, iName := mset.acc.Name, mset.cfg.Name, si.iname mset.mu.RUnlock() - // Log some warning for errors other than errLastSeqMismatch - if err != errLastSeqMismatch { - s.RateLimitWarnf("Error processing inbound source %q for '%s' > '%s': %v", - iname, accName, sname, err) + + // Can happen temporarily all the time during normal operations when the sourcing stream + // is working queue/interest with a limit and discard new. + // TODO - Improve sourcing to WQ with limit and new to use flow control rather than re-creating the consumer. + if errors.Is(err, ErrMaxMsgs) { + // Do not need to do a full retry that includes finding the last sequence in the stream + // for that source. Just re-create starting with the seq we couldn't store instead. + mset.mu.Lock() + mset.retrySourceConsumerAtSeq(iName, si.sseq) + mset.mu.Unlock() + } else { + // Log some warning for errors other than errLastSeqMismatch or errMaxMsgs. + if !errors.Is(err, errLastSeqMismatch) { + s.RateLimitWarnf("Error processing inbound source %q for '%s' > '%s': %v", + iName, accName, sname, err) + } + // Retry in all type of errors if we are still leader. + if mset.isLeader() { + // This will make sure the source is still in mset.sources map, + // find the last sequence and then call setupSourceConsumer. + iNameMap := map[string]struct{}{iName: {}} + mset.setStartingSequenceForSources(iNameMap) + mset.mu.Lock() + mset.retrySourceConsumerAtSeq(iName, si.sseq+1) + mset.mu.Unlock() + } } - // Retry in all type of errors. - // This will make sure the source is still in mset.sources map, - // find the last sequence and then call setSourceConsumer. - mset.retrySourceConsumer(iname) } return false } @@ -3410,9 +3454,11 @@ func (mset *stream) setStartingSequenceForSources(iNames map[string]struct{}) { } } -// lock should be held. // Resets the SourceInfo for all the sources +// lock should be held. func (mset *stream) resetSourceInfo() { + // Reset if needed. + mset.stopSourceConsumers() mset.sources = make(map[string]*sourceInfo) for _, ssi := range mset.cfg.Sources { @@ -3457,13 +3503,6 @@ func (mset *stream) startingSequenceForSources() { var state StreamState mset.store.FastState(&state) - // If the last time has been stamped remember in case we need to fall back to this for any given upstream source. - // TODO(dlc) - This will be ok, but should formalize with new approach and more formal and durable state. - if !state.LastTime.IsZero() { - for _, si := range mset.sources { - si.start = state.LastTime - } - } // Bail if no messages, meaning no context. if state.Msgs == 0 { return @@ -3507,15 +3546,15 @@ func (mset *stream) startingSequenceForSources() { } } - streamName, iName, sSeq := streamAndSeq(string(ss)) + streamName, iName, sseq := streamAndSeq(string(ss)) if iName == _EMPTY_ { // Pre-2.10 message header means it's a match for any source using that stream name for _, ssi := range mset.cfg.Sources { if streamName == ssi.Name || (ssi.External != nil && streamName == ssi.Name+":"+getHash(ssi.External.ApiPrefix)) { - update(ssi.iname, sSeq) + update(ssi.iname, sseq) } } } else { - update(iName, sSeq) + update(iName, sseq) } if len(seqs) == expected { return @@ -3546,7 +3585,7 @@ func (mset *stream) setupSourceConsumers() error { // Setup our consumers at the proper starting position. for _, ssi := range mset.cfg.Sources { if si := mset.sources[ssi.iname]; si != nil { - mset.setSourceConsumer(ssi.iname, si.sseq+1, time.Time{}) + mset.setupSourceConsumer(ssi.iname, si.sseq+1, time.Time{}) } } @@ -3585,12 +3624,11 @@ func (mset *stream) subscribeToStream() error { mset.mirror.trs = trs // delay the actual mirror consumer creation for after a delay mset.scheduleSetupMirrorConsumerRetry() - } else if len(mset.cfg.Sources) > 0 { + } else if len(mset.cfg.Sources) > 0 && mset.sourcesConsumerSetup == nil { // Setup the initial source infos for the sources mset.resetSourceInfo() // Delay the actual source consumer(s) creation(s) for after a delay - - mset.sourcesConsumerSetup = time.AfterFunc(time.Duration(rand.Intn(int(10*time.Millisecond)))+10*time.Millisecond, func() { + mset.sourcesConsumerSetup = time.AfterFunc(time.Duration(rand.Intn(int(500*time.Millisecond)))+100*time.Millisecond, func() { mset.mu.Lock() mset.setupSourceConsumers() mset.mu.Unlock() @@ -3989,21 +4027,24 @@ type inMsg struct { rply string hdr []byte msg []byte + si *sourceInfo } -func (mset *stream) queueInbound(ib *ipQueue[*inMsg], subj, rply string, hdr, msg []byte) { - ib.push(&inMsg{subj, rply, hdr, msg}) +var inMsgPool = sync.Pool{ + New: func() any { + return &inMsg{} + }, } -func (mset *stream) queueInboundMsg(subj, rply string, hdr, msg []byte) { - // Copy these. - if len(hdr) > 0 { - hdr = copyBytes(hdr) - } - if len(msg) > 0 { - msg = copyBytes(msg) - } - mset.queueInbound(mset.msgs, subj, rply, hdr, msg) +func (im *inMsg) returnToPool() { + im.subj, im.rply, im.hdr, im.msg, im.si = _EMPTY_, _EMPTY_, nil, nil, nil + inMsgPool.Put(im) +} + +func (mset *stream) queueInbound(ib *ipQueue[*inMsg], subj, rply string, hdr, msg []byte, si *sourceInfo) { + im := inMsgPool.Get().(*inMsg) + im.subj, im.rply, im.hdr, im.msg, im.si = subj, rply, hdr, msg, si + ib.push(im) } var dgPool = sync.Pool{ @@ -4150,8 +4191,8 @@ func (mset *stream) getDirectRequest(req *JSApiMsgGetRequest, reply string) { // processInboundJetStreamMsg handles processing messages bound for a stream. func (mset *stream) processInboundJetStreamMsg(_ *subscription, c *client, _ *Account, subject, reply string, rmsg []byte) { - hdr, msg := c.msgParts(rmsg) - mset.queueInboundMsg(subject, reply, hdr, msg) + hdr, msg := c.msgParts(copyBytes(rmsg)) // Need to copy. + mset.queueInbound(mset.msgs, subject, reply, hdr, msg, nil) } var ( @@ -4437,20 +4478,9 @@ func (mset *stream) processJetStreamMsg(subject, reply string, hdr, msg []byte, // If we are interest based retention and have no consumers then we can skip. if interestRetention { - if numConsumers == 0 { - noInterest = true - } else if mset.numFilter > 0 { - // Assume no interest and check to disqualify. - noInterest = true - mset.clsMu.RLock() - for _, o := range mset.cList { - if o.cfg.FilterSubject == _EMPTY_ || subjectIsSubsetMatch(subject, o.cfg.FilterSubject) { - noInterest = false - break - } - } - mset.clsMu.RUnlock() - } + mset.clsMu.RLock() + noInterest = numConsumers == 0 || mset.csl == nil || !mset.csl.HasInterest(subject) + mset.clsMu.RUnlock() } // Grab timestamp if not already set. @@ -4935,6 +4965,7 @@ func (mset *stream) internalLoop() { } else { mset.processJetStreamMsg(im.subj, im.rply, im.hdr, im.msg, 0, 0) } + im.returnToPool() } msgs.recycle(&ims) case <-gets.ch: @@ -5108,6 +5139,7 @@ func (mset *stream) stop(deleteFlag, advisory bool) error { mset.ackq.unregister() mset.outq.unregister() mset.sigq.unregister() + mset.smsgs.unregister() } // Snapshot store. @@ -5317,6 +5349,12 @@ func (mset *stream) setConsumer(o *consumer) { // Now update consumers list as well mset.clsMu.Lock() mset.cList = append(mset.cList, o) + if mset.csl == nil { + mset.csl = NewSublistWithCache() + } + for _, sub := range o.signalSubs() { + mset.csl.Insert(sub) + } mset.clsMu.Unlock() } @@ -5348,30 +5386,6 @@ func (mset *stream) removeConsumer(o *consumer) { } } -// Set the consumer as a leader. This will update signaling sublist. -func (mset *stream) setConsumerAsLeader(o *consumer) { - mset.clsMu.Lock() - defer mset.clsMu.Unlock() - - if mset.csl == nil { - mset.csl = NewSublistWithCache() - } - for _, sub := range o.signalSubs() { - mset.csl.Insert(sub) - } -} - -// Remove the consumer as a leader. This will update signaling sublist. -func (mset *stream) removeConsumerAsLeader(o *consumer) { - mset.clsMu.Lock() - defer mset.clsMu.Unlock() - if mset.csl != nil { - for _, sub := range o.signalSubs() { - mset.csl.Remove(sub) - } - } -} - // swapSigSubs will update signal Subs for a new subject filter. // consumer lock should not be held. func (mset *stream) swapSigSubs(o *consumer, newFilters []string) { diff --git a/vendor/github.com/nats-io/nats-server/v2/server/stree/dump.go b/vendor/github.com/nats-io/nats-server/v2/server/stree/dump.go index 79b6fd99f8..4a7d76fb58 100644 --- a/vendor/github.com/nats-io/nats-server/v2/server/stree/dump.go +++ b/vendor/github.com/nats-io/nats-server/v2/server/stree/dump.go @@ -38,7 +38,7 @@ func (t *SubjectTree[T]) dump(w io.Writer, n node, depth int) { } else { // We are a node type here, grab meta portion. bn := n.base() - fmt.Fprintf(w, "%s %s Prefix: %q\n", dumpPre(depth), n.kind(), bn.prefix[:bn.prefixLen]) + fmt.Fprintf(w, "%s %s Prefix: %q\n", dumpPre(depth), n.kind(), bn.prefix) depth++ n.iter(func(n node) bool { t.dump(w, n, depth) diff --git a/vendor/github.com/nats-io/nats-server/v2/server/stree/node.go b/vendor/github.com/nats-io/nats-server/v2/server/stree/node.go index 6285ba3cee..c8edfe3ea9 100644 --- a/vendor/github.com/nats-io/nats-server/v2/server/stree/node.go +++ b/vendor/github.com/nats-io/nats-server/v2/server/stree/node.go @@ -32,13 +32,22 @@ type node interface { path() []byte } -// Maximum prefix len -// We expect the most savings to come from long shared prefixes. -const maxPrefixLen = 24 - -// 64 bytes total - an L1 cache line. type meta struct { - prefix [maxPrefixLen]byte - prefixLen uint16 - size uint16 + prefix []byte + size uint16 +} + +func (n *meta) isLeaf() bool { return false } +func (n *meta) base() *meta { return n } + +func (n *meta) setPrefix(pre []byte) { + n.prefix = append([]byte(nil), pre...) +} + +func (n *meta) numChildren() uint16 { return n.size } +func (n *meta) path() []byte { return n.prefix } + +// Will match parts against our prefix. +func (n *meta) matchParts(parts [][]byte) ([][]byte, bool) { + return matchParts(parts, n.prefix) } diff --git a/vendor/github.com/nats-io/nats-server/v2/server/stree/node16.go b/vendor/github.com/nats-io/nats-server/v2/server/stree/node16.go index b61f286dcd..2d206afda7 100644 --- a/vendor/github.com/nats-io/nats-server/v2/server/stree/node16.go +++ b/vendor/github.com/nats-io/nats-server/v2/server/stree/node16.go @@ -26,16 +26,6 @@ func newNode16(prefix []byte) *node16 { return nn } -func (n *node16) isLeaf() bool { return false } -func (n *node16) base() *meta { return &n.meta } - -func (n *node16) setPrefix(pre []byte) { - n.prefixLen = uint16(min(len(pre), maxPrefixLen)) - for i := uint16(0); i < n.prefixLen; i++ { - n.prefix[i] = pre[i] - } -} - // Currently we do not keep node16 sorted or use bitfields for traversal so just add to the end. // TODO(dlc) - We should revisit here with more detailed benchmarks. func (n *node16) addChild(c byte, nn node) { @@ -47,9 +37,6 @@ func (n *node16) addChild(c byte, nn node) { n.size++ } -func (n *node16) numChildren() uint16 { return n.size } -func (n *node16) path() []byte { return n.prefix[:n.prefixLen] } - func (n *node16) findChild(c byte) *node { for i := uint16(0); i < n.size; i++ { if n.key[i] == c { @@ -62,7 +49,7 @@ func (n *node16) findChild(c byte) *node { func (n *node16) isFull() bool { return n.size >= 16 } func (n *node16) grow() node { - nn := newNode256(n.prefix[:n.prefixLen]) + nn := newNode256(n.prefix) for i := 0; i < 16; i++ { nn.addChild(n.key[i], n.child[i]) } @@ -101,11 +88,6 @@ func (n *node16) shrink() node { return nn } -// Will match parts against our prefix.no -func (n *node16) matchParts(parts [][]byte) ([][]byte, bool) { - return matchParts(parts, n.prefix[:n.prefixLen]) -} - // Iterate over all children calling func f. func (n *node16) iter(f func(node) bool) { for i := uint16(0); i < n.size; i++ { diff --git a/vendor/github.com/nats-io/nats-server/v2/server/stree/node256.go b/vendor/github.com/nats-io/nats-server/v2/server/stree/node256.go index c7bcd33620..fdadde0bc0 100644 --- a/vendor/github.com/nats-io/nats-server/v2/server/stree/node256.go +++ b/vendor/github.com/nats-io/nats-server/v2/server/stree/node256.go @@ -25,24 +25,11 @@ func newNode256(prefix []byte) *node256 { return nn } -func (n *node256) isLeaf() bool { return false } -func (n *node256) base() *meta { return &n.meta } - -func (n *node256) setPrefix(pre []byte) { - n.prefixLen = uint16(min(len(pre), maxPrefixLen)) - for i := uint16(0); i < n.prefixLen; i++ { - n.prefix[i] = pre[i] - } -} - func (n *node256) addChild(c byte, nn node) { n.child[c] = nn n.size++ } -func (n *node256) numChildren() uint16 { return n.size } -func (n *node256) path() []byte { return n.prefix[:n.prefixLen] } - func (n *node256) findChild(c byte) *node { if n.child[c] != nil { return &n.child[c] @@ -75,11 +62,6 @@ func (n *node256) shrink() node { return nn } -// Will match parts against our prefix. -func (n *node256) matchParts(parts [][]byte) ([][]byte, bool) { - return matchParts(parts, n.prefix[:n.prefixLen]) -} - // Iterate over all children calling func f. func (n *node256) iter(f func(node) bool) { for i := 0; i < 256; i++ { diff --git a/vendor/github.com/nats-io/nats-server/v2/server/stree/node4.go b/vendor/github.com/nats-io/nats-server/v2/server/stree/node4.go index 195299e266..2d48962545 100644 --- a/vendor/github.com/nats-io/nats-server/v2/server/stree/node4.go +++ b/vendor/github.com/nats-io/nats-server/v2/server/stree/node4.go @@ -26,16 +26,6 @@ func newNode4(prefix []byte) *node4 { return nn } -func (n *node4) isLeaf() bool { return false } -func (n *node4) base() *meta { return &n.meta } - -func (n *node4) setPrefix(pre []byte) { - n.prefixLen = uint16(min(len(pre), maxPrefixLen)) - for i := uint16(0); i < n.prefixLen; i++ { - n.prefix[i] = pre[i] - } -} - // Currently we do not need to keep sorted for traversal so just add to the end. func (n *node4) addChild(c byte, nn node) { if n.size >= 4 { @@ -46,9 +36,6 @@ func (n *node4) addChild(c byte, nn node) { n.size++ } -func (n *node4) numChildren() uint16 { return n.size } -func (n *node4) path() []byte { return n.prefix[:n.prefixLen] } - func (n *node4) findChild(c byte) *node { for i := uint16(0); i < n.size; i++ { if n.key[i] == c { @@ -61,7 +48,7 @@ func (n *node4) findChild(c byte) *node { func (n *node4) isFull() bool { return n.size >= 4 } func (n *node4) grow() node { - nn := newNode16(n.prefix[:n.prefixLen]) + nn := newNode16(n.prefix) for i := 0; i < 4; i++ { nn.addChild(n.key[i], n.child[i]) } @@ -96,11 +83,6 @@ func (n *node4) shrink() node { return nil } -// Will match parts against our prefix. -func (n *node4) matchParts(parts [][]byte) ([][]byte, bool) { - return matchParts(parts, n.prefix[:n.prefixLen]) -} - // Iterate over all children calling func f. func (n *node4) iter(f func(node) bool) { for i := uint16(0); i < n.size; i++ { diff --git a/vendor/github.com/nats-io/nats-server/v2/server/stree/parts.go b/vendor/github.com/nats-io/nats-server/v2/server/stree/parts.go index 76c260031e..62236c5ad7 100644 --- a/vendor/github.com/nats-io/nats-server/v2/server/stree/parts.go +++ b/vendor/github.com/nats-io/nats-server/v2/server/stree/parts.go @@ -45,6 +45,14 @@ func genParts(filter []byte, parts [][]byte) [][]byte { start = i + 1 } } else if filter[i] == pwc || filter[i] == fwc { + // Wildcard must be at the start or preceded by tsep. + if prev := i - 1; prev >= 0 && filter[prev] != tsep { + continue + } + // Wildcard must be at the end or followed by tsep. + if next := i + 1; next == e || next < e && filter[next] != tsep { + continue + } // We start with a pwc or fwc. parts = append(parts, filter[i:i+1]) if i+1 <= e { diff --git a/vendor/github.com/nats-io/nats-server/v2/server/stree/stree.go b/vendor/github.com/nats-io/nats-server/v2/server/stree/stree.go index ad75eb5fd4..b6531924de 100644 --- a/vendor/github.com/nats-io/nats-server/v2/server/stree/stree.go +++ b/vendor/github.com/nats-io/nats-server/v2/server/stree/stree.go @@ -60,7 +60,7 @@ func (t *SubjectTree[T]) Insert(subject []byte, value T) (*T, bool) { // Find will find the value and return it or false if it was not found. func (t *SubjectTree[T]) Find(subject []byte) (*T, bool) { - var si uint16 + var si int for n := t.root; n != nil; { if n.isLeaf() { if ln := n.(*leaf[T]); ln.match(subject[si:]) { @@ -69,13 +69,13 @@ func (t *SubjectTree[T]) Find(subject []byte) (*T, bool) { return nil, false } // We are a node type here, grab meta portion. - if bn := n.base(); bn.prefixLen > 0 { - end := min(int(si+bn.prefixLen), len(subject)) - if !bytes.Equal(subject[si:end], bn.prefix[:bn.prefixLen]) { + if bn := n.base(); len(bn.prefix) > 0 { + end := min(si+len(bn.prefix), len(subject)) + if !bytes.Equal(subject[si:end], bn.prefix) { return nil, false } // Increment our subject index. - si += bn.prefixLen + si += len(bn.prefix) } if an := n.findChild(pivot(subject, si)); an != nil { n = *an @@ -157,9 +157,9 @@ func (t *SubjectTree[T]) insert(np *node, subject []byte, value T, si int) (*T, // Non-leaf nodes. bn := n.base() - if bn.prefixLen > 0 { - cpi := commonPrefixLen(bn.prefix[:bn.prefixLen], subject[si:]) - if pli := int(bn.prefixLen); cpi >= pli { + if len(bn.prefix) > 0 { + cpi := commonPrefixLen(bn.prefix, subject[si:]) + if pli := len(bn.prefix); cpi >= pli { // Move past this node. We look for an existing child node to recurse into. // If one does not exist we can create a new leaf node. si += pli @@ -180,7 +180,7 @@ func (t *SubjectTree[T]) insert(np *node, subject []byte, value T, si int) (*T, // We will insert a new node4 and attach our current node below after adjusting prefix. nn := newNode4(prefix) // Shift the prefix for our original node. - n.setPrefix(bn.prefix[cpi:bn.prefixLen]) + n.setPrefix(bn.prefix[cpi:]) nn.addChild(pivot(bn.prefix[:], 0), n) // Add in our new leaf. nn.addChild(pivot(subject[si:], 0), newLeaf(subject[si:], value)) @@ -203,7 +203,7 @@ func (t *SubjectTree[T]) insert(np *node, subject []byte, value T, si int) (*T, } // internal function to recursively find the leaf to delete. Will do compaction if the item is found and removed. -func (t *SubjectTree[T]) delete(np *node, subject []byte, si uint16) (*T, bool) { +func (t *SubjectTree[T]) delete(np *node, subject []byte, si int) (*T, bool) { if t == nil || np == nil || *np == nil || len(subject) == 0 { return nil, false } @@ -217,12 +217,12 @@ func (t *SubjectTree[T]) delete(np *node, subject []byte, si uint16) (*T, bool) return nil, false } // Not a leaf node. - if bn := n.base(); bn.prefixLen > 0 { - if !bytes.Equal(subject[si:si+bn.prefixLen], bn.prefix[:bn.prefixLen]) { + if bn := n.base(); len(bn.prefix) > 0 { + if !bytes.Equal(subject[si:si+len(bn.prefix)], bn.prefix) { return nil, false } // Increment our subject index. - si += bn.prefixLen + si += len(bn.prefix) } p := pivot(subject, si) nna := n.findChild(p) @@ -238,7 +238,7 @@ func (t *SubjectTree[T]) delete(np *node, subject []byte, si uint16) (*T, bool) if sn := n.shrink(); sn != nil { bn := n.base() // Make sure to set cap so we force an append to copy below. - pre := bn.prefix[:bn.prefixLen:bn.prefixLen] + pre := bn.prefix[:len(bn.prefix):len(bn.prefix)] // Need to fix up prefixes/suffixes. if sn.isLeaf() { ln := sn.(*leaf[T]) @@ -248,7 +248,7 @@ func (t *SubjectTree[T]) delete(np *node, subject []byte, si uint16) (*T, bool) // We are a node here, we need to add in the old prefix. if len(pre) > 0 { bsn := sn.base() - sn.setPrefix(append(pre, bsn.prefix[:bsn.prefixLen]...)) + sn.setPrefix(append(pre, bsn.prefix...)) } } *np = sn @@ -287,9 +287,9 @@ func (t *SubjectTree[T]) match(n node, parts [][]byte, pre []byte, cb func(subje // We have normal nodes here. // We need to append our prefix bn := n.base() - if bn.prefixLen > 0 { + if len(bn.prefix) > 0 { // Note that this append may reallocate, but it doesn't modify "pre" at the "match" callsite. - pre = append(pre, bn.prefix[:bn.prefixLen]...) + pre = append(pre, bn.prefix...) } // Check our remaining parts. @@ -359,7 +359,7 @@ func (t *SubjectTree[T]) iter(n node, pre []byte, cb func(subject []byte, val *T // We are normal node here. bn := n.base() // Note that this append may reallocate, but it doesn't modify "pre" at the "iter" callsite. - pre = append(pre, bn.prefix[:bn.prefixLen]...) + pre = append(pre, bn.prefix...) // Collect nodes since unsorted. var _nodes [256]node nodes := _nodes[:0] diff --git a/vendor/github.com/nats-io/nats-server/v2/server/stree/util.go b/vendor/github.com/nats-io/nats-server/v2/server/stree/util.go index 5ced54bc95..585800aba0 100644 --- a/vendor/github.com/nats-io/nats-server/v2/server/stree/util.go +++ b/vendor/github.com/nats-io/nats-server/v2/server/stree/util.go @@ -29,7 +29,7 @@ func commonPrefixLen(s1, s2 []byte) int { break } } - return min(i, maxPrefixLen) + return i } // Helper to copy bytes. diff --git a/vendor/github.com/nats-io/nats-server/v2/server/subject_transform.go b/vendor/github.com/nats-io/nats-server/v2/server/subject_transform.go index e7d7257617..8292d48732 100644 --- a/vendor/github.com/nats-io/nats-server/v2/server/subject_transform.go +++ b/vendor/github.com/nats-io/nats-server/v2/server/subject_transform.go @@ -31,6 +31,8 @@ var ( sliceFromLeftMappingFunctionRegEx = regexp.MustCompile(`{{\s*[sS]lice[fF]rom[lL]eft\s*\((.*)\)\s*}}`) sliceFromRightMappingFunctionRegEx = regexp.MustCompile(`{{\s*[sS]lice[fF]rom[rR]ight\s*\((.*)\)\s*}}`) splitMappingFunctionRegEx = regexp.MustCompile(`{{\s*[sS]plit\s*\((.*)\)\s*}}`) + leftMappingFunctionRegEx = regexp.MustCompile(`{{\s*[lL]eft\s*\((.*)\)\s*}}`) + rightMappingFunctionRegEx = regexp.MustCompile(`{{\s*[rR]ight\s*\((.*)\)\s*}}`) ) // Enum for the subject mapping subjectTransform function types @@ -44,6 +46,8 @@ const ( SliceFromLeft SliceFromRight Split + Left + Right ) // Transforms for arbitrarily mapping subjects from one to another for maps, tees and filters. @@ -297,6 +301,18 @@ func indexPlaceHolders(token string) (int16, []int, int32, string, error) { return transformIndexIntArgsHelper(token, args, SliceFromRight) } + // Right(token, length) + args = getMappingFunctionArgs(rightMappingFunctionRegEx, token) + if args != nil { + return transformIndexIntArgsHelper(token, args, Right) + } + + // Left(token, length) + args = getMappingFunctionArgs(leftMappingFunctionRegEx, token) + if args != nil { + return transformIndexIntArgsHelper(token, args, Left) + } + // split(token, deliminator) args = getMappingFunctionArgs(splitMappingFunctionRegEx, token) if args != nil { @@ -515,6 +531,24 @@ func (tr *subjectTransform) TransformTokenizedSubject(tokens []string) string { b.WriteString(tsep) } } + case Left: + sourceToken := tokens[tr.dtokmftokindexesargs[i][0]] + sourceTokenLen := len(sourceToken) + sliceSize := int(tr.dtokmfintargs[i]) + if sliceSize > 0 && sliceSize < sourceTokenLen { + b.WriteString(sourceToken[0:sliceSize]) + } else { // too small to slice at the requested size: don't slice + b.WriteString(sourceToken) + } + case Right: + sourceToken := tokens[tr.dtokmftokindexesargs[i][0]] + sourceTokenLen := len(sourceToken) + sliceSize := int(tr.dtokmfintargs[i]) + if sliceSize > 0 && sliceSize < sourceTokenLen { + b.WriteString(sourceToken[sourceTokenLen-sliceSize : sourceTokenLen]) + } else { // too small to slice at the requested size: don't slice + b.WriteString(sourceToken) + } } } diff --git a/vendor/github.com/nats-io/nats-server/v2/server/sublist.go b/vendor/github.com/nats-io/nats-server/v2/server/sublist.go index ebd7e69def..0000ad9f9a 100644 --- a/vendor/github.com/nats-io/nats-server/v2/server/sublist.go +++ b/vendor/github.com/nats-io/nats-server/v2/server/sublist.go @@ -530,6 +530,12 @@ func (s *Sublist) Match(subject string) *SublistResult { return s.match(subject, true) } +// HasInterest will return whether or not there is any interest in the subject. +// In cases where more detail is not required, this may be faster than Match. +func (s *Sublist) HasInterest(subject string) bool { + return s.hasInterest(subject, true) +} + func (s *Sublist) matchNoLock(subject string) *SublistResult { return s.match(subject, false) } @@ -608,6 +614,49 @@ func (s *Sublist) match(subject string, doLock bool) *SublistResult { return result } +func (s *Sublist) hasInterest(subject string, doLock bool) bool { + // Check cache first. + if doLock { + s.RLock() + } + var matched bool + if s.cache != nil { + if r, ok := s.cache[subject]; ok { + matched = len(r.psubs)+len(r.qsubs) > 0 + } + } + if doLock { + s.RUnlock() + } + if matched { + atomic.AddUint64(&s.cacheHits, 1) + return true + } + + tsa := [32]string{} + tokens := tsa[:0] + start := 0 + for i := 0; i < len(subject); i++ { + if subject[i] == btsep { + if i-start == 0 { + return false + } + tokens = append(tokens, subject[start:i]) + start = i + 1 + } + } + if start >= len(subject) { + return false + } + tokens = append(tokens, subject[start:]) + + if doLock { + s.RLock() + defer s.RUnlock() + } + return matchLevelForAny(s.root, tokens) +} + // Remove entries in the cache until we are under the maximum. // TODO(dlc) this could be smarter now that its not inline. func (s *Sublist) reduceCacheCount() { @@ -720,6 +769,36 @@ func matchLevel(l *level, toks []string, results *SublistResult) { } } +func matchLevelForAny(l *level, toks []string) bool { + var pwc, n *node + for i, t := range toks { + if l == nil { + return false + } + if l.fwc != nil { + return true + } + if pwc = l.pwc; pwc != nil { + if match := matchLevelForAny(pwc.next, toks[i+1:]); match { + return true + } + } + n = l.nodes[t] + if n != nil { + l = n.next + } else { + l = nil + } + } + if n != nil { + return len(n.plist) > 0 || len(n.psubs) > 0 || len(n.qsubs) > 0 + } + if pwc != nil { + return len(pwc.plist) > 0 || len(pwc.psubs) > 0 || len(pwc.qsubs) > 0 + } + return false +} + // lnt is used to track descent into levels for a removal for pruning. type lnt struct { l *level diff --git a/vendor/github.com/nats-io/nats-server/v2/server/websocket.go b/vendor/github.com/nats-io/nats-server/v2/server/websocket.go index e026674d9f..1752942303 100644 --- a/vendor/github.com/nats-io/nats-server/v2/server/websocket.go +++ b/vendor/github.com/nats-io/nats-server/v2/server/websocket.go @@ -125,12 +125,17 @@ type srvWebsocket struct { server *http.Server listener net.Listener listenerErr error - tls bool allowedOrigins map[string]*allowedOrigin // host will be the key sameOrigin bool connectURLs []string connectURLsMap refCountedUrlSet authOverride bool // indicate if there is auth override in websocket config + + // These are immutable and can be accessed without lock. + // This is the case when generating the client INFO. + tls bool // True if TLS is required (TLSConfig is specified). + host string // Host/IP the webserver is listening on (shortcut to opts.Websocket.Host). + port int // Port the webserver is listening on. This is after an ephemeral port may have been selected (shortcut to opts.Websocket.Port). } type allowedOrigin struct { @@ -1102,7 +1107,12 @@ func (s *Server) startWebsocketServer() { s.Warnf("Websocket not configured with TLS. DO NOT USE IN PRODUCTION!") } - s.websocket.tls = proto == "wss" + // These 3 are immutable and will be accessed without lock by the client + // when generating/sending the INFO protocols. + s.websocket.tls = proto == wsSchemePrefixTLS + s.websocket.host, s.websocket.port = o.Host, o.Port + + // This will be updated when/if the cluster changes. s.websocket.connectURLs, err = s.getConnectURLs(o.Advertise, o.Host, o.Port) if err != nil { s.Fatalf("Unable to get websocket connect URLs: %v", err) @@ -1141,8 +1151,10 @@ func (s *Server) startWebsocketServer() { ReadTimeout: o.HandshakeTimeout, ErrorLog: log.New(&captureHTTPServerLog{s, "websocket: "}, _EMPTY_, 0), } + s.websocket.mu.Lock() s.websocket.server = hs s.websocket.listener = hl + s.websocket.mu.Unlock() go func() { if err := hs.Serve(hl); err != http.ErrServerClosed { s.Fatalf("websocket listener error: %v", err) diff --git a/vendor/modules.txt b/vendor/modules.txt index 826523a6f0..f1cd524de8 100644 --- a/vendor/modules.txt +++ b/vendor/modules.txt @@ -1406,10 +1406,10 @@ github.com/mohae/deepcopy # github.com/mschoch/smat v0.2.0 ## explicit; go 1.13 github.com/mschoch/smat -# github.com/nats-io/jwt/v2 v2.5.5 +# github.com/nats-io/jwt/v2 v2.5.6 ## explicit; go 1.18 github.com/nats-io/jwt/v2 -# github.com/nats-io/nats-server/v2 v2.10.14 +# github.com/nats-io/nats-server/v2 v2.10.15 ## explicit; go 1.20 github.com/nats-io/nats-server/v2/conf github.com/nats-io/nats-server/v2/internal/fastrand