Bump github.com/nats-io/nats-server/v2 from 2.10.4 to 2.10.5

Bumps [github.com/nats-io/nats-server/v2](https://github.com/nats-io/nats-server) from 2.10.4 to 2.10.5.
- [Release notes](https://github.com/nats-io/nats-server/releases)
- [Changelog](https://github.com/nats-io/nats-server/blob/main/.goreleaser.yml)
- [Commits](https://github.com/nats-io/nats-server/compare/v2.10.4...v2.10.5)

---
updated-dependencies:
- dependency-name: github.com/nats-io/nats-server/v2
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
This commit is contained in:
dependabot[bot]
2023-11-27 10:48:34 +01:00
committed by Ralf Haferkamp
parent c017037c71
commit 1ca166ff48
18 changed files with 1066 additions and 665 deletions
+3 -3
View File
@@ -60,7 +60,7 @@ require (
github.com/mitchellh/mapstructure v1.5.0 github.com/mitchellh/mapstructure v1.5.0
github.com/mna/pigeon v1.2.1 github.com/mna/pigeon v1.2.1
github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826 github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826
github.com/nats-io/nats-server/v2 v2.10.4 github.com/nats-io/nats-server/v2 v2.10.5
github.com/oklog/run v1.1.0 github.com/oklog/run v1.1.0
github.com/olekukonko/tablewriter v0.0.5 github.com/olekukonko/tablewriter v0.0.5
github.com/onsi/ginkgo v1.16.5 github.com/onsi/ginkgo v1.16.5
@@ -272,7 +272,7 @@ require (
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
github.com/modern-go/reflect2 v1.0.2 // indirect github.com/modern-go/reflect2 v1.0.2 // indirect
github.com/mschoch/smat v0.2.0 // indirect github.com/mschoch/smat v0.2.0 // indirect
github.com/nats-io/jwt/v2 v2.5.2 // indirect github.com/nats-io/jwt/v2 v2.5.3 // indirect
github.com/nats-io/nats.go v1.31.0 // indirect github.com/nats-io/nats.go v1.31.0 // indirect
github.com/nats-io/nkeys v0.4.6 // indirect github.com/nats-io/nkeys v0.4.6 // indirect
github.com/nats-io/nuid v1.0.1 // indirect github.com/nats-io/nuid v1.0.1 // indirect
@@ -331,7 +331,7 @@ require (
golang.org/x/exp v0.0.0-20230905200255-921286631fa9 // indirect golang.org/x/exp v0.0.0-20230905200255-921286631fa9 // indirect
golang.org/x/mod v0.13.0 // indirect golang.org/x/mod v0.13.0 // indirect
golang.org/x/sys v0.14.0 // indirect golang.org/x/sys v0.14.0 // indirect
golang.org/x/time v0.3.0 // indirect golang.org/x/time v0.4.0 // indirect
golang.org/x/tools v0.14.0 // indirect golang.org/x/tools v0.14.0 // indirect
golang.org/x/xerrors v0.0.0-20220907171357-04be3eba64a2 // indirect golang.org/x/xerrors v0.0.0-20220907171357-04be3eba64a2 // indirect
google.golang.org/appengine v1.6.8 // indirect google.golang.org/appengine v1.6.8 // indirect
+6 -5
View File
@@ -1739,10 +1739,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-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/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/namedotcom/go v0.0.0-20180403034216-08470befbe04/go.mod h1:5sN+Lt1CaY4wsPvgQH/jsuJi4XO2ssZbdsIizr4CVC8=
github.com/nats-io/jwt/v2 v2.5.2 h1:DhGH+nKt+wIkDxM6qnVSKjokq5t59AZV5HRcFW0zJwU= github.com/nats-io/jwt/v2 v2.5.3 h1:/9SWvzc6hTfamcgXJ3uYRpgj+QuY2aLNqRiqrKcrpEo=
github.com/nats-io/jwt/v2 v2.5.2/go.mod h1:24BeQtRwxRV8ruvC4CojXlx/WQ/VjuwlYiH+vu/+ibI= github.com/nats-io/jwt/v2 v2.5.3/go.mod h1:iysuPemFcc7p4IoYots3IuELSI4EDe9Y0bQMe+I3Bf4=
github.com/nats-io/nats-server/v2 v2.10.4 h1:uB9xcwon3tPXWAdmTJqqqC6cie3yuPWHJjjTBgaPNus= github.com/nats-io/nats-server/v2 v2.10.5 h1:hhWt6m9ja/mNnm6ixc85jCthDaiUFPaeJI79K/MD980=
github.com/nats-io/nats-server/v2 v2.10.4/go.mod h1:eWm2JmHP9Lqm2oemB6/XGi0/GwsZwtWf8HIPUsh+9ns= github.com/nats-io/nats-server/v2 v2.10.5/go.mod h1:xUMTU4kS//SDkJCSvFwN9SyJ9nUuLhSkzB/Qz0dvjjg=
github.com/nats-io/nats.go v1.31.0 h1:/WFBHEc/dOKBF6qf1TZhrdEfTmOZ5JzdJ+Y3m6Y/p7E= github.com/nats-io/nats.go v1.31.0 h1:/WFBHEc/dOKBF6qf1TZhrdEfTmOZ5JzdJ+Y3m6Y/p7E=
github.com/nats-io/nats.go v1.31.0/go.mod h1:di3Bm5MLsoB4Bx61CBTsxuarI36WbhAwOm8QrW39+i8= github.com/nats-io/nats.go v1.31.0/go.mod h1:di3Bm5MLsoB4Bx61CBTsxuarI36WbhAwOm8QrW39+i8=
github.com/nats-io/nkeys v0.4.6 h1:IzVe95ru2CT6ta874rt9saQRkWfe2nFj1NtvYSLqMzY= github.com/nats-io/nkeys v0.4.6 h1:IzVe95ru2CT6ta874rt9saQRkWfe2nFj1NtvYSLqMzY=
@@ -2548,8 +2548,9 @@ golang.org/x/time v0.0.0-20201208040808-7e3f01d25324/go.mod h1:tRJNPiyCQ0inRvYxb
golang.org/x/time v0.0.0-20210220033141-f8bda1e9f3ba/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20210220033141-f8bda1e9f3ba/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/time v0.0.0-20220922220347-f3bd1da661af/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20220922220347-f3bd1da661af/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/time v0.1.0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.1.0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/time v0.3.0 h1:rg5rLMjNzMS1RkNLzCG38eapWhnYLFYXDXj2gOlr8j4=
golang.org/x/time v0.3.0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.3.0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/time v0.4.0 h1:Z81tqI5ddIoXDPvVQ7/7CC9TnLM7ubaFG2qXYd5BbYY=
golang.org/x/time v0.4.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM=
golang.org/x/tools v0.0.0-20180221164845-07fd8470d635/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20180221164845-07fd8470d635/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20180525024113-a5b4c53f6e8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20180525024113-a5b4c53f6e8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20180828015842-6cd1fcedba52/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20180828015842-6cd1fcedba52/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
+5 -1
View File
@@ -136,8 +136,12 @@ func ValidateOperatorServiceURL(v string) error {
return nil return nil
case "tls": case "tls":
return nil return nil
case "ws":
return nil
case "wss":
return nil
default: default:
return fmt.Errorf("operator service url %q - protocol not supported (only 'nats' or 'tls' only)", v) return fmt.Errorf("operator service url %q - protocol not supported (only 'nats', 'tls', 'ws', 'wss' only)", v)
} }
} }
+1 -1
View File
@@ -525,7 +525,7 @@ func processUserPermissionsTemplate(lim jwt.UserPermissionLimits, ujwt *jwt.User
for _, valueList := range nArrayCartesianProduct(tagValues...) { for _, valueList := range nArrayCartesianProduct(tagValues...) {
b := strings.Builder{} b := strings.Builder{}
for i, token := range newTokens { for i, token := range newTokens {
if token == _EMPTY_ { if token == _EMPTY_ && len(valueList) > 0 {
b.WriteString(valueList[0]) b.WriteString(valueList[0])
valueList = valueList[1:] valueList = valueList[1:]
} else { } else {
+22 -25
View File
@@ -1,4 +1,4 @@
// Copyright 2012-2022 The NATS Authors // Copyright 2012-2023 The NATS Authors
// Licensed under the Apache License, Version 2.0 (the "License"); // Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License. // you may not use this file except in compliance with the License.
// You may obtain a copy of the License at // You may obtain a copy of the License at
@@ -1340,6 +1340,7 @@ func (c *client) readLoop(pre []byte) {
c.Errorf("read error: %v", err) c.Errorf("read error: %v", err)
} }
c.closeConnection(closedStateForErr(err)) c.closeConnection(closedStateForErr(err))
return
} else if bufs == nil { } else if bufs == nil {
continue continue
} }
@@ -1498,15 +1499,6 @@ func (c *client) collapsePtoNB() (net.Buffers, int64) {
return c.out.nb, c.out.pb return c.out.nb, c.out.pb
} }
// This will handle the fixup needed on a partial write.
// Assume pending has been already calculated correctly.
func (c *client) handlePartialWrite(pnb net.Buffers) {
if c.isWebsocket() {
c.ws.frames = append(pnb, c.ws.frames...)
return
}
}
// flushOutbound will flush outbound buffer to a client. // flushOutbound will flush outbound buffer to a client.
// Will return true if data was attempted to be written. // Will return true if data was attempted to be written.
// Lock must be held // Lock must be held
@@ -1677,12 +1669,6 @@ func (c *client) flushOutbound() bool {
c.ws.fs -= n c.ws.fs -= n
} }
// Check for partial writes
// TODO(dlc) - zero write with no error will cause lost message and the writeloop to spin.
if n != attempted && n > 0 {
c.handlePartialWrite(c.out.nb)
}
// Check that if there is still data to send and writeLoop is in wait, // Check that if there is still data to send and writeLoop is in wait,
// then we need to signal. // then we need to signal.
if c.out.pb > 0 { if c.out.pb > 0 {
@@ -2755,7 +2741,7 @@ func (c *client) processSubEx(subject, queue, bsid []byte, cb msgHandler, noForw
return sub, nil return sub, nil
} }
if err := c.addShadowSubscriptions(acc, sub); err != nil { if err := c.addShadowSubscriptions(acc, sub, true); err != nil {
c.Errorf(err.Error()) c.Errorf(err.Error())
} }
@@ -2782,10 +2768,13 @@ type ime struct {
dyn bool dyn bool
} }
// If the client's account has stream imports and there are matches for // If the client's account has stream imports and there are matches for this
// this subscription's subject, then add shadow subscriptions in the // subscription's subject, then add shadow subscriptions in the other accounts
// other accounts that export this subject. // that export this subject.
func (c *client) addShadowSubscriptions(acc *Account, sub *subscription) error { //
// enact=false allows MQTT clients to get the list of shadow subscriptions
// without enacting them, in order to first obtain matching "retained" messages.
func (c *client) addShadowSubscriptions(acc *Account, sub *subscription, enact bool) error {
if acc == nil { if acc == nil {
return ErrMissingAccount return ErrMissingAccount
} }
@@ -2888,7 +2877,7 @@ func (c *client) addShadowSubscriptions(acc *Account, sub *subscription) error {
for i := 0; i < len(ims); i++ { for i := 0; i < len(ims); i++ {
ime := &ims[i] ime := &ims[i]
// We will create a shadow subscription. // We will create a shadow subscription.
nsub, err := c.addShadowSub(sub, ime) nsub, err := c.addShadowSub(sub, ime, enact)
if err != nil { if err != nil {
return err return err
} }
@@ -2905,7 +2894,7 @@ func (c *client) addShadowSubscriptions(acc *Account, sub *subscription) error {
} }
// Add in the shadow subscription. // Add in the shadow subscription.
func (c *client) addShadowSub(sub *subscription, ime *ime) (*subscription, error) { func (c *client) addShadowSub(sub *subscription, ime *ime, enact bool) (*subscription, error) {
im := ime.im im := ime.im
nsub := *sub // copy nsub := *sub // copy
nsub.im = im nsub.im = im
@@ -2929,6 +2918,11 @@ func (c *client) addShadowSub(sub *subscription, ime *ime) (*subscription, error
} }
} }
// Else use original subject // Else use original subject
if !enact {
return &nsub, nil
}
c.Debugf("Creating import subscription on %q from account %q", nsub.subject, im.acc.Name) c.Debugf("Creating import subscription on %q from account %q", nsub.subject, im.acc.Name)
if err := im.acc.sl.Insert(&nsub); err != nil { if err := im.acc.sl.Insert(&nsub); err != nil {
@@ -3298,9 +3292,12 @@ func (c *client) stalledWait(producer *client) {
c.mu.Unlock() c.mu.Unlock()
defer c.mu.Lock() defer c.mu.Lock()
delay := time.NewTimer(ttl)
defer delay.Stop()
select { select {
case <-stall: case <-stall:
case <-time.After(ttl): case <-delay.C:
producer.Debugf("Timed out of fast producer stall (%v)", ttl) producer.Debugf("Timed out of fast producer stall (%v)", ttl)
} }
} }
@@ -5045,7 +5042,7 @@ func (c *client) processSubsOnConfigReload(awcsti map[string]struct{}) {
oldShadows := sub.shadow oldShadows := sub.shadow
sub.shadow = nil sub.shadow = nil
c.mu.Unlock() c.mu.Unlock()
c.addShadowSubscriptions(acc, sub) c.addShadowSubscriptions(acc, sub, true)
for _, nsub := range oldShadows { for _, nsub := range oldShadows {
nsub.im.acc.sl.Remove(nsub) nsub.im.acc.sl.Remove(nsub)
} }
+1 -1
View File
@@ -41,7 +41,7 @@ var (
const ( const (
// VERSION is the current version for the server. // VERSION is the current version for the server.
VERSION = "2.10.4" VERSION = "2.10.5"
// PROTO is the currently supported protocol. // PROTO is the currently supported protocol.
// 0 was the original // 0 was the original
+3 -2
View File
@@ -3731,7 +3731,8 @@ func (o *consumer) processInboundAcks(qch chan struct{}) {
// How often we will check for ack floor drift. // How often we will check for ack floor drift.
// Spread these out for large numbers on a server restart. // Spread these out for large numbers on a server restart.
delta := time.Duration(rand.Int63n(int64(time.Minute))) delta := time.Duration(rand.Int63n(int64(time.Minute)))
var ackFloorCheck = time.Minute + delta ticker := time.NewTicker(time.Minute + delta)
defer ticker.Stop()
for { for {
select { select {
@@ -3746,7 +3747,7 @@ func (o *consumer) processInboundAcks(qch chan struct{}) {
if hasInactiveThresh { if hasInactiveThresh {
o.suppressDeletion() o.suppressDeletion()
} }
case <-time.After(ackFloorCheck): case <-ticker.C:
o.checkAckFloor() o.checkAckFloor()
case <-qch: case <-qch:
return return
+19 -1
View File
@@ -810,8 +810,26 @@ func (s *Server) sendStatsz(subj string) {
return return
} }
shouldCheckInterest := func() bool {
opts := s.getOpts()
if opts.Cluster.Port != 0 || opts.Gateway.Port != 0 || opts.LeafNode.Port != 0 {
return false
}
// If we are here we have no clustering or gateways and are not a leafnode hub.
// Check for leafnode remotes that connect the system account.
if len(opts.LeafNode.Remotes) > 0 {
sysAcc := s.sys.account.GetName()
for _, r := range opts.LeafNode.Remotes {
if r.LocalAccount == sysAcc {
return false
}
}
}
return true
}
// if we are running standalone, check for interest. // if we are running standalone, check for interest.
if s.standAloneMode() { if shouldCheckInterest() {
// Check if we even have interest in this subject. // Check if we even have interest in this subject.
sacc := s.sys.account sacc := s.sys.account
rr := sacc.sl.Match(subj) rr := sacc.sl.Match(subj)
File diff suppressed because it is too large Load Diff
+5
View File
@@ -1197,6 +1197,11 @@ func (a *Account) EnableJetStream(limits map[string]JetStreamAccountLimits) erro
fis, _ := os.ReadDir(sdir) fis, _ := os.ReadDir(sdir)
for _, fi := range fis { for _, fi := range fis {
mdir := filepath.Join(sdir, fi.Name()) mdir := filepath.Join(sdir, fi.Name())
// Check for partially deleted streams. They are marked with "." prefix.
if strings.HasPrefix(fi.Name(), tsep) {
go os.RemoveAll(mdir)
continue
}
key := sha256.Sum256([]byte(fi.Name())) key := sha256.Sum256([]byte(fi.Name()))
hh, err := highwayhash.New64(key[:]) hh, err := highwayhash.New64(key[:])
if err != nil { if err != nil {
+40 -40
View File
@@ -2143,7 +2143,7 @@ func (js *jetStream) monitorStream(mset *stream, sa *streamAssignment, sendSnaps
// from underneath the one that is running since it will be the same raft node. // from underneath the one that is running since it will be the same raft node.
defer n.Stop() defer n.Stop()
qch, lch, aq, uch, ourPeerId := n.QuitC(), n.LeadChangeC(), n.ApplyQ(), mset.updateC(), meta.ID() qch, mqch, lch, aq, uch, ourPeerId := n.QuitC(), mset.monitorQuitC(), n.LeadChangeC(), n.ApplyQ(), mset.updateC(), meta.ID()
s.Debugf("Starting stream monitor for '%s > %s' [%s]", sa.Client.serviceAccount(), sa.Config.Name, n.Group()) s.Debugf("Starting stream monitor for '%s > %s' [%s]", sa.Client.serviceAccount(), sa.Config.Name, n.Group())
defer s.Debugf("Exiting stream monitor for '%s > %s' [%s]", sa.Client.serviceAccount(), sa.Config.Name, n.Group()) defer s.Debugf("Exiting stream monitor for '%s > %s' [%s]", sa.Client.serviceAccount(), sa.Config.Name, n.Group())
@@ -2249,7 +2249,7 @@ func (js *jetStream) monitorStream(mset *stream, sa *streamAssignment, sendSnaps
startDirectAccessMonitoring := func() { startDirectAccessMonitoring := func() {
if dat == nil { if dat == nil {
dat = time.NewTicker(1 * time.Second) dat = time.NewTicker(2 * time.Second)
datc = dat.C datc = dat.C
} }
} }
@@ -2301,6 +2301,8 @@ func (js *jetStream) monitorStream(mset *stream, sa *streamAssignment, sendSnaps
select { select {
case <-s.quitCh: case <-s.quitCh:
return return
case <-mqch:
return
case <-qch: case <-qch:
return return
case <-aq.ch: case <-aq.ch:
@@ -2322,6 +2324,10 @@ func (js *jetStream) monitorStream(mset *stream, sa *streamAssignment, sendSnaps
ne, nb = n.Applied(ce.Index) ne, nb = n.Applied(ce.Index)
ce.ReturnToPool() ce.ReturnToPool()
} else { } else {
// Our stream was closed out from underneath of us, simply return here.
if err == errStreamClosed {
return
}
s.Warnf("Error applying entries to '%s > %s': %v", accName, sa.Config.Name, err) s.Warnf("Error applying entries to '%s > %s': %v", accName, sa.Config.Name, err)
if isClusterResetErr(err) { if isClusterResetErr(err) {
if mset.isMirror() && mset.IsLeader() { if mset.isMirror() && mset.IsLeader() {
@@ -2349,19 +2355,15 @@ func (js *jetStream) monitorStream(mset *stream, sa *streamAssignment, sendSnaps
case isLeader = <-lch: case isLeader = <-lch:
if isLeader { if isLeader {
if mset != nil && n != nil { if mset != nil && n != nil && sendSnapshot {
// Send a snapshot if being asked or if we are tracking n.SendSnapshot(mset.stateSnapshot())
// a failed state so that followers sync. sendSnapshot = false
if clfs := mset.clearCLFS(); clfs > 0 || sendSnapshot {
n.SendSnapshot(mset.stateSnapshot())
sendSnapshot = false
}
} }
if isRestore { if isRestore {
acc, _ := s.LookupAccount(sa.Client.serviceAccount()) acc, _ := s.LookupAccount(sa.Client.serviceAccount())
restoreDoneCh = s.processStreamRestore(sa.Client, acc, sa.Config, _EMPTY_, sa.Reply, _EMPTY_) restoreDoneCh = s.processStreamRestore(sa.Client, acc, sa.Config, _EMPTY_, sa.Reply, _EMPTY_)
continue continue
} else if n.NeedSnapshot() { } else if n != nil && n.NeedSnapshot() {
doSnapshot() doSnapshot()
} }
// Always cancel if this was running. // Always cancel if this was running.
@@ -2388,17 +2390,22 @@ func (js *jetStream) monitorStream(mset *stream, sa *streamAssignment, sendSnaps
// Here we are checking if we are not the leader but we have been asked to allow // Here we are checking if we are not the leader but we have been asked to allow
// direct access. We now allow non-leaders to participate in the queue group. // direct access. We now allow non-leaders to participate in the queue group.
if !isLeader && mset != nil { if !isLeader && mset != nil {
startDirectAccessMonitoring() mset.mu.RLock()
ad, md := mset.cfg.AllowDirect, mset.cfg.MirrorDirect
mset.mu.RUnlock()
if ad || md {
startDirectAccessMonitoring()
}
} }
case <-datc: case <-datc:
if mset == nil || isRecovering { if mset == nil || isRecovering {
return continue
} }
// If we are leader we can stop, we know this is setup now. // If we are leader we can stop, we know this is setup now.
if isLeader { if isLeader {
stopDirectMonitoring() stopDirectMonitoring()
return continue
} }
mset.mu.Lock() mset.mu.Lock()
@@ -2550,6 +2557,8 @@ func (js *jetStream) monitorStream(mset *stream, sa *streamAssignment, sendSnaps
mset.setStreamAssignment(sa) mset.setStreamAssignment(sa)
// Make sure to update our updateC which would have been nil. // Make sure to update our updateC which would have been nil.
uch = mset.updateC() uch = mset.updateC()
// Also update our mqch
mqch = mset.monitorQuitC()
} }
} }
if err != nil { if err != nil {
@@ -2782,6 +2791,7 @@ func (js *jetStream) applyStreamEntries(mset *stream, ce *CommittedEntry, isReco
// Grab last sequence and CLFS. // Grab last sequence and CLFS.
last, clfs := mset.lastSeqAndCLFS() last, clfs := mset.lastSeqAndCLFS()
// We can skip if we know this is less than what we already have. // We can skip if we know this is less than what we already have.
if lseq-clfs < last { if lseq-clfs < last {
s.Debugf("Apply stream entries for '%s > %s' skipping message with sequence %d with last of %d", s.Debugf("Apply stream entries for '%s > %s' skipping message with sequence %d with last of %d",
@@ -2812,13 +2822,14 @@ func (js *jetStream) applyStreamEntries(mset *stream, ce *CommittedEntry, isReco
// Process the actual message here. // Process the actual message here.
if err := mset.processJetStreamMsg(subject, reply, hdr, msg, lseq, ts); err != nil { if err := mset.processJetStreamMsg(subject, reply, hdr, msg, lseq, ts); err != nil {
// Only return in place if we are going to reset stream or we are out of space. // Only return in place if we are going to reset our stream or we are out of space, or we are closed.
if isClusterResetErr(err) || isOutOfSpaceErr(err) { if isClusterResetErr(err) || isOutOfSpaceErr(err) || err == errStreamClosed {
return err return err
} }
s.Debugf("Apply stream entries for '%s > %s' got error processing message: %v", s.Debugf("Apply stream entries for '%s > %s' got error processing message: %v",
mset.account(), mset.name(), err) mset.account(), mset.name(), err)
} }
case deleteMsgOp: case deleteMsgOp:
md, err := decodeMsgDelete(buf[1:]) md, err := decodeMsgDelete(buf[1:])
if err != nil { if err != nil {
@@ -5950,10 +5961,13 @@ func sysRequest[T any](s *Server, subjFormat string, args ...interface{}) (*T, e
} }
}() }()
ttl := time.NewTimer(2 * time.Second)
defer ttl.Stop()
select { select {
case <-s.quitCh: case <-s.quitCh:
return nil, errReqSrvExit return nil, errReqSrvExit
case <-time.After(2 * time.Second): case <-ttl.C:
return nil, errReqTimeout return nil, errReqTimeout
case data := <-results: case data := <-results:
return data, nil return data, nil
@@ -6086,6 +6100,12 @@ func (s *Server) jsClusteredStreamUpdateRequest(ci *ClientInfo, acc *Account, su
if isReplicaChange { if isReplicaChange {
// We are adding new peers here. // We are adding new peers here.
if newCfg.Replicas > len(rg.Peers) { if newCfg.Replicas > len(rg.Peers) {
// Check that we have the allocation available.
if err := js.jsClusteredStreamLimitsCheck(acc, newCfg); err != nil {
resp.Error = err
s.sendAPIErrResponse(ci, acc, subject, reply, string(rmsg), s.jsonResponse(&resp))
return
}
// Check if we do not have a cluster assigned, and if we do not make sure we // Check if we do not have a cluster assigned, and if we do not make sure we
// try to pick one. This could happen with older streams that were assigned by // try to pick one. This could happen with older streams that were assigned by
// previous servers. // previous servers.
@@ -6957,7 +6977,7 @@ func (s *Server) jsClusteredConsumerRequest(ci *ClientInfo, acc *Account, subjec
// Also short circuit if DeliverLastPerSubject is set with no FilterSubject. // Also short circuit if DeliverLastPerSubject is set with no FilterSubject.
if cfg.DeliverPolicy == DeliverLastPerSubject { if cfg.DeliverPolicy == DeliverLastPerSubject {
if cfg.FilterSubject == _EMPTY_ { if cfg.FilterSubject == _EMPTY_ && len(cfg.FilterSubjects) == 0 {
resp.Error = NewJSConsumerInvalidPolicyError(fmt.Errorf("consumer delivery policy is deliver last per subject, but FilterSubject is not set")) resp.Error = NewJSConsumerInvalidPolicyError(fmt.Errorf("consumer delivery policy is deliver last per subject, but FilterSubject is not set"))
s.sendAPIErrResponse(ci, acc, subject, reply, string(rmsg), s.jsonResponse(&resp)) s.sendAPIErrResponse(ci, acc, subject, reply, string(rmsg), s.jsonResponse(&resp))
return return
@@ -7382,7 +7402,7 @@ func (mset *stream) stateSnapshotLocked() []byte {
Bytes: state.Bytes, Bytes: state.Bytes,
FirstSeq: state.FirstSeq, FirstSeq: state.FirstSeq,
LastSeq: state.LastSeq, LastSeq: state.LastSeq,
Failed: mset.clfs, Failed: mset.getCLFS(),
Deleted: state.Deleted, Deleted: state.Deleted,
} }
b, _ := json.Marshal(snap) b, _ := json.Marshal(snap)
@@ -7419,7 +7439,7 @@ func (mset *stream) processClusteredInboundMsg(subject, reply string, hdr, msg [
mset.mu.RLock() mset.mu.RLock()
canRespond := !mset.cfg.NoAck && len(reply) > 0 canRespond := !mset.cfg.NoAck && len(reply) > 0
name, stype, store := mset.cfg.Name, mset.cfg.Storage, mset.store name, stype := mset.cfg.Name, mset.cfg.Storage
s, js, jsa, st, rf, tierName, outq, node := mset.srv, mset.js, mset.jsa, mset.cfg.Storage, mset.cfg.Replicas, mset.tier, mset.outq, mset.node s, js, jsa, st, rf, tierName, outq, node := mset.srv, mset.js, mset.jsa, mset.cfg.Storage, mset.cfg.Replicas, mset.tier, mset.outq, mset.node
maxMsgSize, lseq, clfs := int(mset.cfg.MaxMsgSize), mset.lseq, mset.clfs maxMsgSize, lseq, clfs := int(mset.cfg.MaxMsgSize), mset.lseq, mset.clfs
isLeader, isSealed := mset.isLeader(), mset.cfg.Sealed isLeader, isSealed := mset.isLeader(), mset.cfg.Sealed
@@ -7519,26 +7539,6 @@ func (mset *stream) processClusteredInboundMsg(subject, reply string, hdr, msg [
// Some header checks can be checked pre proposal. Most can not. // Some header checks can be checked pre proposal. Most can not.
if len(hdr) > 0 { if len(hdr) > 0 {
// For CAS operations, e.g. ExpectedLastSeqPerSubject, we can also check here and not have to go through.
// Can only precheck for seq != 0.
if seq, exists := getExpectedLastSeqPerSubject(hdr); exists && store != nil && seq > 0 {
var smv StoreMsg
var fseq uint64
sm, err := store.LoadLastMsg(subject, &smv)
if sm != nil {
fseq = sm.seq
}
if err != nil || fseq != seq {
if canRespond {
var resp = &JSPubAckResponse{PubAck: &PubAck{Stream: name}}
resp.PubAck = &PubAck{Stream: name}
resp.Error = NewJSStreamWrongLastSequenceError(fseq)
b, _ := json.Marshal(resp)
outq.sendMsg(reply, b)
}
return fmt.Errorf("last sequence by subject mismatch: %d vs %d", seq, fseq)
}
}
// Expected stream name can also be pre-checked. // Expected stream name can also be pre-checked.
if sname := getExpectedStream(hdr); sname != _EMPTY_ && sname != name { if sname := getExpectedStream(hdr); sname != _EMPTY_ && sname != name {
if canRespond { if canRespond {
@@ -7746,8 +7746,8 @@ func (mset *stream) processSnapshot(snap *StreamReplicatedState) (e error) {
mset.mu.Lock() mset.mu.Lock()
var state StreamState var state StreamState
mset.clfs = snap.Failed
mset.store.FastState(&state) mset.store.FastState(&state)
mset.setCLFS(snap.Failed)
sreq := mset.calculateSyncRequest(&state, snap) sreq := mset.calculateSyncRequest(&state, snap)
s, js, subject, n := mset.srv, mset.js, mset.sa.Sync, mset.node s, js, subject, n := mset.srv, mset.js, mset.sa.Sync, mset.node
+7 -4
View File
@@ -25,6 +25,7 @@ import (
"net/http" "net/http"
"net/url" "net/url"
"os" "os"
"path"
"reflect" "reflect"
"regexp" "regexp"
"runtime" "runtime"
@@ -2349,7 +2350,7 @@ func (c *client) processLeafSub(argo []byte) (err error) {
// Only add in shadow subs if a new sub or qsub. // Only add in shadow subs if a new sub or qsub.
if osub == nil { if osub == nil {
if err := c.addShadowSubscriptions(acc, sub); err != nil { if err := c.addShadowSubscriptions(acc, sub, true); err != nil {
c.Errorf(err.Error()) c.Errorf(err.Error())
} }
} }
@@ -2784,14 +2785,16 @@ func (c *client) leafNodeSolicitWSConnection(opts *Options, rURL *url.URL, remot
// create a LEAF connection, not a CLIENT. // create a LEAF connection, not a CLIENT.
// In case we use the user's URL path in the future, make sure we append the user's // In case we use the user's URL path in the future, make sure we append the user's
// path to our `/leafnode` path. // path to our `/leafnode` path.
path := leafNodeWSPath lpath := leafNodeWSPath
if curPath := rURL.EscapedPath(); curPath != _EMPTY_ { if curPath := rURL.EscapedPath(); curPath != _EMPTY_ {
if curPath[0] == '/' { if curPath[0] == '/' {
curPath = curPath[1:] curPath = curPath[1:]
} }
path += curPath lpath = path.Join(curPath, lpath)
} else {
lpath = lpath[1:]
} }
ustr := fmt.Sprintf("%s://%s%s", scheme, rURL.Host, path) ustr := fmt.Sprintf("%s://%s/%s", scheme, rURL.Host, lpath)
u, _ := url.Parse(ustr) u, _ := url.Parse(ustr)
req := &http.Request{ req := &http.Request{
Method: "GET", Method: "GET",
+2 -2
View File
@@ -129,7 +129,7 @@ func (ms *memStore) storeRawMsg(subj string, hdr, msg []byte, seq uint64, ts int
return ErrMaxMsgs return ErrMaxMsgs
} }
} }
if ms.cfg.MaxBytes > 0 && ms.state.Bytes+uint64(len(msg)+len(hdr)) >= uint64(ms.cfg.MaxBytes) { if ms.cfg.MaxBytes > 0 && ms.state.Bytes+memStoreMsgSize(subj, hdr, msg) >= uint64(ms.cfg.MaxBytes) {
if !asl { if !asl {
return ErrMaxBytes return ErrMaxBytes
} }
@@ -138,7 +138,7 @@ func (ms *memStore) storeRawMsg(subj string, hdr, msg []byte, seq uint64, ts int
ms.recalculateFirstForSubj(subj, ss.First, ss) ms.recalculateFirstForSubj(subj, ss.First, ss)
} }
sm, ok := ms.msgs[ss.First] sm, ok := ms.msgs[ss.First]
if !ok || memStoreMsgSize(sm.subj, sm.hdr, sm.msg) < uint64(len(msg)+len(hdr)) { if !ok || memStoreMsgSize(sm.subj, sm.hdr, sm.msg) < memStoreMsgSize(subj, hdr, msg) {
return ErrMaxBytes return ErrMaxBytes
} }
} }
+292 -172
View File
@@ -155,6 +155,7 @@ const (
// while "$MQTT.JSA.<node id>.SL.<number>" is for a stream lookup, etc... // while "$MQTT.JSA.<node id>.SL.<number>" is for a stream lookup, etc...
mqttJSAIdTokenPos = 3 mqttJSAIdTokenPos = 3
mqttJSATokenPos = 4 mqttJSATokenPos = 4
mqttJSAClientIDPos = 5
mqttJSAStreamCreate = "SC" mqttJSAStreamCreate = "SC"
mqttJSAStreamUpdate = "SU" mqttJSAStreamUpdate = "SU"
mqttJSAStreamLookup = "SL" mqttJSAStreamLookup = "SL"
@@ -237,10 +238,9 @@ type mqttAccountSessionManager struct {
sl *Sublist // sublist allowing to find retained messages for given subscription sl *Sublist // sublist allowing to find retained messages for given subscription
retmsgs map[string]*mqttRetainedMsgRef // retained messages retmsgs map[string]*mqttRetainedMsgRef // retained messages
jsa mqttJSA jsa mqttJSA
rrmLastSeq uint64 // Restore retained messages expected last sequence rrmLastSeq uint64 // Restore retained messages expected last sequence
rrmDoneCh chan struct{} // To notify the caller that all retained messages have been loaded rrmDoneCh chan struct{} // To notify the caller that all retained messages have been loaded
sp *ipQueue[uint64] // Used for cluster-wide processing of session records being persisted domainTk string // Domain (with trailing "."), or possibly empty. This is added to session subject.
domainTk string // Domain (with trailing "."), or possibly empty. This is added to session subject.
} }
type mqttJSA struct { type mqttJSA struct {
@@ -1109,7 +1109,6 @@ func (s *Server) mqttCreateAccountSessionManager(acc *Account, quitCh chan struc
nuid: nuid.New(), nuid: nuid.New(),
quitCh: quitCh, quitCh: quitCh,
}, },
sp: newIPQueue[uint64](s, qname+"sp"),
} }
// TODO record domain name in as here // TODO record domain name in as here
@@ -1170,14 +1169,15 @@ func (s *Server) mqttCreateAccountSessionManager(acc *Account, quitCh chan struc
// This is a subscription that will process all JS API replies. We could split to // This is a subscription that will process all JS API replies. We could split to
// individual subscriptions if needed, but since there is a bit of common code, // individual subscriptions if needed, but since there is a bit of common code,
// that seemed like a good idea to be all in one place. // that seemed like a good idea to be all in one place.
if err := as.createSubscription(jsa.rplyr+"*.*", if err := as.createSubscription(jsa.rplyr+">",
as.processJSAPIReplies, &sid, &subs); err != nil { as.processJSAPIReplies, &sid, &subs); err != nil {
return nil, err return nil, err
} }
// We will listen for replies to session persist requests so that we can // We will listen for replies to session persist requests so that we can
// detect the use of a session with the same client ID anywhere in the cluster. // detect the use of a session with the same client ID anywhere in the cluster.
if err := as.createSubscription(mqttJSARepliesPrefix+"*."+mqttJSASessPersist+".*", // `$MQTT.JSA.{js-id}.SP.{client-id-hash}.{uuid}`
if err := as.createSubscription(mqttJSARepliesPrefix+"*."+mqttJSASessPersist+".*.*",
as.processSessionPersist, &sid, &subs); err != nil { as.processSessionPersist, &sid, &subs); err != nil {
return nil, err return nil, err
} }
@@ -1203,12 +1203,6 @@ func (s *Server) mqttCreateAccountSessionManager(acc *Account, quitCh chan struc
as.sendJSAPIrequests(s, c, accName, closeCh) as.sendJSAPIrequests(s, c, accName, closeCh)
}) })
// Start the go routine that will handle network updates regarding sessions
s.startGoRoutine(func() {
defer s.grWG.Done()
as.sessPersistProcessing(closeCh)
})
lookupStream := func(stream, txt string) (*StreamInfo, error) { lookupStream := func(stream, txt string) (*StreamInfo, error) {
si, err := jsa.lookupStream(stream) si, err := jsa.lookupStream(stream)
if err != nil { if err != nil {
@@ -1407,9 +1401,12 @@ func (s *Server) mqttCreateAccountSessionManager(acc *Account, quitCh chan struc
} }
if lastSeq > 0 { if lastSeq > 0 {
ttl := time.NewTimer(mqttJSAPITimeout)
defer ttl.Stop()
select { select {
case <-rmDoneCh: case <-rmDoneCh:
case <-time.After(mqttJSAPITimeout): case <-ttl.C:
s.Warnf("Timing out waiting to load %v retained messages", st.Msgs) s.Warnf("Timing out waiting to load %v retained messages", st.Msgs)
case <-quitCh: case <-quitCh:
return nil, ErrServerNotRunning return nil, ErrServerNotRunning
@@ -1454,7 +1451,7 @@ func (s *Server) mqttDetermineReplicas() int {
////////////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////////////
func (jsa *mqttJSA) newRequest(kind, subject string, hdr int, msg []byte) (interface{}, error) { func (jsa *mqttJSA) newRequest(kind, subject string, hdr int, msg []byte) (interface{}, error) {
return jsa.newRequestEx(kind, subject, hdr, msg, mqttJSAPITimeout) return jsa.newRequestEx(kind, subject, _EMPTY_, hdr, msg, mqttJSAPITimeout)
} }
func (jsa *mqttJSA) prefixDomain(subject string) string { func (jsa *mqttJSA) prefixDomain(subject string) string {
@@ -1467,19 +1464,24 @@ func (jsa *mqttJSA) prefixDomain(subject string) string {
return subject return subject
} }
func (jsa *mqttJSA) newRequestEx(kind, subject string, hdr int, msg []byte, timeout time.Duration) (interface{}, error) { func (jsa *mqttJSA) newRequestEx(kind, subject, cidHash string, hdr int, msg []byte, timeout time.Duration) (interface{}, error) {
var sb strings.Builder
jsa.mu.Lock() jsa.mu.Lock()
// Either we use nuid.Next() which uses a global lock, or our own nuid object, but // Either we use nuid.Next() which uses a global lock, or our own nuid object, but
// then it needs to be "write" protected. This approach will reduce across account // then it needs to be "write" protected. This approach will reduce across account
// contention since we won't use the global nuid's lock. // contention since we won't use the global nuid's lock.
var sb strings.Builder
sb.WriteString(jsa.rplyr) sb.WriteString(jsa.rplyr)
sb.WriteString(kind) sb.WriteString(kind)
sb.WriteByte(btsep) sb.WriteByte(btsep)
if cidHash != _EMPTY_ {
sb.WriteString(cidHash)
sb.WriteByte(btsep)
}
sb.WriteString(jsa.nuid.Next()) sb.WriteString(jsa.nuid.Next())
reply := sb.String()
jsa.mu.Unlock() jsa.mu.Unlock()
reply := sb.String()
ch := make(chan interface{}, 1) ch := make(chan interface{}, 1)
jsa.replies.Store(reply, ch) jsa.replies.Store(reply, ch)
@@ -1646,6 +1648,25 @@ func (jsa *mqttJSA) storeMsgWithKind(kind, subject string, headers int, msg []by
return smr, smr.ToError() return smr, smr.ToError()
} }
func (jsa *mqttJSA) storeSessionMsg(domainTk, cidHash string, hdr int, msg []byte) (*JSPubAckResponse, error) {
// Compute subject where the session is being stored
subject := mqttSessStreamSubjectPrefix + domainTk + cidHash
// Passing cidHash will add it to the JS reply subject, so that we can use
// it in processSessionPersist.
smri, err := jsa.newRequestEx(mqttJSASessPersist, subject, cidHash, hdr, msg, mqttJSAPITimeout)
if err != nil {
return nil, err
}
smr := smri.(*JSPubAckResponse)
return smr, smr.ToError()
}
func (jsa *mqttJSA) loadSessionMsg(domainTk, cidHash string) (*StoredMsg, error) {
subject := mqttSessStreamSubjectPrefix + domainTk + cidHash
return jsa.loadLastMsgFor(mqttSessStreamName, subject)
}
func (jsa *mqttJSA) deleteMsg(stream string, seq uint64, wait bool) error { func (jsa *mqttJSA) deleteMsg(stream string, seq uint64, wait bool) error {
dreq := JSApiMsgDeleteRequest{Seq: seq, NoErase: true} dreq := JSApiMsgDeleteRequest{Seq: seq, NoErase: true}
req, _ := json.Marshal(dreq) req, _ := json.Marshal(dreq)
@@ -1817,6 +1838,7 @@ func (as *mqttAccountSessionManager) processSessionPersist(_ *subscription, pc *
if tokenAt(subject, mqttJSAIdTokenPos) == as.jsa.id { if tokenAt(subject, mqttJSAIdTokenPos) == as.jsa.id {
return return
} }
cIDHash := tokenAt(subject, mqttJSAClientIDPos)
_, msg := pc.msgParts(rmsg) _, msg := pc.msgParts(rmsg)
if len(msg) < LEN_CR_LF { if len(msg) < LEN_CR_LF {
return return
@@ -1839,18 +1861,6 @@ func (as *mqttAccountSessionManager) processSessionPersist(_ *subscription, pc *
if ignore { if ignore {
return return
} }
// We would need to lookup the message and that would be a request/reply,
// which we can't do in place here. So move that to a long-running routine
// that will process the session persist record.
as.sp.push(par.Sequence)
}
func (as *mqttAccountSessionManager) processSessPersistRecord(seq uint64) {
smsg, err := as.jsa.loadMsg(mqttSessStreamName, seq)
if err != nil {
return
}
cIDHash := strings.TrimPrefix(smsg.Subject, mqttSessStreamSubjectPrefix+as.domainTk)
as.mu.Lock() as.mu.Lock()
defer as.mu.Unlock() defer as.mu.Unlock()
@@ -1861,7 +1871,7 @@ func (as *mqttAccountSessionManager) processSessPersistRecord(seq uint64) {
// If our current session's stream sequence is higher, it means that this // If our current session's stream sequence is higher, it means that this
// update is stale, so we don't do anything here. // update is stale, so we don't do anything here.
sess.mu.Lock() sess.mu.Lock()
ignore := seq < sess.seq ignore = par.Sequence < sess.seq
sess.mu.Unlock() sess.mu.Unlock()
if ignore { if ignore {
return return
@@ -1881,28 +1891,6 @@ func (as *mqttAccountSessionManager) processSessPersistRecord(seq uint64) {
sess.mu.Unlock() sess.mu.Unlock()
} }
func (as *mqttAccountSessionManager) sessPersistProcessing(closeCh chan struct{}) {
as.mu.RLock()
sp := as.sp
quitCh := as.jsa.quitCh
as.mu.RUnlock()
for {
select {
case <-sp.ch:
seqs := sp.pop()
for _, seq := range seqs {
as.processSessPersistRecord(seq)
}
sp.recycle(&seqs)
case <-closeCh:
return
case <-quitCh:
return
}
}
}
// Adds this client ID to the flappers map, and if needed start the timer // Adds this client ID to the flappers map, and if needed start the timer
// for map cleanup. // for map cleanup.
// //
@@ -2176,6 +2164,30 @@ func (as *mqttAccountSessionManager) removeSession(sess *mqttSession, lock bool)
} }
} }
// Helpers that sets the sub's mqtt fields and possibly serialize
// (pre-loaded) retained messages.
// Session lock held on entry.
func (sess *mqttSession) processSub(c *client, subject, sid []byte, isReserved bool, qos byte, jsDurName string, h msgHandler, initShadow bool) (*subscription, error) {
sub, err := c.processSub(subject, nil, sid, h, false)
if err != nil {
// c.processSub already called c.Errorf(), so no need here.
return nil, err
}
subs := []*subscription{sub}
if initShadow {
subs = append(subs, sub.shadow...)
}
for _, ss := range subs {
if ss.mqtt == nil {
ss.mqtt = &mqttSub{}
}
ss.mqtt.qos = qos
ss.mqtt.reserved = isReserved
ss.mqtt.jsDur = jsDurName
}
return sub, nil
}
// Process subscriptions for the given session/client. // Process subscriptions for the given session/client.
// //
// When `fromSubProto` is false, it means that this is invoked from the CONNECT // When `fromSubProto` is false, it means that this is invoked from the CONNECT
@@ -2193,14 +2205,85 @@ func (as *mqttAccountSessionManager) removeSession(sess *mqttSession, lock bool)
func (as *mqttAccountSessionManager) processSubs(sess *mqttSession, c *client, func (as *mqttAccountSessionManager) processSubs(sess *mqttSession, c *client,
filters []*mqttFilter, fromSubProto, trace bool) ([]*subscription, error) { filters []*mqttFilter, fromSubProto, trace bool) ([]*subscription, error) {
// Helpers to lock/unlock both account manager and session. // Helper to determine if we need to create a separate top-level
asAndSessLock := func() { // subscription for a wildcard.
as.mu.Lock() fwc := func(subject string) (bool, string, string) {
sess.mu.Lock() if !mqttNeedSubForLevelUp(subject) {
return false, _EMPTY_, _EMPTY_
}
// Say subject is "foo.>", remove the ".>" so that it becomes "foo"
fwcsubject := subject[:len(subject)-2]
// Change the sid to "foo fwc"
fwcsid := fwcsubject + mqttMultiLevelSidSuffix
return true, fwcsubject, fwcsid
} }
asAndSessUnlock := func() {
sess.mu.Unlock() // Cache and a helper to load retained messages for a given subject.
as.mu.Unlock() rms := make(map[string]*mqttRetainedMsg)
loadRMS := func(subject []byte) error {
sub := &subscription{
client: c,
subject: subject,
sid: subject,
}
c.mu.Lock()
acc := c.acc
c.mu.Unlock()
if err := c.addShadowSubscriptions(acc, sub, false); err != nil {
return err
}
// Best-effort loading the messages, logs on errors (to c.srv), loads
// once for subject.
as.loadRetainedMessagesForSubject(rms, subject, c.srv)
for _, ss := range sub.shadow {
as.loadRetainedMessagesForSubject(rms, ss.subject, c.srv)
}
return nil
}
// Preload retained messages for all requested subscriptions. Also, since
// it's the first iteration over the filter list, do some cleanup.
for _, f := range filters {
if f.qos > 2 {
f.qos = 2
}
if c.mqtt.downgradeQoS2Sub && f.qos == 2 {
c.Warnf("Downgrading subscription QoS2 to QoS1 for %q, as configured", f.filter)
f.qos = 1
}
// Do not allow subscribing to our internal subjects.
//
// TODO: (levb: not sure why since one can subscribe to `#` and it'll
// include everything; I guess this would discourage? Otherwise another
// candidate for DO NOT DELIVER prefix list).
if strings.HasPrefix(f.filter, mqttSubPrefix) {
f.qos = mqttSubAckFailure
continue
}
if f.qos == 2 {
if err := sess.ensurePubRelConsumerSubscription(c); err != nil {
c.Errorf("failed to initialize PUBREL processing: %v", err)
f.qos = mqttSubAckFailure
continue
}
}
// Load retained messages.
if fromSubProto {
if err := loadRMS([]byte(f.filter)); err != nil {
f.qos = mqttSubAckFailure
continue
}
if need, subject, _ := fwc(f.filter); need {
if err := loadRMS([]byte(subject)); err != nil {
f.qos = mqttSubAckFailure
continue
}
}
}
} }
// Small helper to add the consumer config to the session. // Small helper to add the consumer config to the session.
@@ -2214,90 +2297,80 @@ func (as *mqttAccountSessionManager) processSubs(sess *mqttSession, c *client,
sess.cons[sid] = cc sess.cons[sid] = cc
} }
// Helper that sets the sub's mqtt fields and possibly serialize retained messages. serializeRMS := func(sub *subscription) {
// Assumes account manager and session lock held. for _, ss := range append([]*subscription{sub}, sub.shadow...) {
setupSub := func(sub *subscription, qos byte) { as.serializeRetainedMsgsForSub(rms, sess, c, ss, trace)
subs := []*subscription{sub}
if len(sub.shadow) > 0 {
subs = append(subs, sub.shadow...)
}
for _, sub := range subs {
if sub.mqtt == nil {
sub.mqtt = &mqttSub{}
}
sub.mqtt.qos = qos
sub.mqtt.reserved = isMQTTReservedSubscription(string(sub.subject))
if fromSubProto {
as.serializeRetainedMsgsForSub(sess, c, sub, trace)
}
} }
} }
var err error var err error
subs := make([]*subscription, 0, len(filters)) subs := make([]*subscription, 0, len(filters))
for _, f := range filters { for _, f := range filters {
if f.qos > 2 {
f.qos = 2
}
if c.mqtt.downgradeQoS2Sub && f.qos == 2 {
c.Warnf("Downgrading subscription QoS2 to QoS1 for %q, as configured", f.filter)
f.qos = 1
}
subject := f.filter
sid := subject
if strings.HasPrefix(subject, mqttSubPrefix) { // Skip what's already been identified as a failure.
f.qos = mqttSubAckFailure if f.qos == mqttSubAckFailure {
continue continue
} }
subject := f.filter
bsubject := []byte(subject)
sid := subject
bsid := bsubject
var jscons *ConsumerConfig var jscons *ConsumerConfig
var jssub *subscription var jssub *subscription
// Note that if a subscription already exists on this subject, // Note that if a subscription already exists on this subject, the
// the existing sub is returned. Need to update the qos. // existing sub is returned. Need to update the qos.
asAndSessLock() as.mu.Lock()
sub, err := c.processSub([]byte(subject), nil, []byte(sid), mqttDeliverMsgCbQoS0, false) sess.mu.Lock()
if err == nil { sub, err := sess.processSub(c, bsubject, bsid,
setupSub(sub, f.qos) isMQTTReservedSubscription(subject), f.qos, _EMPTY_, mqttDeliverMsgCbQoS0, true)
} if err == nil && fromSubProto {
if f.qos == 2 { serializeRMS(sub)
err = sess.ensurePubRelConsumerSubscription(c)
}
asAndSessUnlock()
if err == nil {
// This will create (if not already exist) a JS consumer for subscriptions
// of QoS >= 1. But if a JS consumer already exists and the subscription
// for same subject is now a QoS==0, then the JS consumer will be deleted.
jscons, jssub, err = sess.processJSConsumer(c, subject, sid, f.qos, fromSubProto)
} }
sess.mu.Unlock()
as.mu.Unlock()
if err != nil { if err != nil {
// c.processSub already called c.Errorf(), so no need here.
f.qos = mqttSubAckFailure f.qos = mqttSubAckFailure
sess.cleanupFailedSub(c, sub, jscons, jssub) sess.cleanupFailedSub(c, sub, jscons, jssub)
continue continue
} }
if mqttNeedSubForLevelUp(subject) { // This will create (if not already exist) a JS consumer for
// subscriptions of QoS >= 1. But if a JS consumer already exists and
// the subscription for same subject is now a QoS==0, then the JS
// consumer will be deleted.
jscons, jssub, err = sess.processJSConsumer(c, subject, sid, f.qos, fromSubProto)
if err != nil {
f.qos = mqttSubAckFailure
sess.cleanupFailedSub(c, sub, jscons, jssub)
continue
}
// Process the wildcard subject if needed.
if need, fwcsubject, fwcsid := fwc(subject); need {
var fwjscons *ConsumerConfig var fwjscons *ConsumerConfig
var fwjssub *subscription var fwjssub *subscription
var fwcsub *subscription var fwcsub *subscription
// Say subject is "foo.>", remove the ".>" so that it becomes "foo"
fwcsubject := subject[:len(subject)-2]
// Change the sid to "foo fwc"
fwcsid := fwcsubject + mqttMultiLevelSidSuffix
// See note above about existing subscription. // See note above about existing subscription.
asAndSessLock() as.mu.Lock()
fwcsub, err = c.processSub([]byte(fwcsubject), nil, []byte(fwcsid), mqttDeliverMsgCbQoS0, false) sess.mu.Lock()
if err == nil { fwcsub, err = sess.processSub(c, []byte(fwcsubject), []byte(fwcsid),
setupSub(fwcsub, f.qos) isMQTTReservedSubscription(subject), f.qos, _EMPTY_, mqttDeliverMsgCbQoS0, true)
if err == nil && fromSubProto {
serializeRMS(fwcsub)
} }
asAndSessUnlock() sess.mu.Unlock()
if err == nil { as.mu.Unlock()
fwjscons, fwjssub, err = sess.processJSConsumer(c, fwcsubject, fwcsid, f.qos, fromSubProto) if err != nil {
// c.processSub already called c.Errorf(), so no need here.
f.qos = mqttSubAckFailure
sess.cleanupFailedSub(c, sub, jscons, jssub)
continue
} }
fwjscons, fwjssub, err = sess.processJSConsumer(c, fwcsubject, fwcsid, f.qos, fromSubProto)
if err != nil { if err != nil {
// c.processSub already called c.Errorf(), so no need here. // c.processSub already called c.Errorf(), so no need here.
f.qos = mqttSubAckFailure f.qos = mqttSubAckFailure
@@ -2305,6 +2378,7 @@ func (as *mqttAccountSessionManager) processSubs(sess *mqttSession, c *client,
sess.cleanupFailedSub(c, fwcsub, fwjscons, fwjssub) sess.cleanupFailedSub(c, fwcsub, fwjscons, fwjssub)
continue continue
} }
subs = append(subs, fwcsub) subs = append(subs, fwcsub)
addJSConsToSess(fwcsid, fwjscons) addJSConsToSess(fwcsid, fwjscons)
} }
@@ -2328,15 +2402,19 @@ func (as *mqttAccountSessionManager) processSubs(sess *mqttSession, c *client,
// Runs from the client's readLoop. // Runs from the client's readLoop.
// Account session manager lock held on entry. // Account session manager lock held on entry.
// Session lock held on entry. // Session lock held on entry.
func (as *mqttAccountSessionManager) serializeRetainedMsgsForSub(sess *mqttSession, c *client, sub *subscription, trace bool) { func (as *mqttAccountSessionManager) serializeRetainedMsgsForSub(rms map[string]*mqttRetainedMsg, sess *mqttSession, c *client, sub *subscription, trace bool) {
if len(as.retmsgs) == 0 { if len(as.retmsgs) == 0 || len(rms) == 0 {
return return
} }
var rmsa [64]*mqttRetainedMsg result := as.sl.ReverseMatch(string(sub.subject))
rms := rmsa[:0] if len(result.psubs) == 0 {
return
as.getRetainedPublishMsgs(string(sub.subject), &rms) }
for _, rm := range rms { for _, psub := range result.psubs {
rm, ok := rms[string(psub.subject)]
if !ok {
continue
}
if sub.mqtt.prm == nil { if sub.mqtt.prm == nil {
sub.mqtt.prm = &mqttWriter{} sub.mqtt.prm = &mqttWriter{}
} }
@@ -2379,23 +2457,36 @@ func (as *mqttAccountSessionManager) serializeRetainedMsgsForSub(sess *mqttSessi
// Returns in the provided slice all publish retained message records that // Returns in the provided slice all publish retained message records that
// match the given subscription's `subject` (which could have wildcards). // match the given subscription's `subject` (which could have wildcards).
// //
// Account session manager lock held on entry. // Account session manager NOT lock held on entry.
func (as *mqttAccountSessionManager) getRetainedPublishMsgs(subject string, rms *[]*mqttRetainedMsg) { func (as *mqttAccountSessionManager) loadRetainedMessagesForSubject(rms map[string]*mqttRetainedMsg, topSubject []byte, log Logger) {
result := as.sl.ReverseMatch(subject) as.mu.RLock()
if len(as.retmsgs) == 0 {
as.mu.RUnlock()
return
}
result := as.sl.ReverseMatch(string(topSubject))
as.mu.RUnlock()
if len(result.psubs) == 0 { if len(result.psubs) == 0 {
return return
} }
for _, sub := range result.psubs { for _, sub := range result.psubs {
subj := mqttRetainedMsgsStreamSubject + string(sub.subject) subject := string(sub.subject)
jsm, err := as.jsa.loadLastMsgFor(mqttRetainedMsgsStreamName, subj) if rms[subject] != nil {
continue // already loaded
}
loadSubject := mqttRetainedMsgsStreamSubject + subject
jsm, err := as.jsa.loadLastMsgFor(mqttRetainedMsgsStreamName, loadSubject)
if err != nil || jsm == nil { if err != nil || jsm == nil {
log.Warnf("failed to load retained message for subject %q: %v", loadSubject, err)
continue continue
} }
var rm mqttRetainedMsg var rm mqttRetainedMsg
if err := json.Unmarshal(jsm.Data, &rm); err != nil { if err := json.Unmarshal(jsm.Data, &rm); err != nil {
log.Warnf("failed to decode retained message for subject %q: %v", loadSubject, err)
continue continue
} }
*rms = append(*rms, &rm) rms[subject] = &rm
} }
} }
@@ -2417,8 +2508,7 @@ func (as *mqttAccountSessionManager) createOrRestoreSession(clientID string, opt
} }
hash := getHash(clientID) hash := getHash(clientID)
subject := mqttSessStreamSubjectPrefix + as.domainTk + hash smsg, err := jsa.loadSessionMsg(as.domainTk, hash)
smsg, err := jsa.loadLastMsgFor(mqttSessStreamName, subject)
if err != nil { if err != nil {
if isErrorOtherThan(err, JSNoMessageFoundErr) { if isErrorOtherThan(err, JSNoMessageFoundErr) {
return formatError("loading session record", err) return formatError("loading session record", err)
@@ -2434,6 +2524,7 @@ func (as *mqttAccountSessionManager) createOrRestoreSession(clientID string, opt
if err := json.Unmarshal(smsg.Data, ps); err != nil { if err := json.Unmarshal(smsg.Data, ps); err != nil {
return formatError(fmt.Sprintf("unmarshal of session record at sequence %v", smsg.Sequence), err) return formatError(fmt.Sprintf("unmarshal of session record at sequence %v", smsg.Sequence), err)
} }
// Restore this session (even if we don't own it), the caller will do the right thing. // Restore this session (even if we don't own it), the caller will do the right thing.
sess := mqttSessionCreate(jsa, clientID, hash, smsg.Sequence, opts) sess := mqttSessionCreate(jsa, clientID, hash, smsg.Sequence, opts)
sess.domainTk = as.domainTk sess.domainTk = as.domainTk
@@ -2479,7 +2570,7 @@ func (as *mqttAccountSessionManager) transferUniqueSessStreamsToMuxed(log *Serve
}() }()
jsa := &as.jsa jsa := &as.jsa
sni, err := jsa.newRequestEx(mqttJSAStreamNames, JSApiStreams, 0, nil, 5*time.Second) sni, err := jsa.newRequestEx(mqttJSAStreamNames, JSApiStreams, _EMPTY_, 0, nil, 5*time.Second)
if err != nil { if err != nil {
log.Errorf("Unable to transfer MQTT session streams: %v", err) log.Errorf("Unable to transfer MQTT session streams: %v", err)
return return
@@ -2514,10 +2605,8 @@ func (as *mqttAccountSessionManager) transferUniqueSessStreamsToMuxed(log *Serve
log.Warnf(" Unable to unmarshal the content of this stream, may not be a legitimate MQTT session stream, skipping") log.Warnf(" Unable to unmarshal the content of this stream, may not be a legitimate MQTT session stream, skipping")
continue continue
} }
// Compute subject where the session is being stored
subject := mqttSessStreamSubjectPrefix + as.domainTk + getHash(ps.ID)
// Store record to MQTT session stream // Store record to MQTT session stream
if _, err := jsa.storeMsgWithKind(mqttJSASessPersist, subject, 0, smsg.Data); err != nil { if _, err := jsa.storeSessionMsg(as.domainTk, getHash(ps.ID), 0, smsg.Data); err != nil {
log.Errorf(" Unable to transfer the session record: %v", err) log.Errorf(" Unable to transfer the session record: %v", err)
return return
} }
@@ -2553,7 +2642,8 @@ func (as *mqttAccountSessionManager) transferRetainedToPerKeySubjectStream(log *
} }
// Store the message again, this time with the new per-key subject. // Store the message again, this time with the new per-key subject.
subject := mqttRetainedMsgsStreamSubject + rmsg.Subject subject := mqttRetainedMsgsStreamSubject + rmsg.Subject
if _, err := jsa.storeMsgWithKind(mqttJSASessPersist, subject, 0, smsg.Data); err != nil {
if _, err := jsa.storeMsg(subject, 0, smsg.Data); err != nil {
log.Errorf(" Unable to transfer the retained message with sequence %d: %v", smsg.Sequence, err) log.Errorf(" Unable to transfer the retained message with sequence %d: %v", smsg.Sequence, err)
errors++ errors++
continue continue
@@ -2619,7 +2709,7 @@ func (sess *mqttSession) save() error {
} }
b, _ := json.Marshal(&ps) b, _ := json.Marshal(&ps)
subject := mqttSessStreamSubjectPrefix + sess.domainTk + sess.idHash domainTk, cidHash := sess.domainTk, sess.idHash
seq := sess.seq seq := sess.seq
sess.mu.Unlock() sess.mu.Unlock()
@@ -2637,7 +2727,7 @@ func (sess *mqttSession) save() error {
b = bb.Bytes() b = bb.Bytes()
} }
resp, err := sess.jsa.storeMsgWithKind(mqttJSASessPersist, subject, hdr, b) resp, err := sess.jsa.storeSessionMsg(domainTk, cidHash, hdr, b)
if err != nil { if err != nil {
return fmt.Errorf("unable to persist session %q (seq=%v): %v", ps.ID, seq, err) return fmt.Errorf("unable to persist session %q (seq=%v): %v", ps.ID, seq, err)
} }
@@ -2691,8 +2781,13 @@ func (sess *mqttSession) clear() error {
} }
if seq > 0 { if seq > 0 {
if err := sess.jsa.deleteMsg(mqttSessStreamName, seq, true); err != nil { err := sess.jsa.deleteMsg(mqttSessStreamName, seq, true)
return fmt.Errorf("unable to delete session %q record at sequence %v", id, seq) // Ignore the various errors indicating that the message (or sequence)
// is already deleted, can happen in a cluster.
if isErrorOtherThan(err, JSSequenceNotFoundErrF) {
if isErrorOtherThan(err, JSStreamMsgDeleteFailedF) || !strings.Contains(err.Error(), ErrStoreMsgNotFound.Error()) {
return fmt.Errorf("unable to delete session %q record at sequence %v: %v", id, seq, err)
}
} }
} }
return nil return nil
@@ -3149,6 +3244,9 @@ func (c *client) mqttConnectTrace(cp *mqttConnectProto) string {
trace += fmt.Sprintf(" will=(topic=%s QoS=%v retain=%v)", trace += fmt.Sprintf(" will=(topic=%s QoS=%v retain=%v)",
cp.will.topic, cp.will.qos, cp.will.retain) cp.will.topic, cp.will.qos, cp.will.retain)
} }
if cp.flags&mqttConnFlagCleanSession != 0 {
trace += " clean"
}
if c.opts.Username != _EMPTY_ { if c.opts.Username != _EMPTY_ {
trace += fmt.Sprintf(" username=%s", c.opts.Username) trace += fmt.Sprintf(" username=%s", c.opts.Username)
} }
@@ -4349,11 +4447,11 @@ func mqttIsReservedSub(sub *subscription, subject string) bool {
// Check if a sub is a reserved wildcard. E.g. '#', '*', or '*/" prefix. // Check if a sub is a reserved wildcard. E.g. '#', '*', or '*/" prefix.
func isMQTTReservedSubscription(subject string) bool { func isMQTTReservedSubscription(subject string) bool {
if len(subject) == 1 && subject[0] == fwc || subject[0] == pwc { if len(subject) == 1 && (subject[0] == fwc || subject[0] == pwc) {
return true return true
} }
// Match "*.<>" // Match "*.<>"
if len(subject) > 1 && subject[0] == pwc && subject[1] == btsep { if len(subject) > 1 && (subject[0] == pwc && subject[1] == btsep) {
return true return true
} }
return false return false
@@ -4467,9 +4565,6 @@ func (sess *mqttSession) cleanupFailedSub(c *client, sub *subscription, cc *Cons
// Make sure we are set up to deliver PUBREL messages to this QoS2-subscribed // Make sure we are set up to deliver PUBREL messages to this QoS2-subscribed
// session. // session.
//
// Session lock held on entry. Need to make sure no other subscribe packet races
// to do the same.
func (sess *mqttSession) ensurePubRelConsumerSubscription(c *client) error { func (sess *mqttSession) ensurePubRelConsumerSubscription(c *client) error {
opts := c.srv.getOpts() opts := c.srv.getOpts()
ackWait := opts.MQTT.AckWait ackWait := opts.MQTT.AckWait
@@ -4481,21 +4576,32 @@ func (sess *mqttSession) ensurePubRelConsumerSubscription(c *client) error {
maxAckPending = mqttDefaultMaxAckPending maxAckPending = mqttDefaultMaxAckPending
} }
sess.mu.Lock()
pubRelSubscribed := sess.pubRelSubscribed
pubRelSubject := sess.pubRelSubject
pubRelDeliverySubjectB := sess.pubRelDeliverySubjectB
pubRelDeliverySubject := sess.pubRelDeliverySubject
pubRelConsumer := sess.pubRelConsumer
tmaxack := sess.tmaxack
idHash := sess.idHash
id := sess.id
sess.mu.Unlock()
// Subscribe before the consumer is created so we don't loose any messages. // Subscribe before the consumer is created so we don't loose any messages.
if !sess.pubRelSubscribed { if !pubRelSubscribed {
_, err := c.processSub(sess.pubRelDeliverySubjectB, nil, sess.pubRelDeliverySubjectB, _, err := c.processSub(pubRelDeliverySubjectB, nil, pubRelDeliverySubjectB,
mqttDeliverPubRelCb, false) mqttDeliverPubRelCb, false)
if err != nil { if err != nil {
c.Errorf("Unable to create subscription for JetStream consumer on %q: %v", sess.pubRelDeliverySubject, err) c.Errorf("Unable to create subscription for JetStream consumer on %q: %v", pubRelDeliverySubject, err)
return err return err
} }
sess.pubRelSubscribed = true pubRelSubscribed = true
} }
// Create the consumer if needed. // Create the consumer if needed.
if sess.pubRelConsumer == nil { if pubRelConsumer == nil {
// Check that the limit of subs' maxAckPending are not going over the limit // Check that the limit of subs' maxAckPending are not going over the limit
if after := sess.tmaxack + maxAckPending; after > mqttMaxAckTotalLimit { if after := tmaxack + maxAckPending; after > mqttMaxAckTotalLimit {
return fmt.Errorf("max_ack_pending for all consumers would be %v which exceeds the limit of %v", return fmt.Errorf("max_ack_pending for all consumers would be %v which exceeds the limit of %v",
after, mqttMaxAckTotalLimit) after, mqttMaxAckTotalLimit)
} }
@@ -4503,11 +4609,11 @@ func (sess *mqttSession) ensurePubRelConsumerSubscription(c *client) error {
ccr := &CreateConsumerRequest{ ccr := &CreateConsumerRequest{
Stream: mqttOutStreamName, Stream: mqttOutStreamName,
Config: ConsumerConfig{ Config: ConsumerConfig{
DeliverSubject: sess.pubRelDeliverySubject, DeliverSubject: pubRelDeliverySubject,
Durable: mqttPubRelConsumerDurablePrefix + sess.idHash, Durable: mqttPubRelConsumerDurablePrefix + idHash,
AckPolicy: AckExplicit, AckPolicy: AckExplicit,
DeliverPolicy: DeliverNew, DeliverPolicy: DeliverNew,
FilterSubject: sess.pubRelSubject, FilterSubject: pubRelSubject,
AckWait: ackWait, AckWait: ackWait,
MaxAckPending: maxAckPending, MaxAckPending: maxAckPending,
MemoryStorage: opts.MQTT.ConsumerMemoryStorage, MemoryStorage: opts.MQTT.ConsumerMemoryStorage,
@@ -4517,28 +4623,41 @@ func (sess *mqttSession) ensurePubRelConsumerSubscription(c *client) error {
ccr.Config.InactiveThreshold = opts.MQTT.ConsumerInactiveThreshold ccr.Config.InactiveThreshold = opts.MQTT.ConsumerInactiveThreshold
} }
if _, err := sess.jsa.createConsumer(ccr); err != nil { if _, err := sess.jsa.createConsumer(ccr); err != nil {
c.Errorf("Unable to add JetStream consumer for PUBREL for client %q: err=%v", sess.id, err) c.Errorf("Unable to add JetStream consumer for PUBREL for client %q: err=%v", id, err)
return err return err
} }
sess.pubRelConsumer = &ccr.Config pubRelConsumer = &ccr.Config
sess.tmaxack += maxAckPending tmaxack += maxAckPending
} }
sess.mu.Lock()
sess.pubRelSubscribed = pubRelSubscribed
sess.pubRelConsumer = pubRelConsumer
sess.tmaxack = tmaxack
sess.mu.Unlock()
return nil return nil
} }
// When invoked with a QoS of 0, looks for an existing JS durable consumer for // When invoked with a QoS of 0, looks for an existing JS durable consumer for
// the given sid and if one is found, delete the JS durable consumer and unsub // the given sid and if one is found, delete the JS durable consumer and unsub
// the NATS subscription on the delivery subject. // the NATS subscription on the delivery subject.
//
// With a QoS > 0, creates or update the existing JS durable consumer along with // With a QoS > 0, creates or update the existing JS durable consumer along with
// its NATS subscription on a delivery subject. // its NATS subscription on a delivery subject.
// //
// Lock not held on entry, but session is in the locked map. // Session lock is acquired and released as needed. Session is in the locked
// map.
func (sess *mqttSession) processJSConsumer(c *client, subject, sid string, func (sess *mqttSession) processJSConsumer(c *client, subject, sid string,
qos byte, fromSubProto bool) (*ConsumerConfig, *subscription, error) { qos byte, fromSubProto bool) (*ConsumerConfig, *subscription, error) {
// Check if we are already a JS consumer for this SID. sess.mu.Lock()
cc, exists := sess.cons[sid] cc, exists := sess.cons[sid]
tmaxack := sess.tmaxack
idHash := sess.idHash
sess.mu.Unlock()
// Check if we are already a JS consumer for this SID.
if exists { if exists {
// If current QoS is 0, it means that we need to delete the existing // If current QoS is 0, it means that we need to delete the existing
// one (that was QoS > 0) // one (that was QoS > 0)
@@ -4547,7 +4666,11 @@ func (sess *mqttSession) processJSConsumer(c *client, subject, sid string,
// the form: mqttSubPrefix + <nuid>. It is also used as the sid // the form: mqttSubPrefix + <nuid>. It is also used as the sid
// for the NATS subscription, so use that for the lookup. // for the NATS subscription, so use that for the lookup.
sub := c.subs[cc.DeliverSubject] sub := c.subs[cc.DeliverSubject]
sess.mu.Lock()
delete(sess.cons, sid) delete(sess.cons, sid)
sess.mu.Unlock()
sess.deleteConsumer(cc) sess.deleteConsumer(cc)
if sub != nil { if sub != nil {
c.processUnsub(sub.sid) c.processUnsub(sub.sid)
@@ -4583,12 +4706,12 @@ func (sess *mqttSession) processJSConsumer(c *client, subject, sid string,
} }
// Check that the limit of subs' maxAckPending are not going over the limit // Check that the limit of subs' maxAckPending are not going over the limit
if after := sess.tmaxack + maxAckPending; after > mqttMaxAckTotalLimit { if after := tmaxack + maxAckPending; after > mqttMaxAckTotalLimit {
return nil, nil, fmt.Errorf("max_ack_pending for all consumers would be %v which exceeds the limit of %v", return nil, nil, fmt.Errorf("max_ack_pending for all consumers would be %v which exceeds the limit of %v",
after, mqttMaxAckTotalLimit) after, mqttMaxAckTotalLimit)
} }
durName := sess.idHash + "_" + nuid.Next() durName := idHash + "_" + nuid.Next()
ccr := &CreateConsumerRequest{ ccr := &CreateConsumerRequest{
Stream: mqttStreamName, Stream: mqttStreamName,
Config: ConsumerConfig{ Config: ConsumerConfig{
@@ -4610,25 +4733,22 @@ func (sess *mqttSession) processJSConsumer(c *client, subject, sid string,
return nil, nil, err return nil, nil, err
} }
cc = &ccr.Config cc = &ccr.Config
sess.tmaxack += maxAckPending tmaxack += maxAckPending
} }
// This is an internal subscription on subject like "$MQTT.sub.<nuid>" that is setup // This is an internal subscription on subject like "$MQTT.sub.<nuid>" that is setup
// for the JS durable's deliver subject. // for the JS durable's deliver subject.
sess.mu.Lock() sess.mu.Lock()
sub, err := c.processSub([]byte(inbox), nil, []byte(inbox), mqttDeliverMsgCbQoS12, false) sess.tmaxack = tmaxack
sub, err := sess.processSub(c, []byte(inbox), []byte(inbox),
isMQTTReservedSubscription(subject), qos, cc.Durable, mqttDeliverMsgCbQoS12, false)
sess.mu.Unlock()
if err != nil { if err != nil {
sess.mu.Unlock()
sess.deleteConsumer(cc) sess.deleteConsumer(cc)
c.Errorf("Unable to create subscription for JetStream consumer on %q: %v", subject, err) c.Errorf("Unable to create subscription for JetStream consumer on %q: %v", subject, err)
return nil, nil, err return nil, nil, err
} }
if sub.mqtt == nil {
sub.mqtt = &mqttSub{}
}
sub.mqtt.qos = qos
sub.mqtt.jsDur = cc.Durable
sub.mqtt.reserved = isMQTTReservedSubscription(subject)
sess.mu.Unlock()
return cc, sub, nil return cc, sub, nil
} }
+279 -156
View File
@@ -103,7 +103,6 @@ const (
Follower RaftState = iota Follower RaftState = iota
Leader Leader
Candidate Candidate
Observer
Closed Closed
) )
@@ -115,8 +114,6 @@ func (state RaftState) String() string {
return "CANDIDATE" return "CANDIDATE"
case Leader: case Leader:
return "LEADER" return "LEADER"
case Observer:
return "OBSERVER"
case Closed: case Closed:
return "CLOSED" return "CLOSED"
} }
@@ -125,108 +122,105 @@ func (state RaftState) String() string {
type raft struct { type raft struct {
sync.RWMutex sync.RWMutex
created time.Time
group string created time.Time // Time that the group was created
sd string accName string // Account name of the asset this raft group is for
id string group string // Raft group
wal WAL sd string // Store directory
wtype StorageType id string // Node ID
track bool
werr error wal WAL // WAL store (filestore or memstore)
wtype StorageType // WAL type, e.g. FileStorage or MemoryStorage
track bool //
werr error // Last write error
state atomic.Int32 // RaftState state atomic.Int32 // RaftState
hh hash.Hash64 hh hash.Hash64 // Highwayhash, used for snapshots
snapfile string snapfile string // Snapshot filename
csz int
qn int
peers map[string]*lps
removed map[string]struct{}
acks map[uint64]map[string]struct{}
pae map[uint64]*appendEntry
elect *time.Timer
active time.Time
llqrt time.Time
lsut time.Time
term uint64 // The current vote term
pterm uint64 // Previous term from the last snapshot
pindex uint64 // Previous index from the last snapshot
commit uint64 // Sequence number of the most recent commit
applied uint64 // Sequence number of the most recently applied commit
leader string // The ID of the leader
vote string
hash string
s *Server
c *client
js *jetStream
dflag bool
pleader bool
observer bool
extSt extensionState
// Subjects for votes, updates, replays. csz int // Cluster size
psubj string qn int // Number of nodes needed to establish quorum
rpsubj string peers map[string]*lps // Other peers in the Raft group
vsubj string
vreply string
asubj string
areply string
sq *sendq removed map[string]struct{} // Peers that were removed from the group
aesub *subscription acks map[uint64]map[string]struct{} // Append entry responses/acks, map of entry index -> peer ID
pae map[uint64]*appendEntry // Pending append entries
// Are we doing a leadership transfer. elect *time.Timer // Election timer, normally accessed via electTimer
lxfer bool active time.Time // Last activity time, i.e. for heartbeats
llqrt time.Time // Last quorum lost time
lsut time.Time // Last scale-up time
// For holding term and vote and peerstate to be written. term uint64 // The current vote term
wtv []byte pterm uint64 // Previous term from the last snapshot
wps []byte pindex uint64 // Previous index from the last snapshot
wtvch chan struct{} commit uint64 // Sequence number of the most recent commit
wpsch chan struct{} applied uint64 // Sequence number of the most recently applied commit
// For when we need to catch up as a follower. leader string // The ID of the leader
catchup *catchupState vote string // Our current vote state
lxfer bool // Are we doing a leadership transfer?
// For leader or server catching up a follower. s *Server // Reference to top-level server
progress map[string]*ipQueue[uint64] c *client // Internal client for subscriptions
js *jetStream // JetStream, if running, to see if we are out of resources
// For when we have paused our applyC. dflag bool // Debug flag
paused bool pleader bool // Has the group ever had a leader?
hcommit uint64 observer bool // The node is observing, i.e. not participating in voting
pobserver bool extSt extensionState // Extension state
// Queues and Channels psubj string // Proposals subject
prop *ipQueue[*Entry] rpsubj string // Remove peers subject
entry *ipQueue[*appendEntry] vsubj string // Vote requests subject
resp *ipQueue[*appendEntryResponse] vreply string // Vote responses subject
apply *ipQueue[*CommittedEntry] asubj string // Append entries subject
reqs *ipQueue[*voteRequest] areply string // Append entries responses subject
votes *ipQueue[*voteResponse]
stepdown *ipQueue[string]
leadc chan bool
quit chan struct{}
// Account name of the asset this raft group is for sq *sendq // Send queue for outbound RPC messages
accName string aesub *subscription // Subscription for handleAppendEntry callbacks
// Random generator, used to generate inboxes for instance wtv []byte // Term and vote to be written
prand *rand.Rand wps []byte // Peer state to be written
wtvch chan struct{} // Signals when a term vote was just written, to kick file writer
wpsch chan struct{} // Signals when a peer state was just written, to kick file writer
catchup *catchupState // For when we need to catch up as a follower.
progress map[string]*ipQueue[uint64] // For leader or server catching up a follower.
paused bool // Whether or not applies are paused
hcommit uint64 // The commit at the time that applies were paused
pobserver bool // Whether we were an observer at the time that applies were paused
prop *ipQueue[*Entry] // Proposals
entry *ipQueue[*appendEntry] // Append entries
resp *ipQueue[*appendEntryResponse] // Append entries responses
apply *ipQueue[*CommittedEntry] // Apply queue (committed entries to be passed to upper layer)
reqs *ipQueue[*voteRequest] // Vote requests
votes *ipQueue[*voteResponse] // Vote responses
stepdown *ipQueue[string] // Stepdown requests
leadc chan bool // Leader changes
quit chan struct{} // Raft group shutdown
prand *rand.Rand // Random generator, used to generate inboxes for instance
} }
// cacthupState structure that holds our subscription, and catchup term and index // cacthupState structure that holds our subscription, and catchup term and index
// as well as starting term and index and how many updates we have seen. // as well as starting term and index and how many updates we have seen.
type catchupState struct { type catchupState struct {
sub *subscription sub *subscription // Subscription that catchup messages will arrive on
cterm uint64 cterm uint64 // Catchup term
cindex uint64 cindex uint64 // Catchup index
pterm uint64 pterm uint64 // Starting term
pindex uint64 pindex uint64 // Starting index
active time.Time active time.Time // Last time we received a message for this catchup
} }
// lps holds peer state of last time and last index replicated. // lps holds peer state of last time and last index replicated.
type lps struct { type lps struct {
ts int64 ts int64 // Last timestamp
li uint64 li uint64 // Last index replicated
kp bool // marks as known peer. kp bool // Known peer
} }
const ( const (
@@ -237,7 +231,6 @@ const (
hbIntervalDefault = 1 * time.Second hbIntervalDefault = 1 * time.Second
lostQuorumIntervalDefault = hbIntervalDefault * 10 // 10 seconds lostQuorumIntervalDefault = hbIntervalDefault * 10 // 10 seconds
lostQuorumCheckIntervalDefault = hbIntervalDefault * 10 // 10 seconds lostQuorumCheckIntervalDefault = hbIntervalDefault * 10 // 10 seconds
) )
var ( var (
@@ -383,7 +376,6 @@ func (s *Server) startRaftNode(accName string, cfg *RaftConfig, labels pprofLabe
track: cfg.Track, track: cfg.Track,
csz: ps.clusterSize, csz: ps.clusterSize,
qn: ps.clusterSize/2 + 1, qn: ps.clusterSize/2 + 1,
hash: hash,
peers: make(map[string]*lps), peers: make(map[string]*lps),
acks: make(map[uint64]map[string]struct{}), acks: make(map[uint64]map[string]struct{}),
pae: make(map[uint64]*appendEntry), pae: make(map[uint64]*appendEntry),
@@ -413,14 +405,19 @@ func (s *Server) startRaftNode(accName string, cfg *RaftConfig, labels pprofLabe
n.dflag = true n.dflag = true
} }
// Set up the highwayhash for the snapshots.
key := sha256.Sum256([]byte(n.group)) key := sha256.Sum256([]byte(n.group))
n.hh, _ = highwayhash.New64(key[:]) n.hh, _ = highwayhash.New64(key[:])
// If we have a term and vote file (tav.idx on the filesystem) then read in
// what we think the term and vote was. It's possible these are out of date
// so a catch-up may be required.
if term, vote, err := n.readTermVote(); err == nil && term > 0 { if term, vote, err := n.readTermVote(); err == nil && term > 0 {
n.term = term n.term = term
n.vote = vote n.vote = vote
} }
// Make sure that the snapshots directory exists.
if err := os.MkdirAll(filepath.Join(n.sd, snapshotsDir), 0750); err != nil { if err := os.MkdirAll(filepath.Join(n.sd, snapshotsDir), 0750); err != nil {
return nil, fmt.Errorf("could not create snapshots directory - %v", err) return nil, fmt.Errorf("could not create snapshots directory - %v", err)
} }
@@ -433,6 +430,9 @@ func (s *Server) startRaftNode(accName string, cfg *RaftConfig, labels pprofLabe
n.setupLastSnapshot() n.setupLastSnapshot()
} }
// Retrieve the stream state from the WAL. If there are pending append
// entries that were committed but not applied before we last shut down,
// we will try to replay them and process them here.
var state StreamState var state StreamState
n.wal.FastState(&state) n.wal.FastState(&state)
if state.Msgs > 0 { if state.Msgs > 0 {
@@ -444,6 +444,8 @@ func (s *Server) startRaftNode(accName string, cfg *RaftConfig, labels pprofLabe
} }
} }
// It looks like there are entries we have committed but not applied
// yet. Replay them.
for index := state.FirstSeq; index <= state.LastSeq; index++ { for index := state.FirstSeq; index <= state.LastSeq; index++ {
ae, err := n.loadEntry(index) ae, err := n.loadEntry(index)
if err != nil { if err != nil {
@@ -469,15 +471,18 @@ func (s *Server) startRaftNode(accName string, cfg *RaftConfig, labels pprofLabe
// Make sure to track ourselves. // Make sure to track ourselves.
n.peers[n.id] = &lps{time.Now().UnixNano(), 0, true} n.peers[n.id] = &lps{time.Now().UnixNano(), 0, true}
// Track known peers // Track known peers
for _, peer := range ps.knownPeers { for _, peer := range ps.knownPeers {
// Set these to 0 to start but mark as known peer.
if peer != n.id { if peer != n.id {
// Set these to 0 to start but mark as known peer.
n.peers[peer] = &lps{0, 0, true} n.peers[peer] = &lps{0, 0, true}
} }
} }
// Setup our internal subscriptions. // Setup our internal subscriptions for proposals, votes and append entries.
// If we fail to do this for some reason then this is fatal — we cannot
// continue setting up or the Raft node may be partially/totally isolated.
if err := n.createInternalSubs(); err != nil { if err := n.createInternalSubs(); err != nil {
n.shutdown(true) n.shutdown(true)
return nil, err return nil, err
@@ -486,18 +491,26 @@ func (s *Server) startRaftNode(accName string, cfg *RaftConfig, labels pprofLabe
n.debug("Started") n.debug("Started")
// Check if we need to start in observer mode due to lame duck status. // Check if we need to start in observer mode due to lame duck status.
// This will stop us from taking on the leader role when we're about to
// shutdown anyway.
if s.isLameDuckMode() { if s.isLameDuckMode() {
n.debug("Will start in observer mode due to lame duck status") n.debug("Will start in observer mode due to lame duck status")
n.SetObserver(true) n.SetObserver(true)
} }
// Set the election timer and lost quorum timers to now, so that we
// won't accidentally trigger either state without knowing the real state
// of the other nodes.
n.Lock() n.Lock()
n.resetElectionTimeout() n.resetElectionTimeout()
n.llqrt = time.Now() n.llqrt = time.Now()
n.Unlock() n.Unlock()
// Register the Raft group.
labels["group"] = n.group labels["group"] = n.group
s.registerRaftNode(n.group, n) s.registerRaftNode(n.group, n)
// Start the goroutines for the Raft state machine and the file writer.
s.startGoRoutine(n.run, labels) s.startGoRoutine(n.run, labels)
s.startGoRoutine(n.fileWriter) s.startGoRoutine(n.fileWriter)
@@ -529,7 +542,8 @@ func (s *Server) clusterNameForNode(node string) string {
return _EMPTY_ return _EMPTY_
} }
// Server will track all raft nodes. // Registers the Raft node with the server, as it will track all of the Raft
// nodes.
func (s *Server) registerRaftNode(group string, n RaftNode) { func (s *Server) registerRaftNode(group string, n RaftNode) {
s.rnMu.Lock() s.rnMu.Lock()
defer s.rnMu.Unlock() defer s.rnMu.Unlock()
@@ -539,6 +553,7 @@ func (s *Server) registerRaftNode(group string, n RaftNode) {
s.raftNodes[group] = n s.raftNodes[group] = n
} }
// Unregisters the Raft node from the server, i.e. at shutdown.
func (s *Server) unregisterRaftNode(group string) { func (s *Server) unregisterRaftNode(group string) {
s.rnMu.Lock() s.rnMu.Lock()
defer s.rnMu.Unlock() defer s.rnMu.Unlock()
@@ -547,12 +562,15 @@ func (s *Server) unregisterRaftNode(group string) {
} }
} }
// Returns how many Raft nodes are running in this server instance.
func (s *Server) numRaftNodes() int { func (s *Server) numRaftNodes() int {
s.rnMu.Lock() s.rnMu.Lock()
defer s.rnMu.Unlock() defer s.rnMu.Unlock()
return len(s.raftNodes) return len(s.raftNodes)
} }
// Finds the Raft node for a given Raft group, if any. If there is no Raft node
// running for this group then it can return nil.
func (s *Server) lookupRaftNode(group string) RaftNode { func (s *Server) lookupRaftNode(group string) RaftNode {
s.rnMu.RLock() s.rnMu.RLock()
defer s.rnMu.RUnlock() defer s.rnMu.RUnlock()
@@ -563,6 +581,8 @@ func (s *Server) lookupRaftNode(group string) RaftNode {
return n return n
} }
// Reloads the debug state for all running Raft nodes. This is necessary when
// the configuration has been reloaded and the debug log level has changed.
func (s *Server) reloadDebugRaftNodes(debug bool) { func (s *Server) reloadDebugRaftNodes(debug bool) {
if s == nil { if s == nil {
return return
@@ -577,15 +597,19 @@ func (s *Server) reloadDebugRaftNodes(debug bool) {
s.rnMu.RUnlock() s.rnMu.RUnlock()
} }
// Requests that all Raft nodes on this server step down and place them into
// observer mode. This is called when the server is shutting down.
func (s *Server) stepdownRaftNodes() { func (s *Server) stepdownRaftNodes() {
if s == nil { if s == nil {
return return
} }
var nodes []RaftNode
s.rnMu.RLock() s.rnMu.RLock()
if len(s.raftNodes) > 0 { if len(s.raftNodes) == 0 {
s.Debugf("Stepping down all leader raft nodes") s.rnMu.RUnlock()
return
} }
s.Debugf("Stepping down all leader raft nodes")
nodes := make([]RaftNode, 0, len(s.raftNodes))
for _, n := range s.raftNodes { for _, n := range s.raftNodes {
nodes = append(nodes, n) nodes = append(nodes, n)
} }
@@ -599,15 +623,20 @@ func (s *Server) stepdownRaftNodes() {
} }
} }
// Shuts down all Raft nodes on this server. This is called either when the
// server is either entering lame duck mode, shutting down or when JetStream
// has been disabled.
func (s *Server) shutdownRaftNodes() { func (s *Server) shutdownRaftNodes() {
if s == nil { if s == nil {
return return
} }
var nodes []RaftNode
s.rnMu.RLock() s.rnMu.RLock()
if len(s.raftNodes) > 0 { if len(s.raftNodes) == 0 {
s.Debugf("Shutting down all raft nodes") s.rnMu.RUnlock()
return
} }
nodes := make([]RaftNode, 0, len(s.raftNodes))
s.Debugf("Shutting down all raft nodes")
for _, n := range s.raftNodes { for _, n := range s.raftNodes {
nodes = append(nodes, n) nodes = append(nodes, n)
} }
@@ -625,11 +654,12 @@ func (s *Server) transferRaftLeaders() bool {
if s == nil { if s == nil {
return false return false
} }
var nodes []RaftNode
s.rnMu.RLock() s.rnMu.RLock()
if len(s.raftNodes) > 0 { if len(s.raftNodes) == 0 {
s.Debugf("Transferring any raft leaders") s.rnMu.RUnlock()
return false
} }
nodes := make([]RaftNode, 0, len(s.raftNodes))
for _, n := range s.raftNodes { for _, n := range s.raftNodes {
nodes = append(nodes, n) nodes = append(nodes, n)
} }
@@ -668,7 +698,8 @@ func (n *raft) Propose(data []byte) error {
return nil return nil
} }
// ProposeDirect will propose entries directly. // ProposeDirect will propose entries directly by skipping the Raft state
// machine and sending them straight to the wire instead.
// This should only be called on the leader. // This should only be called on the leader.
func (n *raft) ProposeDirect(entries []*Entry) error { func (n *raft) ProposeDirect(entries []*Entry) error {
if state := n.State(); state != Leader { if state := n.State(); state != Leader {
@@ -746,13 +777,16 @@ func (n *raft) ProposeRemovePeer(peer string) error {
return werr return werr
} }
// If we are the leader then we are responsible for processing the
// peer remove and then notifying the rest of the group that the
// peer was removed.
if isLeader { if isLeader {
prop.push(newEntry(EntryRemovePeer, []byte(peer))) prop.push(newEntry(EntryRemovePeer, []byte(peer)))
n.doRemovePeerAsLeader(peer) n.doRemovePeerAsLeader(peer)
return nil return nil
} }
// Need to forward. // Otherwise we need to forward the proposal to the leader.
n.sendRPC(subj, _EMPTY_, []byte(peer)) n.sendRPC(subj, _EMPTY_, []byte(peer))
return nil return nil
} }
@@ -779,7 +813,8 @@ func (n *raft) AdjustBootClusterSize(csz int) error {
if csz < 2 { if csz < 2 {
csz = 2 csz = 2
} }
// Adjust. // Adjust the cluster size and the number of nodes needed to establish
// a quorum.
n.csz = csz n.csz = csz
n.qn = n.csz/2 + 1 n.qn = n.csz/2 + 1
@@ -798,7 +833,8 @@ func (n *raft) AdjustClusterSize(csz int) error {
csz = 2 csz = 2
} }
// Adjust. // Adjust the cluster size and the number of nodes needed to establish
// a quorum.
n.csz = csz n.csz = csz
n.qn = n.csz/2 + 1 n.qn = n.csz/2 + 1
n.Unlock() n.Unlock()
@@ -808,7 +844,8 @@ func (n *raft) AdjustClusterSize(csz int) error {
} }
// PauseApply will allow us to pause processing of append entries onto our // PauseApply will allow us to pause processing of append entries onto our
// external apply chan. // external apply queue. In effect this means that the upper layer will no longer
// receive any new entries from the Raft group.
func (n *raft) PauseApply() error { func (n *raft) PauseApply() error {
if n.State() == Leader { if n.State() == Leader {
return errAlreadyLeader return errAlreadyLeader
@@ -832,6 +869,8 @@ func (n *raft) PauseApply() error {
return nil return nil
} }
// ResumeApply will resume sending applies to the external apply queue. This
// means that we will start sending new entries to the upper layer.
func (n *raft) ResumeApply() { func (n *raft) ResumeApply() {
n.Lock() n.Lock()
defer n.Unlock() defer n.Unlock()
@@ -862,8 +901,9 @@ func (n *raft) ResumeApply() {
} }
} }
// Applied is to be called when the FSM has applied the committed entries. // Applied is a callback that must be be called by the upper layer when it
// Applied will return the number of entries and an estimation of the // has successfully applied the committed entries that it received from the
// apply queue. It will return the number of entries and an estimation of the
// byte size that could be removed with a snapshot/compact. // byte size that could be removed with a snapshot/compact.
func (n *raft) Applied(index uint64) (entries uint64, bytes uint64) { func (n *raft) Applied(index uint64) (entries uint64, bytes uint64) {
n.Lock() n.Lock()
@@ -878,6 +918,9 @@ func (n *raft) Applied(index uint64) (entries uint64, bytes uint64) {
if index > n.applied { if index > n.applied {
n.applied = index n.applied = index
} }
// Calculate the number of entries and estimate the byte size that
// we can now remove with a compaction/snapshot.
var state StreamState var state StreamState
n.wal.FastState(&state) n.wal.FastState(&state)
if n.applied > state.FirstSeq { if n.applied > state.FirstSeq {
@@ -945,11 +988,14 @@ func (n *raft) InstallSnapshot(data []byte) error {
n.Lock() n.Lock()
// If a write error has occurred already then stop here.
if werr := n.werr; werr != nil { if werr := n.werr; werr != nil {
n.Unlock() n.Unlock()
return werr return werr
} }
// Check that a catchup isn't already taking place. If it is then we won't
// allow installing snapshots until it is done.
if len(n.progress) > 0 { if len(n.progress) > 0 {
n.Unlock() n.Unlock()
return errCatchupsRunning return errCatchupsRunning
@@ -967,10 +1013,13 @@ func (n *raft) InstallSnapshot(data []byte) error {
var term uint64 var term uint64
if ae, _ := n.loadEntry(n.applied); ae != nil { if ae, _ := n.loadEntry(n.applied); ae != nil {
// Use the term from the most recently applied entry if possible.
term = ae.term term = ae.term
} else if ae, _ = n.loadFirstEntry(); ae != nil { } else if ae, _ = n.loadFirstEntry(); ae != nil {
// Otherwise see if we can find the term from the first entry.
term = ae.term term = ae.term
} else { } else {
// Last resort is to use the last pterm that we knew of.
term = n.pterm term = n.pterm
} }
@@ -1013,6 +1062,9 @@ func (n *raft) InstallSnapshot(data []byte) error {
return nil return nil
} }
// NeedSnapshot returns true if it is necessary to try to install a snapshot, i.e.
// after we have finished recovering/replaying at startup, on a regular interval or
// as a part of cleaning up when shutting down.
func (n *raft) NeedSnapshot() bool { func (n *raft) NeedSnapshot() bool {
n.RLock() n.RLock()
defer n.RUnlock() defer n.RUnlock()
@@ -1024,6 +1076,8 @@ const (
snapFileT = "snap.%d.%d" snapFileT = "snap.%d.%d"
) )
// termAndIndexFromSnapfile tries to load the snapshot file and returns the term
// and index from that snapshot.
func termAndIndexFromSnapFile(sn string) (term, index uint64, err error) { func termAndIndexFromSnapFile(sn string) (term, index uint64, err error) {
if sn == _EMPTY_ { if sn == _EMPTY_ {
return 0, 0, errBadSnapName return 0, 0, errBadSnapName
@@ -1035,6 +1089,9 @@ func termAndIndexFromSnapFile(sn string) (term, index uint64, err error) {
return term, index, nil return term, index, nil
} }
// setupLastSnapshot is called at startup to try and recover the last snapshot from
// the disk if possible. We will try to recover the term, index and commit/applied
// indices and then notify the upper layer what we found. Compacts the WAL if needed.
func (n *raft) setupLastSnapshot() { func (n *raft) setupLastSnapshot() {
snapDir := filepath.Join(n.sd, snapshotsDir) snapDir := filepath.Join(n.sd, snapshotsDir)
psnaps, err := os.ReadDir(snapDir) psnaps, err := os.ReadDir(snapDir)
@@ -1084,19 +1141,25 @@ func (n *raft) setupLastSnapshot() {
n.snapfile = latest n.snapfile = latest
snap, err := n.loadLastSnapshot() snap, err := n.loadLastSnapshot()
if err != nil { if err != nil {
// We failed to recover the last snapshot for some reason, so we will
// assume it has been corrupted and will try to delete it.
if n.snapfile != _EMPTY_ { if n.snapfile != _EMPTY_ {
os.Remove(n.snapfile) os.Remove(n.snapfile)
n.snapfile = _EMPTY_ n.snapfile = _EMPTY_
} }
} else { return
n.pindex = snap.lastIndex }
n.pterm = snap.lastTerm
n.commit = snap.lastIndex // We successfully recovered the last snapshot from the disk.
n.applied = snap.lastIndex // Recover state from the snapshot and then notify the upper layer.
n.apply.push(newCommittedEntry(n.commit, []*Entry{{EntrySnapshot, snap.data}})) // Compact the WAL when we're done if needed.
if _, err := n.wal.Compact(snap.lastIndex + 1); err != nil { n.pindex = snap.lastIndex
n.setWriteErrLocked(err) n.pterm = snap.lastTerm
} n.commit = snap.lastIndex
n.applied = snap.lastIndex
n.apply.push(newCommittedEntry(n.commit, []*Entry{{EntrySnapshot, snap.data}}))
if _, err := n.wal.Compact(snap.lastIndex + 1); err != nil {
n.setWriteErrLocked(err)
} }
} }
@@ -1162,14 +1225,18 @@ func (n *raft) Leader() bool {
return n.State() == Leader return n.State() == Leader
} }
// isCatchingUp returns true if a catchup is currently taking place.
func (n *raft) isCatchingUp() bool { func (n *raft) isCatchingUp() bool {
n.RLock() n.RLock()
defer n.RUnlock() defer n.RUnlock()
return n.catchup != nil return n.catchup != nil
} }
// This function may block for up to ~10ms to check // isCurrent is called from the healthchecks and returns true if we believe
// forward progress in some cases. // that the upper layer is current with the Raft layer, i.e. that it has applied
// all of the commits that we have given it.
// Optionally we can also check whether or not we're making forward progress if we
// aren't current, in which case this function may block for up to ~10ms to find out.
// Lock should be held. // Lock should be held.
func (n *raft) isCurrent(includeForwardProgress bool) bool { func (n *raft) isCurrent(includeForwardProgress bool) bool {
// Check if we are closed. // Check if we are closed.
@@ -1220,7 +1287,7 @@ func (n *raft) isCurrent(includeForwardProgress bool) bool {
// Otherwise, wait for a short period of time and see if we are making any // Otherwise, wait for a short period of time and see if we are making any
// forward progress. // forward progress.
if startDelta := n.commit - n.applied; startDelta > 0 { if startDelta := n.commit - n.applied; startDelta > 0 {
for i := 0; i < 10; i++ { // 5ms, in 0.5ms increments for i := 0; i < 10; i++ { // 10ms, in 1ms increments
n.Unlock() n.Unlock()
time.Sleep(time.Millisecond) time.Sleep(time.Millisecond)
n.Lock() n.Lock()
@@ -1480,9 +1547,16 @@ func (n *raft) UpdateKnownPeers(knownPeers []string) {
} }
} }
// ApplyQ returns the apply queue that new commits will be sent to for the
// upper layer to apply.
func (n *raft) ApplyQ() *ipQueue[*CommittedEntry] { return n.apply } func (n *raft) ApplyQ() *ipQueue[*CommittedEntry] { return n.apply }
func (n *raft) LeadChangeC() <-chan bool { return n.leadc }
func (n *raft) QuitC() <-chan struct{} { return n.quit } // LeadChangeC returns the leader change channel, notifying when the Raft
// leader role has moved.
func (n *raft) LeadChangeC() <-chan bool { return n.leadc }
// QuitC returns the quit channel, notifying when the Raft group has shut down.
func (n *raft) QuitC() <-chan struct{} { return n.quit }
func (n *raft) Created() time.Time { func (n *raft) Created() time.Time {
n.RLock() n.RLock()
@@ -1679,6 +1753,10 @@ func (n *raft) resetElectWithLock(et time.Duration) {
n.Unlock() n.Unlock()
} }
// run is the top-level runner for the Raft state machine. Depending on the
// state of the node (leader, follower, candidate, observer), this will call
// through to other functions. It is expected that this function will run for
// the entire life of the Raft node once started.
func (n *raft) run() { func (n *raft) run() {
s := n.s s := n.s
defer s.grWG.Done() defer s.grWG.Done()
@@ -1715,9 +1793,6 @@ func (n *raft) run() {
n.runAsCandidate() n.runAsCandidate()
case Leader: case Leader:
n.runAsLeader() n.runAsLeader()
case Observer:
// TODO(dlc) - fix.
n.runAsFollower()
case Closed: case Closed:
return return
} }
@@ -1765,7 +1840,8 @@ func (n *raft) setObserver(isObserver bool, extSt extensionState) {
n.extSt = extSt n.extSt = extSt
} }
// Invoked when being notified that there is something in the entryc's queue // processAppendEntries is called by the Raft state machine when there are
// new append entries to be committed and sent to the upper state machine.
func (n *raft) processAppendEntries() { func (n *raft) processAppendEntries() {
canProcess := true canProcess := true
if n.isClosed() { if n.isClosed() {
@@ -1776,7 +1852,8 @@ func (n *raft) processAppendEntries() {
n.debug("AppendEntry not processing inbound, no resources") n.debug("AppendEntry not processing inbound, no resources")
canProcess = false canProcess = false
} }
// Always pop the entries, but check if we can process them. // Always pop the entries, but check if we can process them. If we can't
// then the entries are effectively dropped.
aes := n.entry.pop() aes := n.entry.pop()
if canProcess { if canProcess {
for _, ae := range aes { for _, ae := range aes {
@@ -1786,19 +1863,25 @@ func (n *raft) processAppendEntries() {
n.entry.recycle(&aes) n.entry.recycle(&aes)
} }
// runAsFollower is called by run and will block for as long as the node is
// running in the follower state.
func (n *raft) runAsFollower() { func (n *raft) runAsFollower() {
for n.State() == Follower { for {
elect := n.electTimer() elect := n.electTimer()
select { select {
case <-n.entry.ch: case <-n.entry.ch:
// New append entries have arrived over the network.
n.processAppendEntries() n.processAppendEntries()
case <-n.s.quitCh: case <-n.s.quitCh:
// The server is shutting down.
n.shutdown(false) n.shutdown(false)
return return
case <-n.quit: case <-n.quit:
// The Raft node is shutting down.
return return
case <-elect.C: case <-elect.C:
// The election timer has fired so we think it's time to call an election.
// If we are out of resources we just want to stay in this state for the moment. // If we are out of resources we just want to stay in this state for the moment.
if n.outOfResources() { if n.outOfResources() {
n.resetElectionTimeoutWithLock() n.resetElectionTimeoutWithLock()
@@ -1820,17 +1903,23 @@ func (n *raft) runAsFollower() {
return return
} }
case <-n.votes.ch: case <-n.votes.ch:
// We're receiving votes from the network, probably because we have only
// just stepped down and they were already in flight. Ignore them.
n.debug("Ignoring old vote response, we have stepped down") n.debug("Ignoring old vote response, we have stepped down")
n.votes.popOne() n.votes.popOne()
case <-n.resp.ch: case <-n.resp.ch:
// Ignore // We're receiving append entry responses from the network, probably because
// we have only just stepped down and they were already in flight. Ignore them.
n.resp.popOne() n.resp.popOne()
case <-n.reqs.ch: case <-n.reqs.ch:
// We've just received a vote request from the network.
// Because of drain() it is possible that we get nil from popOne(). // Because of drain() it is possible that we get nil from popOne().
if voteReq, ok := n.reqs.popOne(); ok { if voteReq, ok := n.reqs.popOne(); ok {
n.processVoteRequest(voteReq) n.processVoteRequest(voteReq)
} }
case <-n.stepdown.ch: case <-n.stepdown.ch:
// We've received a stepdown request, start following the new leader if
// we can.
if newLeader, ok := n.stepdown.popOne(); ok { if newLeader, ok := n.stepdown.popOne(); ok {
n.switchToFollower(newLeader) n.switchToFollower(newLeader)
return return
@@ -1839,26 +1928,29 @@ func (n *raft) runAsFollower() {
} }
} }
// Pool for CommitedEntry re-use. // Pool for CommittedEntry re-use.
var cePool = sync.Pool{ var cePool = sync.Pool{
New: func() any { New: func() any {
return &CommittedEntry{} return &CommittedEntry{}
}, },
} }
// CommitEntry is handed back to the user to apply a commit to their FSM. // CommittedEntry is handed back to the user to apply a commit to their upper layer.
type CommittedEntry struct { type CommittedEntry struct {
Index uint64 Index uint64
Entries []*Entry Entries []*Entry
} }
// Create a new ComittedEntry. // Create a new CommittedEntry. When the returned entry is no longer needed, it
// should be returned to the pool by calling ReturnToPool.
func newCommittedEntry(index uint64, entries []*Entry) *CommittedEntry { func newCommittedEntry(index uint64, entries []*Entry) *CommittedEntry {
ce := cePool.Get().(*CommittedEntry) ce := cePool.Get().(*CommittedEntry)
ce.Index, ce.Entries = index, entries ce.Index, ce.Entries = index, entries
return ce return ce
} }
// ReturnToPool returns the CommittedEntry to the pool, after which point it is
// no longer safe to reuse.
func (ce *CommittedEntry) ReturnToPool() { func (ce *CommittedEntry) ReturnToPool() {
if ce == nil { if ce == nil {
return return
@@ -1879,7 +1971,8 @@ var entryPool = sync.Pool{
}, },
} }
// Helper to create new entries. // Helper to create new entries. When the returned entry is no longer needed, it
// should be returned to the entryPool pool.
func newEntry(t EntryType, data []byte) *Entry { func newEntry(t EntryType, data []byte) *Entry {
entry := entryPool.Get().(*Entry) entry := entryPool.Get().(*Entry)
entry.Type, entry.Data = t, data entry.Type, entry.Data = t, data
@@ -1895,15 +1988,15 @@ var aePool = sync.Pool{
// appendEntry is the main struct that is used to sync raft peers. // appendEntry is the main struct that is used to sync raft peers.
type appendEntry struct { type appendEntry struct {
leader string leader string // The leader that this append entry came from.
term uint64 term uint64 // The current term, as the leader understands it.
commit uint64 commit uint64 // The commit index, as the leader understands it.
pterm uint64 pterm uint64 // The previous term, for checking consistency.
pindex uint64 pindex uint64 // The previous commit index, for checking consistency.
entries []*Entry entries []*Entry // Entries to process.
// internal use only. // Below fields are for internal use only:
reply string reply string // Reply subject to respond to once committed.
sub *subscription sub *subscription // The subscription that the append entry came in on.
buf []byte buf []byte
} }
@@ -2166,12 +2259,15 @@ func (n *raft) runAsLeader() {
// For forwarded proposals, both normal and remove peer proposals. // For forwarded proposals, both normal and remove peer proposals.
fsub, err := n.subscribe(psubj, n.handleForwardedProposal) fsub, err := n.subscribe(psubj, n.handleForwardedProposal)
if err != nil { if err != nil {
n.debug("Error subscribing to forwarded proposals: %v", err) n.warn("Error subscribing to forwarded proposals: %v", err)
n.stepdown.push(noLeader)
return return
} }
rpsub, err := n.subscribe(rpsubj, n.handleForwardedRemovePeerProposal) rpsub, err := n.subscribe(rpsubj, n.handleForwardedRemovePeerProposal)
if err != nil { if err != nil {
n.debug("Error subscribing to forwarded proposals: %v", err) n.warn("Error subscribing to forwarded remove peer proposals: %v", err)
n.unsubscribe(fsub)
n.stepdown.push(noLeader)
return return
} }
@@ -2537,7 +2633,7 @@ func (n *raft) loadEntry(index uint64) (*appendEntry, error) {
return n.decodeAppendEntry(sm.msg, nil, _EMPTY_) return n.decodeAppendEntry(sm.msg, nil, _EMPTY_)
} }
// applyCommit will update our commit index and apply the entry to the apply chan. // applyCommit will update our commit index and apply the entry to the apply queue.
// lock should be held. // lock should be held.
func (n *raft) applyCommit(index uint64) error { func (n *raft) applyCommit(index uint64) error {
if n.State() == Closed { if n.State() == Closed {
@@ -2779,7 +2875,7 @@ func (n *raft) runAsCandidate() {
// We vote for ourselves. // We vote for ourselves.
votes := 1 votes := 1
for n.State() == Candidate { for {
elect := n.electTimer() elect := n.electTimer()
select { select {
case <-n.entry.ch: case <-n.entry.ch:
@@ -2840,16 +2936,22 @@ func (n *raft) runAsCandidate() {
} }
} }
// handleAppendEntry handles an append entry from the wire. // handleAppendEntry handles an append entry from the wire. This function
// is an internal callback from the "asubj" append entry subscription.
func (n *raft) handleAppendEntry(sub *subscription, c *client, _ *Account, subject, reply string, msg []byte) { func (n *raft) handleAppendEntry(sub *subscription, c *client, _ *Account, subject, reply string, msg []byte) {
msg = copyBytes(msg) msg = copyBytes(msg)
if ae, err := n.decodeAppendEntry(msg, sub, reply); err == nil { if ae, err := n.decodeAppendEntry(msg, sub, reply); err == nil {
// Push to the new entry channel. From here one of the worker
// goroutines (runAsLeader, runAsFollower, runAsCandidate) will
// pick it up.
n.entry.push(ae) n.entry.push(ae)
} else { } else {
n.warn("AppendEntry failed to be placed on internal channel: corrupt entry") n.warn("AppendEntry failed to be placed on internal channel: corrupt entry")
} }
} }
// cancelCatchup will stop an in-flight catchup by unsubscribing from the
// catchup subscription.
// Lock should be held. // Lock should be held.
func (n *raft) cancelCatchup() { func (n *raft) cancelCatchup() {
n.debug("Canceling catchup subscription since we are now up to date") n.debug("Canceling catchup subscription since we are now up to date")
@@ -2875,6 +2977,9 @@ func (n *raft) catchupStalled() bool {
return false return false
} }
// createCatchup will create the state needed to track a catchup as it
// runs. It then creates a unique inbox for this catchup and subscribes
// to it. The remote side will stream entries to that subject.
// Lock should be held. // Lock should be held.
func (n *raft) createCatchup(ae *appendEntry) string { func (n *raft) createCatchup(ae *appendEntry) string {
// Cleanup any old ones. // Cleanup any old ones.
@@ -2938,7 +3043,7 @@ func (n *raft) truncateWAL(term, index uint64) {
n.term, n.pterm, n.pindex = term, term, index n.term, n.pterm, n.pindex = term, term, index
} }
// Reset our WAL. // Reset our WAL. This is equivalent to truncating all data from the log.
// Lock should be held. // Lock should be held.
func (n *raft) resetWAL() { func (n *raft) resetWAL() {
n.truncateWAL(0, 0) n.truncateWAL(0, 0)
@@ -2952,7 +3057,9 @@ func (n *raft) updateLeader(newLeader string) {
} }
} }
// processAppendEntry will process an appendEntry. // processAppendEntry will process an appendEntry. This is called either
// during recovery or from processAppendEntries when there are new entries
// to be committed.
func (n *raft) processAppendEntry(ae *appendEntry, sub *subscription) { func (n *raft) processAppendEntry(ae *appendEntry, sub *subscription) {
n.Lock() n.Lock()
// Don't reset here if we have been asked to assume leader position. // Don't reset here if we have been asked to assume leader position.
@@ -2991,7 +3098,9 @@ func (n *raft) processAppendEntry(ae *appendEntry, sub *subscription) {
return return
} }
// If we received an append entry as a candidate we should convert to a follower. // If we received an append entry as a candidate then it would appear that
// another node has taken on the leader role already, so we should convert
// to a follower of that node instead.
if n.State() == Candidate { if n.State() == Candidate {
n.debug("Received append entry in candidate state from %q, converting to follower", ae.leader) n.debug("Received append entry in candidate state from %q, converting to follower", ae.leader)
if n.term < ae.term { if n.term < ae.term {
@@ -3004,7 +3113,8 @@ func (n *raft) processAppendEntry(ae *appendEntry, sub *subscription) {
// Catching up state. // Catching up state.
catchingUp := n.catchup != nil catchingUp := n.catchup != nil
// Is this a new entry? // Is this a new entry? New entries will be delivered on the append entry
// sub, rather than a catch-up sub.
isNew := sub != nil && sub == n.aesub isNew := sub != nil && sub == n.aesub
// Track leader directly // Track leader directly
@@ -3204,7 +3314,7 @@ func (n *raft) processAppendEntry(ae *appendEntry, sub *subscription) {
n.lxfer = true n.lxfer = true
n.xferCampaign() n.xferCampaign()
} else if n.paused && !n.pobserver { } else if n.paused && !n.pobserver {
// Here we can become a leader but need to wait for resume of the apply channel. // Here we can become a leader but need to wait for resume of the apply queue.
n.lxfer = true n.lxfer = true
} }
} else { } else {
@@ -3254,9 +3364,12 @@ func (n *raft) processAppendEntry(ae *appendEntry, sub *subscription) {
} }
} }
// processPeerState is called when a peer state entry is received
// over the wire or when we're updating known peers.
// Lock should be held. // Lock should be held.
func (n *raft) processPeerState(ps *peerState) { func (n *raft) processPeerState(ps *peerState) {
// Update our version of peers to that of the leader. // Update our version of peers to that of the leader. Calculate
// the number of nodes needed to establish a quorum.
n.csz = ps.clusterSize n.csz = ps.clusterSize
n.qn = n.csz/2 + 1 n.qn = n.csz/2 + 1
@@ -3274,15 +3387,19 @@ func (n *raft) processPeerState(ps *peerState) {
n.writePeerState(ps) n.writePeerState(ps)
} }
// Process a response. // processAppendEntryResponse is called when we receive an append entry
// response from another node. They will send a confirmation to tell us
// whether they successfully committed the entry or not.
func (n *raft) processAppendEntryResponse(ar *appendEntryResponse) { func (n *raft) processAppendEntryResponse(ar *appendEntryResponse) {
n.trackPeer(ar.peer) n.trackPeer(ar.peer)
if ar.success { if ar.success {
// The remote node successfully committed the append entry.
n.trackResponse(ar) n.trackResponse(ar)
arPool.Put(ar) arPool.Put(ar)
} else if ar.term > n.term { } else if ar.term > n.term {
// False here and they have a higher term. // The remote node didn't commit the append entry, it looks like
// they are on a newer term than we are. Step down.
n.Lock() n.Lock()
n.term = ar.term n.term = ar.term
n.vote = noVote n.vote = noVote
@@ -3293,6 +3410,8 @@ func (n *raft) processAppendEntryResponse(ar *appendEntryResponse) {
n.Unlock() n.Unlock()
arPool.Put(ar) arPool.Put(ar)
} else if ar.reply != _EMPTY_ { } else if ar.reply != _EMPTY_ {
// The remote node didn't commit the append entry and they are
// still on the same term, so let's try to catch them up.
n.catchupFollower(ar) n.catchupFollower(ar)
} }
} }
@@ -3308,7 +3427,8 @@ func (n *raft) buildAppendEntry(entries []*Entry) *appendEntry {
return newAppendEntry(n.id, n.term, n.commit, n.pterm, n.pindex, entries) return newAppendEntry(n.id, n.term, n.commit, n.pterm, n.pindex, entries)
} }
// Determine if we should store an entry. // Determine if we should store an entry. This stops us from storing
// heartbeat messages.
func (ae *appendEntry) shouldStore() bool { func (ae *appendEntry) shouldStore() bool {
return ae != nil && len(ae.entries) > 0 return ae != nil && len(ae.entries) > 0
} }
@@ -3621,6 +3741,7 @@ func (n *raft) fileWriter() {
case <-n.quit: case <-n.quit:
return return
case <-n.wtvch: case <-n.wtvch:
// We've been asked to write out the term-and-vote file.
var buf [termVoteLen]byte var buf [termVoteLen]byte
n.RLock() n.RLock()
copy(buf[0:], n.wtv) copy(buf[0:], n.wtv)
@@ -3633,6 +3754,7 @@ func (n *raft) fileWriter() {
n.warn("Error writing term and vote file for %q: %v", n.group, err) n.warn("Error writing term and vote file for %q: %v", n.group, err)
} }
case <-n.wpsch: case <-n.wpsch:
// We've been asked to write out the peer state file.
n.RLock() n.RLock()
buf := copyBytes(n.wps) buf := copyBytes(n.wps)
n.RUnlock() n.RUnlock()
@@ -3656,7 +3778,7 @@ func (n *raft) writeTermVote() {
copy(buf[8:], n.vote) copy(buf[8:], n.vote)
b := buf[:8+len(n.vote)] b := buf[:8+len(n.vote)]
// If same as what we have we can ignore. // If the term and vote hasn't changed then don't rewrite to disk.
if bytes.Equal(n.wtv, b) { if bytes.Equal(n.wtv, b) {
return return
} }
@@ -3734,7 +3856,8 @@ func (n *raft) processVoteRequest(vr *voteRequest) error {
vresp := &voteResponse{n.term, n.id, false} vresp := &voteResponse{n.term, n.id, false}
defer n.debug("Sending a voteResponse %+v -> %q", vresp, vr.reply) defer n.debug("Sending a voteResponse %+v -> %q", vresp, vr.reply)
// Ignore if we are newer. // Ignore if we are newer. This is important so that we don't accidentally process
// votes from a previous term if they were still in flight somewhere.
if vr.term < n.term { if vr.term < n.term {
n.Unlock() n.Unlock()
n.sendReply(vr.reply, vresp.encode()) n.sendReply(vr.reply, vresp.encode())
+55 -46
View File
@@ -239,6 +239,7 @@ type stream struct {
ddindex int ddindex int
ddtmr *time.Timer ddtmr *time.Timer
qch chan struct{} qch chan struct{}
mqch chan struct{}
active bool active bool
ddloaded bool ddloaded bool
closed bool closed bool
@@ -558,6 +559,7 @@ func (a *Account) addStreamWithAssignment(config *StreamConfig, fsConfig *FileSt
msgs: newIPQueue[*inMsg](s, qpfx+"messages"), msgs: newIPQueue[*inMsg](s, qpfx+"messages"),
gets: newIPQueue[*directGetReq](s, qpfx+"direct gets"), gets: newIPQueue[*directGetReq](s, qpfx+"direct gets"),
qch: make(chan struct{}), qch: make(chan struct{}),
mqch: make(chan struct{}),
uch: make(chan struct{}, 4), uch: make(chan struct{}, 4),
sch: make(chan struct{}, 1), sch: make(chan struct{}, 1),
} }
@@ -785,6 +787,15 @@ func (mset *stream) setStreamAssignment(sa *streamAssignment) {
} }
} }
func (mset *stream) monitorQuitC() <-chan struct{} {
if mset == nil {
return nil
}
mset.mu.RLock()
defer mset.mu.RUnlock()
return mset.mqch
}
func (mset *stream) updateC() <-chan struct{} { func (mset *stream) updateC() <-chan struct{} {
if mset == nil { if mset == nil {
return nil return nil
@@ -985,14 +996,6 @@ func (mset *stream) lastSeqAndCLFS() (uint64, uint64) {
return mset.lseq, mset.getCLFS() return mset.lseq, mset.getCLFS()
} }
func (mset *stream) clearCLFS() uint64 {
mset.clMu.Lock()
defer mset.clMu.Unlock()
clfs := mset.clfs
mset.clfs, mset.clseq = 0, 0
return clfs
}
func (mset *stream) getCLFS() uint64 { func (mset *stream) getCLFS() uint64 {
mset.clMu.Lock() mset.clMu.Lock()
defer mset.clMu.Unlock() defer mset.clMu.Unlock()
@@ -4077,6 +4080,7 @@ func (mset *stream) processInboundJetStreamMsg(_ *subscription, c *client, _ *Ac
var ( var (
errLastSeqMismatch = errors.New("last sequence mismatch") errLastSeqMismatch = errors.New("last sequence mismatch")
errMsgIdDuplicate = errors.New("msgid is duplicate") errMsgIdDuplicate = errors.New("msgid is duplicate")
errStreamClosed = errors.New("stream closed")
) )
// processJetStreamMsg is where we try to actually process the stream msg. // processJetStreamMsg is where we try to actually process the stream msg.
@@ -4085,7 +4089,7 @@ func (mset *stream) processJetStreamMsg(subject, reply string, hdr, msg []byte,
c, s, store := mset.client, mset.srv, mset.store c, s, store := mset.client, mset.srv, mset.store
if mset.closed || c == nil { if mset.closed || c == nil {
mset.mu.Unlock() mset.mu.Unlock()
return nil return errStreamClosed
} }
// Apply the input subject transform if any // Apply the input subject transform if any
@@ -4415,7 +4419,6 @@ func (mset *stream) processJetStreamMsg(subject, reply string, hdr, msg []byte,
// Make sure to take into account any message assignments that we had to skip (clfs). // Make sure to take into account any message assignments that we had to skip (clfs).
seq = lseq + 1 - clfs seq = lseq + 1 - clfs
// Check for preAcks and the need to skip vs store. // Check for preAcks and the need to skip vs store.
if mset.hasAllPreAcks(seq, subject) { if mset.hasAllPreAcks(seq, subject) {
mset.clearAllPreAcks(seq) mset.clearAllPreAcks(seq)
store.SkipMsg() store.SkipMsg()
@@ -4907,9 +4910,28 @@ func (mset *stream) stop(deleteFlag, advisory bool) error {
accName := jsa.account.Name accName := jsa.account.Name
jsa.mu.Unlock() jsa.mu.Unlock()
// Clean up consumers. // Mark as closed, kick monitor and collect consumers first.
mset.mu.Lock() mset.mu.Lock()
mset.closed = true mset.closed = true
// Signal to the monitor loop.
// Can't use qch here.
if mset.mqch != nil {
close(mset.mqch)
mset.mqch = nil
}
// Stop responding to sync requests.
mset.stopClusterSubs()
// Unsubscribe from direct stream.
mset.unsubscribeToStream(true)
// Our info sub if we spun it up.
if mset.infoSub != nil {
mset.srv.sysUnsubscribe(mset.infoSub)
mset.infoSub = nil
}
// Clean up consumers.
var obs []*consumer var obs []*consumer
for _, o := range mset.consumers { for _, o := range mset.consumers {
obs = append(obs, o) obs = append(obs, o)
@@ -4930,21 +4952,6 @@ func (mset *stream) stop(deleteFlag, advisory bool) error {
mset.cancelSourceConsumer(si.iname) mset.cancelSourceConsumer(si.iname)
} }
} }
// Cluster cleanup
var sa *streamAssignment
if n := mset.node; n != nil {
if deleteFlag {
n.Delete()
sa = mset.sa
} else {
if n.NeedSnapshot() {
// Attempt snapshot on clean exit.
n.InstallSnapshot(mset.stateSnapshotLocked())
}
n.Stop()
}
}
mset.mu.Unlock() mset.mu.Unlock()
isShuttingDown := js.isShuttingDown() isShuttingDown := js.isShuttingDown()
@@ -4961,17 +4968,6 @@ func (mset *stream) stop(deleteFlag, advisory bool) error {
} }
mset.mu.Lock() mset.mu.Lock()
// Stop responding to sync requests.
mset.stopClusterSubs()
// Unsubscribe from direct stream.
mset.unsubscribeToStream(true)
// Our info sub if we spun it up.
if mset.infoSub != nil {
mset.srv.sysUnsubscribe(mset.infoSub)
mset.infoSub = nil
}
// Send stream delete advisory after the consumers. // Send stream delete advisory after the consumers.
if deleteFlag && advisory { if deleteFlag && advisory {
mset.sendDeleteAdvisoryLocked() mset.sendDeleteAdvisoryLocked()
@@ -4983,11 +4979,17 @@ func (mset *stream) stop(deleteFlag, advisory bool) error {
mset.qch = nil mset.qch = nil
} }
c := mset.client // Cluster cleanup
mset.client = nil var sa *streamAssignment
if c == nil { if n := mset.node; n != nil {
mset.mu.Unlock() if deleteFlag {
return nil n.Delete()
sa = mset.sa
} else {
// Always attempt snapshot on clean exit.
n.InstallSnapshot(mset.stateSnapshotLocked())
n.Stop()
}
} }
// Cleanup duplicate timer if running. // Cleanup duplicate timer if running.
@@ -5013,6 +5015,8 @@ func (mset *stream) stop(deleteFlag, advisory bool) error {
// Snapshot store. // Snapshot store.
store := mset.store store := mset.store
c := mset.client
mset.client = nil
// Clustered cleanup. // Clustered cleanup.
mset.mu.Unlock() mset.mu.Unlock()
@@ -5027,7 +5031,9 @@ func (mset *stream) stop(deleteFlag, advisory bool) error {
js.mu.Unlock() js.mu.Unlock()
} }
c.closeConnection(ClientClosed) if c != nil {
c.closeConnection(ClientClosed)
}
if sysc != nil { if sysc != nil {
sysc.closeConnection(ClientClosed) sysc.closeConnection(ClientClosed)
@@ -5042,9 +5048,12 @@ func (mset *stream) stop(deleteFlag, advisory bool) error {
js.releaseStreamResources(&mset.cfg) js.releaseStreamResources(&mset.cfg)
// cleanup directories after the stream // cleanup directories after the stream
accDir := filepath.Join(js.config.StoreDir, accName) accDir := filepath.Join(js.config.StoreDir, accName)
// no op if not empty // Do cleanup in separate go routine similar to how fs will use purge here..
os.Remove(filepath.Join(accDir, streamsDir)) go func() {
os.Remove(accDir) // no op if not empty
os.Remove(filepath.Join(accDir, streamsDir))
os.Remove(accDir)
}()
} else if store != nil { } else if store != nil {
// Ignore errors. // Ignore errors.
store.Stop() store.Stop()
+2 -2
View File
@@ -693,9 +693,9 @@ func (s *Server) wsUpgrade(w http.ResponseWriter, r *http.Request) (*wsUpgradeRe
kind := CLIENT kind := CLIENT
if r.URL != nil { if r.URL != nil {
ep := r.URL.EscapedPath() ep := r.URL.EscapedPath()
if strings.HasPrefix(ep, leafNodeWSPath) { if strings.HasSuffix(ep, leafNodeWSPath) {
kind = LEAF kind = LEAF
} else if strings.HasPrefix(ep, mqttWSPath) { } else if strings.HasSuffix(ep, mqttWSPath) {
kind = MQTT kind = MQTT
} }
} }
+4 -4
View File
@@ -1371,10 +1371,10 @@ github.com/mohae/deepcopy
# github.com/mschoch/smat v0.2.0 # github.com/mschoch/smat v0.2.0
## explicit; go 1.13 ## explicit; go 1.13
github.com/mschoch/smat github.com/mschoch/smat
# github.com/nats-io/jwt/v2 v2.5.2 # github.com/nats-io/jwt/v2 v2.5.3
## explicit; go 1.18 ## explicit; go 1.18
github.com/nats-io/jwt/v2 github.com/nats-io/jwt/v2
# github.com/nats-io/nats-server/v2 v2.10.4 # github.com/nats-io/nats-server/v2 v2.10.5
## explicit; go 1.20 ## explicit; go 1.20
github.com/nats-io/nats-server/v2/conf github.com/nats-io/nats-server/v2/conf
github.com/nats-io/nats-server/v2/internal/ldap github.com/nats-io/nats-server/v2/internal/ldap
@@ -2079,8 +2079,8 @@ golang.org/x/text/transform
golang.org/x/text/unicode/bidi golang.org/x/text/unicode/bidi
golang.org/x/text/unicode/norm golang.org/x/text/unicode/norm
golang.org/x/text/width golang.org/x/text/width
# golang.org/x/time v0.3.0 # golang.org/x/time v0.4.0
## explicit ## explicit; go 1.18
golang.org/x/time/rate golang.org/x/time/rate
# golang.org/x/tools v0.14.0 # golang.org/x/tools v0.14.0
## explicit; go 1.18 ## explicit; go 1.18