Merge pull request #166 from opencloud-eu/dependabot/go_modules/github.com/nats-io/nats-server/v2-2.10.25
Bump github.com/nats-io/nats-server/v2 from 2.10.24 to 2.10.25
This commit is contained in:
@@ -57,7 +57,7 @@ require (
|
|||||||
github.com/mitchellh/mapstructure v1.5.0
|
github.com/mitchellh/mapstructure v1.5.0
|
||||||
github.com/mna/pigeon v1.3.0
|
github.com/mna/pigeon v1.3.0
|
||||||
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.24
|
github.com/nats-io/nats-server/v2 v2.10.25
|
||||||
github.com/nats-io/nats.go v1.38.0
|
github.com/nats-io/nats.go v1.38.0
|
||||||
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
|
||||||
@@ -323,7 +323,7 @@ require (
|
|||||||
go.uber.org/zap v1.23.0 // indirect
|
go.uber.org/zap v1.23.0 // indirect
|
||||||
golang.org/x/mod v0.22.0 // indirect
|
golang.org/x/mod v0.22.0 // indirect
|
||||||
golang.org/x/sys v0.29.0 // indirect
|
golang.org/x/sys v0.29.0 // indirect
|
||||||
golang.org/x/time v0.8.0 // indirect
|
golang.org/x/time v0.9.0 // indirect
|
||||||
golang.org/x/tools v0.28.0 // indirect
|
golang.org/x/tools v0.28.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/genproto v0.0.0-20241118233622-e639e219e697 // indirect
|
google.golang.org/genproto v0.0.0-20241118233622-e639e219e697 // indirect
|
||||||
|
|||||||
@@ -823,8 +823,8 @@ github.com/mwitkow/go-conntrack v0.0.0-20190716064945-2f068394615f/go.mod h1:qRW
|
|||||||
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.7.3 h1:6bNPK+FXgBeAqdj4cYQ0F8ViHRbi7woQLq4W29nUAzE=
|
github.com/nats-io/jwt/v2 v2.7.3 h1:6bNPK+FXgBeAqdj4cYQ0F8ViHRbi7woQLq4W29nUAzE=
|
||||||
github.com/nats-io/jwt/v2 v2.7.3/go.mod h1:GvkcbHhKquj3pkioy5put1wvPxs78UlZ7D/pY+BgZk4=
|
github.com/nats-io/jwt/v2 v2.7.3/go.mod h1:GvkcbHhKquj3pkioy5put1wvPxs78UlZ7D/pY+BgZk4=
|
||||||
github.com/nats-io/nats-server/v2 v2.10.24 h1:KcqqQAD0ZZcG4yLxtvSFJY7CYKVYlnlWoAiVZ6i/IY4=
|
github.com/nats-io/nats-server/v2 v2.10.25 h1:J0GWLDDXo5HId7ti/lTmBfs+lzhmu8RPkoKl0eSCqwc=
|
||||||
github.com/nats-io/nats-server/v2 v2.10.24/go.mod h1:olvKt8E5ZlnjyqBGbAXtxvSQKsPodISK5Eo/euIta4s=
|
github.com/nats-io/nats-server/v2 v2.10.25/go.mod h1:/YYYQO7cuoOBt+A7/8cVjuhWTaTUEAlZbJT+3sMAfFU=
|
||||||
github.com/nats-io/nats.go v1.38.0 h1:A7P+g7Wjp4/NWqDOOP/K6hfhr54DvdDQUznt5JFg9XA=
|
github.com/nats-io/nats.go v1.38.0 h1:A7P+g7Wjp4/NWqDOOP/K6hfhr54DvdDQUznt5JFg9XA=
|
||||||
github.com/nats-io/nats.go v1.38.0/go.mod h1:IGUM++TwokGnXPs82/wCuiHS02/aKrdYUQkU8If6yjw=
|
github.com/nats-io/nats.go v1.38.0/go.mod h1:IGUM++TwokGnXPs82/wCuiHS02/aKrdYUQkU8If6yjw=
|
||||||
github.com/nats-io/nkeys v0.4.9 h1:qe9Faq2Gxwi6RZnZMXfmGMZkg3afLLOtrU+gDZJ35b0=
|
github.com/nats-io/nkeys v0.4.9 h1:qe9Faq2Gxwi6RZnZMXfmGMZkg3afLLOtrU+gDZJ35b0=
|
||||||
@@ -1464,8 +1464,8 @@ golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxb
|
|||||||
golang.org/x/time v0.0.0-20200630173020-3af7569d3a1e/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
golang.org/x/time v0.0.0-20200630173020-3af7569d3a1e/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||||
golang.org/x/time v0.0.0-20201208040808-7e3f01d25324/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
golang.org/x/time v0.0.0-20201208040808-7e3f01d25324/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-20210220033141-f8bda1e9f3ba/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||||
golang.org/x/time v0.8.0 h1:9i3RxcPv3PZnitoVGMPDKZSq1xW1gK1Xy3ArNOGZfEg=
|
golang.org/x/time v0.9.0 h1:EsRrnYcQiGH+5FfbgvV4AP7qEZstoyrHB0DzarOQ4ZY=
|
||||||
golang.org/x/time v0.8.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM=
|
golang.org/x/time v0.9.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-20180828015842-6cd1fcedba52/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-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||||
|
|||||||
+1
-1
@@ -55,7 +55,7 @@ func init() {
|
|||||||
|
|
||||||
const (
|
const (
|
||||||
// VERSION is the current version for the server.
|
// VERSION is the current version for the server.
|
||||||
VERSION = "2.10.24"
|
VERSION = "2.10.25"
|
||||||
|
|
||||||
// PROTO is the currently supported protocol.
|
// PROTO is the currently supported protocol.
|
||||||
// 0 was the original
|
// 0 was the original
|
||||||
|
|||||||
+94
-79
@@ -1373,6 +1373,8 @@ func (o *consumer) setLeader(isLeader bool) {
|
|||||||
// If we were the leader make sure to drain queued up acks.
|
// If we were the leader make sure to drain queued up acks.
|
||||||
if wasLeader {
|
if wasLeader {
|
||||||
o.ackMsgs.drain()
|
o.ackMsgs.drain()
|
||||||
|
// Reset amount of acks that need to be processed.
|
||||||
|
atomic.StoreInt64(&o.awl, 0)
|
||||||
// Also remove any pending replies since we should not be the one to respond at this point.
|
// Also remove any pending replies since we should not be the one to respond at this point.
|
||||||
o.replies = nil
|
o.replies = nil
|
||||||
}
|
}
|
||||||
@@ -1415,8 +1417,23 @@ func (o *consumer) unsubscribe(sub *subscription) {
|
|||||||
|
|
||||||
// We need to make sure we protect access to the outq.
|
// We need to make sure we protect access to the outq.
|
||||||
// Do all advisory sends here.
|
// Do all advisory sends here.
|
||||||
func (o *consumer) sendAdvisory(subj string, msg []byte) {
|
func (o *consumer) sendAdvisory(subject string, e any) {
|
||||||
o.outq.sendMsg(subj, msg)
|
if o.acc == nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// If there is no one listening for this advisory then save ourselves the effort
|
||||||
|
// and don't bother encoding the JSON or sending it.
|
||||||
|
if sl := o.acc.sl; (sl != nil && !sl.HasInterest(subject)) && !o.srv.hasGatewayInterest(o.acc.Name, subject) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
j, err := json.Marshal(e)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
o.outq.sendMsg(subject, j)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (o *consumer) sendDeleteAdvisoryLocked() {
|
func (o *consumer) sendDeleteAdvisoryLocked() {
|
||||||
@@ -1432,13 +1449,8 @@ func (o *consumer) sendDeleteAdvisoryLocked() {
|
|||||||
Domain: o.srv.getOpts().JetStreamDomain,
|
Domain: o.srv.getOpts().JetStreamDomain,
|
||||||
}
|
}
|
||||||
|
|
||||||
j, err := json.Marshal(e)
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
subj := JSAdvisoryConsumerDeletedPre + "." + o.stream + "." + o.name
|
subj := JSAdvisoryConsumerDeletedPre + "." + o.stream + "." + o.name
|
||||||
o.sendAdvisory(subj, j)
|
o.sendAdvisory(subj, e)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (o *consumer) sendCreateAdvisory() {
|
func (o *consumer) sendCreateAdvisory() {
|
||||||
@@ -1457,13 +1469,8 @@ func (o *consumer) sendCreateAdvisory() {
|
|||||||
Domain: o.srv.getOpts().JetStreamDomain,
|
Domain: o.srv.getOpts().JetStreamDomain,
|
||||||
}
|
}
|
||||||
|
|
||||||
j, err := json.Marshal(e)
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
subj := JSAdvisoryConsumerCreatedPre + "." + o.stream + "." + o.name
|
subj := JSAdvisoryConsumerCreatedPre + "." + o.stream + "." + o.name
|
||||||
o.sendAdvisory(subj, j)
|
o.sendAdvisory(subj, e)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Created returns created time.
|
// Created returns created time.
|
||||||
@@ -1573,6 +1580,8 @@ var (
|
|||||||
consumerNotActiveMaxInterval = defaultConsumerNotActiveMaxInterval
|
consumerNotActiveMaxInterval = defaultConsumerNotActiveMaxInterval
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// deleteNotActive must only be called from time.AfterFunc or in its own
|
||||||
|
// goroutine, as it can block on clean-up.
|
||||||
func (o *consumer) deleteNotActive() {
|
func (o *consumer) deleteNotActive() {
|
||||||
o.mu.Lock()
|
o.mu.Lock()
|
||||||
if o.mset == nil {
|
if o.mset == nil {
|
||||||
@@ -1613,8 +1622,25 @@ func (o *consumer) deleteNotActive() {
|
|||||||
|
|
||||||
s, js := o.mset.srv, o.srv.js.Load()
|
s, js := o.mset.srv, o.srv.js.Load()
|
||||||
acc, stream, name, isDirect := o.acc.Name, o.stream, o.name, o.cfg.Direct
|
acc, stream, name, isDirect := o.acc.Name, o.stream, o.name, o.cfg.Direct
|
||||||
|
var qch, cqch chan struct{}
|
||||||
|
if o.srv != nil {
|
||||||
|
qch = o.srv.quitCh
|
||||||
|
}
|
||||||
|
if o.js != nil {
|
||||||
|
cqch = o.js.clusterQuitC()
|
||||||
|
}
|
||||||
o.mu.Unlock()
|
o.mu.Unlock()
|
||||||
|
|
||||||
|
// Useful for pprof.
|
||||||
|
setGoRoutineLabels(pprofLabels{
|
||||||
|
"account": acc,
|
||||||
|
"stream": stream,
|
||||||
|
"consumer": name,
|
||||||
|
})
|
||||||
|
|
||||||
|
// We will delete locally regardless.
|
||||||
|
defer o.delete()
|
||||||
|
|
||||||
// If we are clustered, check if we still have this consumer assigned.
|
// If we are clustered, check if we still have this consumer assigned.
|
||||||
// If we do forward a proposal to delete ourselves to the metacontroller leader.
|
// If we do forward a proposal to delete ourselves to the metacontroller leader.
|
||||||
if !isDirect && s.JetStreamIsClustered() {
|
if !isDirect && s.JetStreamIsClustered() {
|
||||||
@@ -1637,38 +1663,40 @@ func (o *consumer) deleteNotActive() {
|
|||||||
if ca != nil && cc != nil {
|
if ca != nil && cc != nil {
|
||||||
// Check to make sure we went away.
|
// Check to make sure we went away.
|
||||||
// Don't think this needs to be a monitored go routine.
|
// Don't think this needs to be a monitored go routine.
|
||||||
go func() {
|
jitter := time.Duration(rand.Int63n(int64(consumerNotActiveStartInterval)))
|
||||||
jitter := time.Duration(rand.Int63n(int64(consumerNotActiveStartInterval)))
|
interval := consumerNotActiveStartInterval + jitter
|
||||||
interval := consumerNotActiveStartInterval + jitter
|
ticker := time.NewTicker(interval)
|
||||||
ticker := time.NewTicker(interval)
|
defer ticker.Stop()
|
||||||
defer ticker.Stop()
|
for {
|
||||||
for range ticker.C {
|
select {
|
||||||
js.mu.RLock()
|
case <-ticker.C:
|
||||||
if js.shuttingDown {
|
case <-qch:
|
||||||
js.mu.RUnlock()
|
return
|
||||||
return
|
case <-cqch:
|
||||||
}
|
|
||||||
nca := js.consumerAssignment(acc, stream, name)
|
|
||||||
js.mu.RUnlock()
|
|
||||||
// Make sure this is not a new consumer with the same name.
|
|
||||||
if nca != nil && nca == ca {
|
|
||||||
s.Warnf("Consumer assignment for '%s > %s > %s' not cleaned up, retrying", acc, stream, name)
|
|
||||||
meta.ForwardProposal(removeEntry)
|
|
||||||
if interval < consumerNotActiveMaxInterval {
|
|
||||||
interval *= 2
|
|
||||||
ticker.Reset(interval)
|
|
||||||
}
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
// We saw that consumer has been removed, all done.
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
}()
|
js.mu.RLock()
|
||||||
|
if js.shuttingDown {
|
||||||
|
js.mu.RUnlock()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
nca := js.consumerAssignment(acc, stream, name)
|
||||||
|
js.mu.RUnlock()
|
||||||
|
// Make sure this is not a new consumer with the same name.
|
||||||
|
if nca != nil && nca == ca {
|
||||||
|
s.Warnf("Consumer assignment for '%s > %s > %s' not cleaned up, retrying", acc, stream, name)
|
||||||
|
meta.ForwardProposal(removeEntry)
|
||||||
|
if interval < consumerNotActiveMaxInterval {
|
||||||
|
interval *= 2
|
||||||
|
ticker.Reset(interval)
|
||||||
|
}
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
// We saw that consumer has been removed, all done.
|
||||||
|
return
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// We will delete here regardless.
|
|
||||||
o.delete()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (o *consumer) watchGWinterest() {
|
func (o *consumer) watchGWinterest() {
|
||||||
@@ -2382,12 +2410,7 @@ func (o *consumer) processNak(sseq, dseq, dc uint64, nak []byte) {
|
|||||||
Domain: o.srv.getOpts().JetStreamDomain,
|
Domain: o.srv.getOpts().JetStreamDomain,
|
||||||
}
|
}
|
||||||
|
|
||||||
j, err := json.Marshal(e)
|
o.sendAdvisory(o.nakEventT, e)
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
o.sendAdvisory(o.nakEventT, j)
|
|
||||||
|
|
||||||
// Check to see if we have delays attached.
|
// Check to see if we have delays attached.
|
||||||
if len(nak) > len(AckNak) {
|
if len(nak) > len(AckNak) {
|
||||||
@@ -2462,15 +2485,8 @@ func (o *consumer) processTerm(sseq, dseq, dc uint64, reason, reply string) bool
|
|||||||
Domain: o.srv.getOpts().JetStreamDomain,
|
Domain: o.srv.getOpts().JetStreamDomain,
|
||||||
}
|
}
|
||||||
|
|
||||||
j, err := json.Marshal(e)
|
|
||||||
if err != nil {
|
|
||||||
// We had an error during the marshal, so we can't send the advisory,
|
|
||||||
// but we still need to tell the caller that the ack was processed.
|
|
||||||
return ackedInPlace
|
|
||||||
}
|
|
||||||
|
|
||||||
subj := JSAdvisoryConsumerMsgTerminatedPre + "." + o.stream + "." + o.name
|
subj := JSAdvisoryConsumerMsgTerminatedPre + "." + o.stream + "." + o.name
|
||||||
o.sendAdvisory(subj, j)
|
o.sendAdvisory(subj, e)
|
||||||
return ackedInPlace
|
return ackedInPlace
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -2765,12 +2781,7 @@ func (o *consumer) sampleAck(sseq, dseq, dc uint64) {
|
|||||||
Domain: o.srv.getOpts().JetStreamDomain,
|
Domain: o.srv.getOpts().JetStreamDomain,
|
||||||
}
|
}
|
||||||
|
|
||||||
j, err := json.Marshal(e)
|
o.sendAdvisory(o.ackEventT, e)
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
o.sendAdvisory(o.ackEventT, j)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Process an ACK.
|
// Process an ACK.
|
||||||
@@ -2851,7 +2862,8 @@ func (o *consumer) processAckMsg(sseq, dseq, dc uint64, reply string, doSample b
|
|||||||
// no-op
|
// no-op
|
||||||
if dseq <= o.adflr || sseq <= o.asflr {
|
if dseq <= o.adflr || sseq <= o.asflr {
|
||||||
o.mu.Unlock()
|
o.mu.Unlock()
|
||||||
return ackInPlace
|
// Return true to let caller respond back to the client.
|
||||||
|
return true
|
||||||
}
|
}
|
||||||
if o.maxp > 0 && len(o.pending) >= o.maxp {
|
if o.maxp > 0 && len(o.pending) >= o.maxp {
|
||||||
needSignal = true
|
needSignal = true
|
||||||
@@ -3515,12 +3527,7 @@ func (o *consumer) notifyDeliveryExceeded(sseq, dc uint64) {
|
|||||||
Domain: o.srv.getOpts().JetStreamDomain,
|
Domain: o.srv.getOpts().JetStreamDomain,
|
||||||
}
|
}
|
||||||
|
|
||||||
j, err := json.Marshal(e)
|
o.sendAdvisory(o.deliveryExcEventT, e)
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
o.sendAdvisory(o.deliveryExcEventT, j)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check if the candidate subject matches a filter if its present.
|
// Check if the candidate subject matches a filter if its present.
|
||||||
@@ -3596,17 +3603,23 @@ func (o *consumer) getNextMsg() (*jsPubMsg, uint64, error) {
|
|||||||
}
|
}
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
if seq > 0 {
|
pmsg := getJSPubMsgFromPool()
|
||||||
pmsg := getJSPubMsgFromPool()
|
sm, err := o.mset.store.LoadMsg(seq, &pmsg.StoreMsg)
|
||||||
sm, err := o.mset.store.LoadMsg(seq, &pmsg.StoreMsg)
|
if sm == nil || err != nil {
|
||||||
if sm == nil || err != nil {
|
pmsg.returnToPool()
|
||||||
pmsg.returnToPool()
|
pmsg, dc = nil, 0
|
||||||
pmsg, dc = nil, 0
|
// Adjust back deliver count.
|
||||||
// Adjust back deliver count.
|
o.decDeliveryCount(seq)
|
||||||
o.decDeliveryCount(seq)
|
|
||||||
}
|
|
||||||
return pmsg, dc, err
|
|
||||||
}
|
}
|
||||||
|
// Message was scheduled for redelivery but was removed in the meantime.
|
||||||
|
if err == ErrStoreMsgNotFound || err == errDeletedMsg {
|
||||||
|
// This is a race condition where the message is still in o.pending and
|
||||||
|
// scheduled for redelivery, but it has been removed from the stream.
|
||||||
|
// o.processTerm is called in a goroutine so could run after we get here.
|
||||||
|
// That will correct the pending state and delivery/ack floors, so just skip here.
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
return pmsg, dc, err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -5379,6 +5392,7 @@ func (o *consumer) requestNextMsgSubject() string {
|
|||||||
|
|
||||||
func (o *consumer) decStreamPending(sseq uint64, subj string) {
|
func (o *consumer) decStreamPending(sseq uint64, subj string) {
|
||||||
o.mu.Lock()
|
o.mu.Lock()
|
||||||
|
|
||||||
// Update our cached num pending only if we think deliverMsg has not done so.
|
// Update our cached num pending only if we think deliverMsg has not done so.
|
||||||
if sseq >= o.sseq && o.isFilteredMatch(subj) {
|
if sseq >= o.sseq && o.isFilteredMatch(subj) {
|
||||||
o.npc--
|
o.npc--
|
||||||
@@ -5390,6 +5404,7 @@ func (o *consumer) decStreamPending(sseq uint64, subj string) {
|
|||||||
if o.rdc != nil {
|
if o.rdc != nil {
|
||||||
rdc = o.rdc[sseq]
|
rdc = o.rdc[sseq]
|
||||||
}
|
}
|
||||||
|
|
||||||
o.mu.Unlock()
|
o.mu.Unlock()
|
||||||
|
|
||||||
// If it was pending process it like an ack.
|
// If it was pending process it like an ack.
|
||||||
|
|||||||
+22
@@ -324,6 +324,28 @@ func (ci *ClientInfo) forAssignmentSnap() *ClientInfo {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// forProposal returns the minimum amount of ClientInfo we need for assignment proposals.
|
||||||
|
func (ci *ClientInfo) forProposal() *ClientInfo {
|
||||||
|
if ci == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
cci := *ci
|
||||||
|
cci.Jwt = _EMPTY_
|
||||||
|
cci.IssuerKey = _EMPTY_
|
||||||
|
return &cci
|
||||||
|
}
|
||||||
|
|
||||||
|
// forAdvisory returns the minimum amount of ClientInfo we need for JS advisory events.
|
||||||
|
func (ci *ClientInfo) forAdvisory() *ClientInfo {
|
||||||
|
if ci == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
cci := *ci
|
||||||
|
cci.Jwt = _EMPTY_
|
||||||
|
cci.Alternates = nil
|
||||||
|
return &cci
|
||||||
|
}
|
||||||
|
|
||||||
// ServerStats hold various statistics that we will periodically send out.
|
// ServerStats hold various statistics that we will periodically send out.
|
||||||
type ServerStats struct {
|
type ServerStats struct {
|
||||||
Start time.Time `json:"start"`
|
Start time.Time `json:"start"`
|
||||||
|
|||||||
+159
-80
@@ -495,7 +495,10 @@ func newFileStoreWithCreated(fcfg FileStoreConfig, cfg StreamConfig, created tim
|
|||||||
|
|
||||||
// Do age checks too, make sure to call in place.
|
// Do age checks too, make sure to call in place.
|
||||||
if fs.cfg.MaxAge != 0 {
|
if fs.cfg.MaxAge != 0 {
|
||||||
fs.expireMsgsOnRecover()
|
err := fs.expireMsgsOnRecover()
|
||||||
|
if isPermissionError(err) {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
fs.startAgeChk()
|
fs.startAgeChk()
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1376,14 +1379,14 @@ func (mb *msgBlock) rebuildStateLocked() (*LostStreamData, []uint64, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
hdr := buf[index : index+msgHdrSize]
|
hdr := buf[index : index+msgHdrSize]
|
||||||
rl, slen := le.Uint32(hdr[0:]), le.Uint16(hdr[20:])
|
rl, slen := le.Uint32(hdr[0:]), int(le.Uint16(hdr[20:]))
|
||||||
|
|
||||||
hasHeaders := rl&hbit != 0
|
hasHeaders := rl&hbit != 0
|
||||||
// Clear any headers bit that could be set.
|
// Clear any headers bit that could be set.
|
||||||
rl &^= hbit
|
rl &^= hbit
|
||||||
dlen := int(rl) - msgHdrSize
|
dlen := int(rl) - msgHdrSize
|
||||||
// Do some quick sanity checks here.
|
// Do some quick sanity checks here.
|
||||||
if dlen < 0 || int(slen) > (dlen-recordHashSize) || dlen > int(rl) || index+rl > lbuf || rl > rlBadThresh {
|
if dlen < 0 || slen > (dlen-recordHashSize) || dlen > int(rl) || index+rl > lbuf || rl > rlBadThresh {
|
||||||
truncate(index)
|
truncate(index)
|
||||||
return gatherLost(lbuf - index), tombstones, errBadMsg
|
return gatherLost(lbuf - index), tombstones, errBadMsg
|
||||||
}
|
}
|
||||||
@@ -1978,9 +1981,9 @@ func (fs *fileStore) recoverMsgs() error {
|
|||||||
// We will treat this differently in case we have a recovery
|
// We will treat this differently in case we have a recovery
|
||||||
// that will expire alot of messages on startup.
|
// that will expire alot of messages on startup.
|
||||||
// Should only be called on startup.
|
// Should only be called on startup.
|
||||||
func (fs *fileStore) expireMsgsOnRecover() {
|
func (fs *fileStore) expireMsgsOnRecover() error {
|
||||||
if fs.state.Msgs == 0 {
|
if fs.state.Msgs == 0 {
|
||||||
return
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
var minAge = time.Now().UnixNano() - int64(fs.cfg.MaxAge)
|
var minAge = time.Now().UnixNano() - int64(fs.cfg.MaxAge)
|
||||||
@@ -1992,7 +1995,7 @@ func (fs *fileStore) expireMsgsOnRecover() {
|
|||||||
// usually taken care of by fs.removeMsgBlock() but we do not call that here.
|
// usually taken care of by fs.removeMsgBlock() but we do not call that here.
|
||||||
var last msgId
|
var last msgId
|
||||||
|
|
||||||
deleteEmptyBlock := func(mb *msgBlock) {
|
deleteEmptyBlock := func(mb *msgBlock) error {
|
||||||
// If we are the last keep state to remember first/last sequence.
|
// If we are the last keep state to remember first/last sequence.
|
||||||
// Do this part by hand since not deleting one by one.
|
// Do this part by hand since not deleting one by one.
|
||||||
if mb == fs.lmb {
|
if mb == fs.lmb {
|
||||||
@@ -2008,8 +2011,12 @@ func (fs *fileStore) expireMsgsOnRecover() {
|
|||||||
}
|
}
|
||||||
return true
|
return true
|
||||||
})
|
})
|
||||||
mb.dirtyCloseWithRemove(true)
|
err := mb.dirtyCloseWithRemove(true)
|
||||||
|
if isPermissionError(err) {
|
||||||
|
return err
|
||||||
|
}
|
||||||
deleted++
|
deleted++
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, mb := range fs.blks {
|
for _, mb := range fs.blks {
|
||||||
@@ -2023,8 +2030,11 @@ func (fs *fileStore) expireMsgsOnRecover() {
|
|||||||
if mb.last.ts <= minAge {
|
if mb.last.ts <= minAge {
|
||||||
purged += mb.msgs
|
purged += mb.msgs
|
||||||
bytes += mb.bytes
|
bytes += mb.bytes
|
||||||
deleteEmptyBlock(mb)
|
err := deleteEmptyBlock(mb)
|
||||||
mb.mu.Unlock()
|
mb.mu.Unlock()
|
||||||
|
if isPermissionError(err) {
|
||||||
|
return err
|
||||||
|
}
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -2148,6 +2158,7 @@ func (fs *fileStore) expireMsgsOnRecover() {
|
|||||||
if purged > 0 {
|
if purged > 0 {
|
||||||
fs.dirty++
|
fs.dirty++
|
||||||
}
|
}
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func copyMsgBlocks(src []*msgBlock) []*msgBlock {
|
func copyMsgBlocks(src []*msgBlock) []*msgBlock {
|
||||||
@@ -2315,8 +2326,8 @@ func (mb *msgBlock) firstMatching(filter string, wc bool, start uint64, sm *Stor
|
|||||||
fseq = lseq + 1
|
fseq = lseq + 1
|
||||||
for _, subj := range subs {
|
for _, subj := range subs {
|
||||||
ss, _ := mb.fss.Find(stringToBytes(subj))
|
ss, _ := mb.fss.Find(stringToBytes(subj))
|
||||||
if ss != nil && ss.firstNeedsUpdate {
|
if ss != nil && (ss.firstNeedsUpdate || ss.lastNeedsUpdate) {
|
||||||
mb.recalculateFirstForSubj(subj, ss.First, ss)
|
mb.recalculateForSubj(subj, ss)
|
||||||
}
|
}
|
||||||
if ss == nil || start > ss.Last || ss.First >= fseq {
|
if ss == nil || start > ss.Last || ss.First >= fseq {
|
||||||
continue
|
continue
|
||||||
@@ -2445,8 +2456,8 @@ func (mb *msgBlock) filteredPendingLocked(filter string, wc bool, sseq uint64) (
|
|||||||
// If we already found a partial then don't do anything else.
|
// If we already found a partial then don't do anything else.
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if ss.firstNeedsUpdate {
|
if ss.firstNeedsUpdate || ss.lastNeedsUpdate {
|
||||||
mb.recalculateFirstForSubj(bytesToString(bsubj), ss.First, ss)
|
mb.recalculateForSubj(bytesToString(bsubj), ss)
|
||||||
}
|
}
|
||||||
if sseq <= ss.First {
|
if sseq <= ss.First {
|
||||||
update(ss)
|
update(ss)
|
||||||
@@ -2616,10 +2627,6 @@ func (fs *fileStore) numFilteredPendingWithLast(filter string, last bool, ss *Si
|
|||||||
// Always reset.
|
// Always reset.
|
||||||
ss.First, ss.Last, ss.Msgs = 0, 0, 0
|
ss.First, ss.Last, ss.Msgs = 0, 0, 0
|
||||||
|
|
||||||
if filter == _EMPTY_ {
|
|
||||||
filter = fwcs
|
|
||||||
}
|
|
||||||
|
|
||||||
// We do need to figure out the first and last sequences.
|
// We do need to figure out the first and last sequences.
|
||||||
wc := subjectHasWildcard(filter)
|
wc := subjectHasWildcard(filter)
|
||||||
start, stop := uint32(math.MaxUint32), uint32(0)
|
start, stop := uint32(math.MaxUint32), uint32(0)
|
||||||
@@ -2749,8 +2756,8 @@ func (fs *fileStore) SubjectsState(subject string) map[string]SimpleState {
|
|||||||
mb.lsts = time.Now().UnixNano()
|
mb.lsts = time.Now().UnixNano()
|
||||||
mb.fss.Match(stringToBytes(subject), func(bsubj []byte, ss *SimpleState) {
|
mb.fss.Match(stringToBytes(subject), func(bsubj []byte, ss *SimpleState) {
|
||||||
subj := string(bsubj)
|
subj := string(bsubj)
|
||||||
if ss.firstNeedsUpdate {
|
if ss.firstNeedsUpdate || ss.lastNeedsUpdate {
|
||||||
mb.recalculateFirstForSubj(subj, ss.First, ss)
|
mb.recalculateForSubj(subj, ss)
|
||||||
}
|
}
|
||||||
oss := fss[subj]
|
oss := fss[subj]
|
||||||
if oss.First == 0 { // New
|
if oss.First == 0 { // New
|
||||||
@@ -2940,8 +2947,8 @@ func (fs *fileStore) NumPending(sseq uint64, filter string, lastPerSubject bool)
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
subj := bytesToString(bsubj)
|
subj := bytesToString(bsubj)
|
||||||
if ss.firstNeedsUpdate {
|
if ss.firstNeedsUpdate || ss.lastNeedsUpdate {
|
||||||
mb.recalculateFirstForSubj(subj, ss.First, ss)
|
mb.recalculateForSubj(subj, ss)
|
||||||
}
|
}
|
||||||
if sseq <= ss.First {
|
if sseq <= ss.First {
|
||||||
t += ss.Msgs
|
t += ss.Msgs
|
||||||
@@ -3228,8 +3235,8 @@ func (fs *fileStore) NumPendingMulti(sseq uint64, sl *Sublist, lastPerSubject bo
|
|||||||
// If we already found a partial then don't do anything else.
|
// If we already found a partial then don't do anything else.
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if ss.firstNeedsUpdate {
|
if ss.firstNeedsUpdate || ss.lastNeedsUpdate {
|
||||||
mb.recalculateFirstForSubj(subj, ss.First, ss)
|
mb.recalculateForSubj(subj, ss)
|
||||||
}
|
}
|
||||||
if sseq <= ss.First {
|
if sseq <= ss.First {
|
||||||
t += ss.Msgs
|
t += ss.Msgs
|
||||||
@@ -3467,6 +3474,9 @@ func (fs *fileStore) newMsgBlockForWrite() (*msgBlock, error) {
|
|||||||
dios <- struct{}{}
|
dios <- struct{}{}
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
if isPermissionError(err) {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
mb.dirtyCloseWithRemove(true)
|
mb.dirtyCloseWithRemove(true)
|
||||||
return nil, fmt.Errorf("Error creating msg block file: %v", err)
|
return nil, fmt.Errorf("Error creating msg block file: %v", err)
|
||||||
}
|
}
|
||||||
@@ -3902,8 +3912,8 @@ func (fs *fileStore) firstSeqForSubj(subj string) (uint64, error) {
|
|||||||
info.fblk = i
|
info.fblk = i
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if ss.firstNeedsUpdate {
|
if ss.firstNeedsUpdate || ss.lastNeedsUpdate {
|
||||||
mb.recalculateFirstForSubj(subj, ss.First, ss)
|
mb.recalculateForSubj(subj, ss)
|
||||||
}
|
}
|
||||||
mb.mu.Unlock()
|
mb.mu.Unlock()
|
||||||
// Re-acquire fs lock
|
// Re-acquire fs lock
|
||||||
@@ -4034,8 +4044,8 @@ func (fs *fileStore) enforceMsgPerSubjectLimit(fireCallback bool) {
|
|||||||
mb.mu.Lock()
|
mb.mu.Lock()
|
||||||
mb.ensurePerSubjectInfoLoaded()
|
mb.ensurePerSubjectInfoLoaded()
|
||||||
ss, ok := mb.fss.Find(stringToBytes(subj))
|
ss, ok := mb.fss.Find(stringToBytes(subj))
|
||||||
if ok && ss != nil && ss.firstNeedsUpdate {
|
if ok && ss != nil && (ss.firstNeedsUpdate || ss.lastNeedsUpdate) {
|
||||||
mb.recalculateFirstForSubj(subj, ss.First, ss)
|
mb.recalculateForSubj(subj, ss)
|
||||||
}
|
}
|
||||||
mb.mu.Unlock()
|
mb.mu.Unlock()
|
||||||
if ss == nil {
|
if ss == nil {
|
||||||
@@ -4364,12 +4374,12 @@ func (mb *msgBlock) compactWithFloor(floor uint64) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
hdr := buf[index : index+msgHdrSize]
|
hdr := buf[index : index+msgHdrSize]
|
||||||
rl, slen := le.Uint32(hdr[0:]), le.Uint16(hdr[20:])
|
rl, slen := le.Uint32(hdr[0:]), int(le.Uint16(hdr[20:]))
|
||||||
// Clear any headers bit that could be set.
|
// Clear any headers bit that could be set.
|
||||||
rl &^= hbit
|
rl &^= hbit
|
||||||
dlen := int(rl) - msgHdrSize
|
dlen := int(rl) - msgHdrSize
|
||||||
// Do some quick sanity checks here.
|
// Do some quick sanity checks here.
|
||||||
if dlen < 0 || int(slen) > dlen || dlen > int(rl) || rl > rlBadThresh || index+rl > lbuf {
|
if dlen < 0 || slen > (dlen-recordHashSize) || dlen > int(rl) || index+rl > lbuf || rl > rlBadThresh {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
// Only need to process non-deleted messages.
|
// Only need to process non-deleted messages.
|
||||||
@@ -5473,16 +5483,23 @@ func (mb *msgBlock) recompressOnDiskIfNeeded() error {
|
|||||||
<-dios
|
<-dios
|
||||||
tmpFD, err := os.OpenFile(tmpFN, os.O_CREATE|os.O_TRUNC|os.O_WRONLY, defaultFilePerms)
|
tmpFD, err := os.OpenFile(tmpFN, os.O_CREATE|os.O_TRUNC|os.O_WRONLY, defaultFilePerms)
|
||||||
dios <- struct{}{}
|
dios <- struct{}{}
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("failed to create temporary file: %w", err)
|
return fmt.Errorf("failed to create temporary file: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
errorCleanup := func(err error) error {
|
||||||
|
tmpFD.Close()
|
||||||
|
os.Remove(tmpFN)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
// The original buffer at this point is uncompressed, so we will now compress
|
// The original buffer at this point is uncompressed, so we will now compress
|
||||||
// it if needed. Note that if the selected algorithm is NoCompression, the
|
// it if needed. Note that if the selected algorithm is NoCompression, the
|
||||||
// Compress function will just return the input buffer unmodified.
|
// Compress function will just return the input buffer unmodified.
|
||||||
cmpBuf, err := alg.Compress(origBuf)
|
cmpBuf, err := alg.Compress(origBuf)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("failed to compress block: %w", err)
|
return errorCleanup(fmt.Errorf("failed to compress block: %w", err))
|
||||||
}
|
}
|
||||||
|
|
||||||
// We only need to write out the metadata header if compression is enabled.
|
// We only need to write out the metadata header if compression is enabled.
|
||||||
@@ -5500,7 +5517,7 @@ func (mb *msgBlock) recompressOnDiskIfNeeded() error {
|
|||||||
if mb.bek != nil && len(cmpBuf) > 0 {
|
if mb.bek != nil && len(cmpBuf) > 0 {
|
||||||
bek, err := genBlockEncryptionKey(mb.fs.fcfg.Cipher, mb.seed, mb.nonce)
|
bek, err := genBlockEncryptionKey(mb.fs.fcfg.Cipher, mb.seed, mb.nonce)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return errorCleanup(err)
|
||||||
}
|
}
|
||||||
mb.bek = bek
|
mb.bek = bek
|
||||||
mb.bek.XORKeyStream(cmpBuf, cmpBuf)
|
mb.bek.XORKeyStream(cmpBuf, cmpBuf)
|
||||||
@@ -5508,11 +5525,6 @@ func (mb *msgBlock) recompressOnDiskIfNeeded() error {
|
|||||||
|
|
||||||
// Write the new block data (which might be compressed or encrypted) to the
|
// Write the new block data (which might be compressed or encrypted) to the
|
||||||
// temporary file.
|
// temporary file.
|
||||||
errorCleanup := func(err error) error {
|
|
||||||
tmpFD.Close()
|
|
||||||
os.Remove(tmpFN)
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if n, err := tmpFD.Write(cmpBuf); err != nil {
|
if n, err := tmpFD.Write(cmpBuf); err != nil {
|
||||||
return errorCleanup(fmt.Errorf("failed to write to temporary file: %w", err))
|
return errorCleanup(fmt.Errorf("failed to write to temporary file: %w", err))
|
||||||
} else if n != len(cmpBuf) {
|
} else if n != len(cmpBuf) {
|
||||||
@@ -6488,7 +6500,7 @@ func (mb *msgBlock) msgFromBuf(buf []byte, sm *StoreMsg, hh hash.Hash64) (*Store
|
|||||||
dlen := int(rl) - msgHdrSize
|
dlen := int(rl) - msgHdrSize
|
||||||
slen := int(le.Uint16(hdr[20:]))
|
slen := int(le.Uint16(hdr[20:]))
|
||||||
// Simple sanity check.
|
// Simple sanity check.
|
||||||
if dlen < 0 || slen > (dlen-recordHashSize) || dlen > int(rl) || int(rl) > len(buf) {
|
if dlen < 0 || slen > (dlen-recordHashSize) || dlen > int(rl) || int(rl) > len(buf) || rl > rlBadThresh {
|
||||||
return nil, errBadMsg
|
return nil, errBadMsg
|
||||||
}
|
}
|
||||||
data := buf[msgHdrSize : msgHdrSize+dlen]
|
data := buf[msgHdrSize : msgHdrSize+dlen]
|
||||||
@@ -7783,9 +7795,9 @@ func (mb *msgBlock) dirtyClose() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Should be called with lock held.
|
// Should be called with lock held.
|
||||||
func (mb *msgBlock) dirtyCloseWithRemove(remove bool) {
|
func (mb *msgBlock) dirtyCloseWithRemove(remove bool) error {
|
||||||
if mb == nil {
|
if mb == nil {
|
||||||
return
|
return nil
|
||||||
}
|
}
|
||||||
// Stop cache expiration timer.
|
// Stop cache expiration timer.
|
||||||
if mb.ctmr != nil {
|
if mb.ctmr != nil {
|
||||||
@@ -7807,13 +7819,20 @@ func (mb *msgBlock) dirtyCloseWithRemove(remove bool) {
|
|||||||
// Clear any tracking by subject if we are removing.
|
// Clear any tracking by subject if we are removing.
|
||||||
mb.fss = nil
|
mb.fss = nil
|
||||||
if mb.mfn != _EMPTY_ {
|
if mb.mfn != _EMPTY_ {
|
||||||
os.Remove(mb.mfn)
|
err := os.Remove(mb.mfn)
|
||||||
|
if isPermissionError(err) {
|
||||||
|
return err
|
||||||
|
}
|
||||||
mb.mfn = _EMPTY_
|
mb.mfn = _EMPTY_
|
||||||
}
|
}
|
||||||
if mb.kfn != _EMPTY_ {
|
if mb.kfn != _EMPTY_ {
|
||||||
os.Remove(mb.kfn)
|
err := os.Remove(mb.kfn)
|
||||||
|
if isPermissionError(err) {
|
||||||
|
return err
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Remove a seq from the fss and select new first.
|
// Remove a seq from the fss and select new first.
|
||||||
@@ -7836,24 +7855,14 @@ func (mb *msgBlock) removeSeqPerSubject(subj string, seq uint64) {
|
|||||||
|
|
||||||
ss.Msgs--
|
ss.Msgs--
|
||||||
|
|
||||||
// Only one left.
|
// We can lazily calculate the first/last sequence when needed.
|
||||||
if ss.Msgs == 1 {
|
|
||||||
if seq == ss.Last {
|
|
||||||
ss.Last = ss.First
|
|
||||||
} else {
|
|
||||||
ss.First = ss.Last
|
|
||||||
}
|
|
||||||
ss.firstNeedsUpdate = false
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// We can lazily calculate the first sequence when needed.
|
|
||||||
ss.firstNeedsUpdate = seq == ss.First || ss.firstNeedsUpdate
|
ss.firstNeedsUpdate = seq == ss.First || ss.firstNeedsUpdate
|
||||||
|
ss.lastNeedsUpdate = seq == ss.Last || ss.lastNeedsUpdate
|
||||||
}
|
}
|
||||||
|
|
||||||
// Will recalulate the first sequence for this subject in this block.
|
// Will recalculate the first and/or last sequence for this subject in this block.
|
||||||
// Will avoid slower path message lookups and scan the cache directly instead.
|
// Will avoid slower path message lookups and scan the cache directly instead.
|
||||||
func (mb *msgBlock) recalculateFirstForSubj(subj string, startSeq uint64, ss *SimpleState) {
|
func (mb *msgBlock) recalculateForSubj(subj string, ss *SimpleState) {
|
||||||
// Need to make sure messages are loaded.
|
// Need to make sure messages are loaded.
|
||||||
if mb.cacheNotLoaded() {
|
if mb.cacheNotLoaded() {
|
||||||
if err := mb.loadMsgsWithLock(); err != nil {
|
if err := mb.loadMsgsWithLock(); err != nil {
|
||||||
@@ -7861,42 +7870,100 @@ func (mb *msgBlock) recalculateFirstForSubj(subj string, startSeq uint64, ss *Si
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Mark first as updated.
|
startSlot := int(ss.First - mb.cache.fseq)
|
||||||
ss.firstNeedsUpdate = false
|
if startSlot < 0 {
|
||||||
|
startSlot = 0
|
||||||
startSlot := int(startSeq - mb.cache.fseq)
|
}
|
||||||
if startSlot >= len(mb.cache.idx) {
|
if startSlot >= len(mb.cache.idx) {
|
||||||
ss.First = ss.Last
|
ss.First = ss.Last
|
||||||
return
|
return
|
||||||
} else if startSlot < 0 {
|
}
|
||||||
startSlot = 0
|
endSlot := int(ss.Last - mb.cache.fseq)
|
||||||
|
if endSlot < 0 {
|
||||||
|
endSlot = 0
|
||||||
|
}
|
||||||
|
if endSlot >= len(mb.cache.idx) || startSlot > endSlot {
|
||||||
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
var le = binary.LittleEndian
|
var le = binary.LittleEndian
|
||||||
for slot, fseq := startSlot, atomic.LoadUint64(&mb.first.seq); slot < len(mb.cache.idx); slot++ {
|
if ss.firstNeedsUpdate {
|
||||||
bi := mb.cache.idx[slot] &^ hbit
|
// Mark first as updated.
|
||||||
if bi == dbit {
|
ss.firstNeedsUpdate = false
|
||||||
// delete marker so skip.
|
|
||||||
continue
|
fseq := ss.First + 1
|
||||||
|
if mbFseq := atomic.LoadUint64(&mb.first.seq); fseq < mbFseq {
|
||||||
|
fseq = mbFseq
|
||||||
}
|
}
|
||||||
li := int(bi) - mb.cache.off
|
for slot := startSlot; slot < len(mb.cache.idx); slot++ {
|
||||||
if li >= len(mb.cache.buf) {
|
bi := mb.cache.idx[slot] &^ hbit
|
||||||
ss.First = ss.Last
|
if bi == dbit {
|
||||||
return
|
// delete marker so skip.
|
||||||
}
|
|
||||||
buf := mb.cache.buf[li:]
|
|
||||||
hdr := buf[:msgHdrSize]
|
|
||||||
slen := int(le.Uint16(hdr[20:]))
|
|
||||||
if subj == bytesToString(buf[msgHdrSize:msgHdrSize+slen]) {
|
|
||||||
seq := le.Uint64(hdr[4:])
|
|
||||||
if seq < fseq || seq&ebit != 0 || mb.dmap.Exists(seq) {
|
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
ss.First = seq
|
li := int(bi) - mb.cache.off
|
||||||
if ss.Msgs == 1 {
|
if li >= len(mb.cache.buf) {
|
||||||
|
ss.First = ss.Last
|
||||||
|
return
|
||||||
|
}
|
||||||
|
buf := mb.cache.buf[li:]
|
||||||
|
hdr := buf[:msgHdrSize]
|
||||||
|
slen := int(le.Uint16(hdr[20:]))
|
||||||
|
if subj == bytesToString(buf[msgHdrSize:msgHdrSize+slen]) {
|
||||||
|
seq := le.Uint64(hdr[4:])
|
||||||
|
if seq < fseq || seq&ebit != 0 || mb.dmap.Exists(seq) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
ss.First = seq
|
||||||
|
if ss.Msgs == 1 {
|
||||||
|
ss.Last = seq
|
||||||
|
ss.lastNeedsUpdate = false
|
||||||
|
return
|
||||||
|
}
|
||||||
|
// Skip the start slot ahead, if we need to recalculate last we can stop early.
|
||||||
|
startSlot = slot
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if ss.lastNeedsUpdate {
|
||||||
|
// Mark last as updated.
|
||||||
|
ss.lastNeedsUpdate = false
|
||||||
|
|
||||||
|
lseq := ss.Last - 1
|
||||||
|
if mbLseq := atomic.LoadUint64(&mb.last.seq); lseq > mbLseq {
|
||||||
|
lseq = mbLseq
|
||||||
|
}
|
||||||
|
for slot := endSlot; slot >= startSlot; slot-- {
|
||||||
|
bi := mb.cache.idx[slot] &^ hbit
|
||||||
|
if bi == dbit {
|
||||||
|
// delete marker so skip.
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
li := int(bi) - mb.cache.off
|
||||||
|
if li >= len(mb.cache.buf) {
|
||||||
|
// Can't overwrite ss.Last, just skip.
|
||||||
|
return
|
||||||
|
}
|
||||||
|
buf := mb.cache.buf[li:]
|
||||||
|
hdr := buf[:msgHdrSize]
|
||||||
|
slen := int(le.Uint16(hdr[20:]))
|
||||||
|
if subj == bytesToString(buf[msgHdrSize:msgHdrSize+slen]) {
|
||||||
|
seq := le.Uint64(hdr[4:])
|
||||||
|
if seq > lseq || seq&ebit != 0 || mb.dmap.Exists(seq) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
// Sequence should never be lower, but guard against it nonetheless.
|
||||||
|
if seq < ss.First {
|
||||||
|
seq = ss.First
|
||||||
|
}
|
||||||
ss.Last = seq
|
ss.Last = seq
|
||||||
|
if ss.Msgs == 1 {
|
||||||
|
ss.First = seq
|
||||||
|
ss.firstNeedsUpdate = false
|
||||||
|
}
|
||||||
|
return
|
||||||
}
|
}
|
||||||
return
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -8178,7 +8245,15 @@ func (fs *fileStore) flushStreamStateLoop(qch, done chan struct{}) {
|
|||||||
for {
|
for {
|
||||||
select {
|
select {
|
||||||
case <-t.C:
|
case <-t.C:
|
||||||
fs.writeFullState()
|
err := fs.writeFullState()
|
||||||
|
if isPermissionError(err) && fs.srv != nil {
|
||||||
|
fs.warn("File system permission denied when flushing stream state, disabling JetStream: %v", err)
|
||||||
|
// messages in block cache could be lost in the worst case.
|
||||||
|
// In the clustered mode it is very highly unlikely as a result of replication.
|
||||||
|
fs.srv.DisableJetStream()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
case <-qch:
|
case <-qch:
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -8386,7 +8461,11 @@ func (fs *fileStore) _writeFullState(force bool) error {
|
|||||||
// Protect with dios.
|
// Protect with dios.
|
||||||
<-dios
|
<-dios
|
||||||
err := os.WriteFile(fn, buf, defaultFilePerms)
|
err := os.WriteFile(fn, buf, defaultFilePerms)
|
||||||
|
// if file system is not writable isPermissionError is set to true
|
||||||
dios <- struct{}{}
|
dios <- struct{}{}
|
||||||
|
if isPermissionError(err) {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
// Update dirty if successful.
|
// Update dirty if successful.
|
||||||
if err == nil {
|
if err == nil {
|
||||||
|
|||||||
+5
-2
@@ -190,14 +190,16 @@ func (q *ipQueue[T]) len() int {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Empty the queue and consumes the notification signal if present.
|
// Empty the queue and consumes the notification signal if present.
|
||||||
|
// Returns the number of items that were drained from the queue.
|
||||||
// Note that this could cause a reader go routine that has been
|
// Note that this could cause a reader go routine that has been
|
||||||
// notified that there is something in the queue (reading from queue's `ch`)
|
// notified that there is something in the queue (reading from queue's `ch`)
|
||||||
// may then get nothing if `drain()` is invoked before the `pop()` or `popOne()`.
|
// may then get nothing if `drain()` is invoked before the `pop()` or `popOne()`.
|
||||||
func (q *ipQueue[T]) drain() {
|
func (q *ipQueue[T]) drain() int {
|
||||||
if q == nil {
|
if q == nil {
|
||||||
return
|
return 0
|
||||||
}
|
}
|
||||||
q.Lock()
|
q.Lock()
|
||||||
|
olen := len(q.elts)
|
||||||
if q.elts != nil {
|
if q.elts != nil {
|
||||||
q.resetAndReturnToPool(&q.elts)
|
q.resetAndReturnToPool(&q.elts)
|
||||||
q.elts, q.pos = nil, 0
|
q.elts, q.pos = nil, 0
|
||||||
@@ -209,6 +211,7 @@ func (q *ipQueue[T]) drain() {
|
|||||||
default:
|
default:
|
||||||
}
|
}
|
||||||
q.Unlock()
|
q.Unlock()
|
||||||
|
return olen
|
||||||
}
|
}
|
||||||
|
|
||||||
// Since the length of the queue goes to 0 after a pop(), it is good to
|
// Since the length of the queue goes to 0 after a pop(), it is good to
|
||||||
|
|||||||
+11
@@ -2974,3 +2974,14 @@ func fixCfgMirrorWithDedupWindow(cfg *StreamConfig) {
|
|||||||
cfg.Duplicates = 0
|
cfg.Duplicates = 0
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (s *Server) handleWritePermissionError() {
|
||||||
|
//TODO Check if we should add s.jetStreamOOSPending in condition
|
||||||
|
if s.JetStreamEnabled() {
|
||||||
|
s.Errorf("File system permission denied while writing, disabling JetStream")
|
||||||
|
|
||||||
|
go s.DisableJetStream()
|
||||||
|
|
||||||
|
//TODO Send respective advisory if needed, same as in handleOutOfSpace
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
+22
-12
@@ -836,7 +836,8 @@ func (js *jetStream) apiDispatch(sub *subscription, c *client, acc *Account, sub
|
|||||||
limit := atomic.LoadInt64(&js.queueLimit)
|
limit := atomic.LoadInt64(&js.queueLimit)
|
||||||
if pending >= int(limit) {
|
if pending >= int(limit) {
|
||||||
s.rateLimitFormatWarnf("JetStream API queue limit reached, dropping %d requests", pending)
|
s.rateLimitFormatWarnf("JetStream API queue limit reached, dropping %d requests", pending)
|
||||||
s.jsAPIRoutedReqs.drain()
|
drained := int64(s.jsAPIRoutedReqs.drain())
|
||||||
|
atomic.AddInt64(&js.apiInflight, -drained)
|
||||||
|
|
||||||
s.publishAdvisory(nil, JSAdvisoryAPILimitReached, JSAPILimitReachedAdvisory{
|
s.publishAdvisory(nil, JSAdvisoryAPILimitReached, JSAPILimitReachedAdvisory{
|
||||||
TypedEvent: TypedEvent{
|
TypedEvent: TypedEvent{
|
||||||
@@ -846,7 +847,7 @@ func (js *jetStream) apiDispatch(sub *subscription, c *client, acc *Account, sub
|
|||||||
},
|
},
|
||||||
Server: s.Name(),
|
Server: s.Name(),
|
||||||
Domain: js.config.Domain,
|
Domain: js.config.Domain,
|
||||||
Dropped: int64(pending),
|
Dropped: drained,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -864,8 +865,10 @@ func (s *Server) processJSAPIRoutedRequests() {
|
|||||||
for {
|
for {
|
||||||
select {
|
select {
|
||||||
case <-queue.ch:
|
case <-queue.ch:
|
||||||
reqs := queue.pop()
|
// Only pop one item at a time here, otherwise if the system is recovering
|
||||||
for _, r := range reqs {
|
// from queue buildup, then one worker will pull off all the tasks and the
|
||||||
|
// others will be starved of work.
|
||||||
|
for r, ok := queue.popOne(); ok && r != nil; r, ok = queue.popOne() {
|
||||||
client.pa = r.pa
|
client.pa = r.pa
|
||||||
start := time.Now()
|
start := time.Now()
|
||||||
r.jsub.icb(r.sub, client, r.acc, r.subject, r.reply, r.msg)
|
r.jsub.icb(r.sub, client, r.acc, r.subject, r.reply, r.msg)
|
||||||
@@ -874,7 +877,6 @@ func (s *Server) processJSAPIRoutedRequests() {
|
|||||||
}
|
}
|
||||||
atomic.AddInt64(&js.apiInflight, -1)
|
atomic.AddInt64(&js.apiInflight, -1)
|
||||||
}
|
}
|
||||||
queue.recycle(&reqs)
|
|
||||||
case <-s.quitCh:
|
case <-s.quitCh:
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -3416,7 +3418,7 @@ func (s *Server) processStreamRestore(ci *ClientInfo, acc *Account, cfg *StreamC
|
|||||||
Time: start,
|
Time: start,
|
||||||
},
|
},
|
||||||
Stream: streamName,
|
Stream: streamName,
|
||||||
Client: ci,
|
Client: ci.forAdvisory(),
|
||||||
Domain: domain,
|
Domain: domain,
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -3548,7 +3550,7 @@ func (s *Server) processStreamRestore(ci *ClientInfo, acc *Account, cfg *StreamC
|
|||||||
Start: start,
|
Start: start,
|
||||||
End: end,
|
End: end,
|
||||||
Bytes: int64(total),
|
Bytes: int64(total),
|
||||||
Client: ci,
|
Client: ci.forAdvisory(),
|
||||||
Domain: domain,
|
Domain: domain,
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -3681,7 +3683,7 @@ func (s *Server) jsStreamSnapshotRequest(sub *subscription, c *client, _ *Accoun
|
|||||||
},
|
},
|
||||||
Stream: mset.name(),
|
Stream: mset.name(),
|
||||||
State: sr.State,
|
State: sr.State,
|
||||||
Client: ci,
|
Client: ci.forAdvisory(),
|
||||||
Domain: s.getOpts().JetStreamDomain,
|
Domain: s.getOpts().JetStreamDomain,
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -3699,7 +3701,7 @@ func (s *Server) jsStreamSnapshotRequest(sub *subscription, c *client, _ *Accoun
|
|||||||
Stream: mset.name(),
|
Stream: mset.name(),
|
||||||
Start: start,
|
Start: start,
|
||||||
End: end,
|
End: end,
|
||||||
Client: ci,
|
Client: ci.forAdvisory(),
|
||||||
Domain: s.getOpts().JetStreamDomain,
|
Domain: s.getOpts().JetStreamDomain,
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -4263,9 +4265,17 @@ func (s *Server) jsConsumerInfoRequest(sub *subscription, c *client, _ *Account,
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
js.mu.RLock()
|
||||||
|
meta := cc.meta
|
||||||
|
js.mu.RUnlock()
|
||||||
|
|
||||||
|
// Since these could wait on the Raft group lock, don't do so under the JS lock.
|
||||||
|
ourID := meta.ID()
|
||||||
|
groupLeader := meta.GroupLeader()
|
||||||
|
groupCreated := meta.Created()
|
||||||
|
|
||||||
js.mu.RLock()
|
js.mu.RLock()
|
||||||
isLeader, sa, ca := cc.isLeader(), js.streamAssignment(acc.Name, streamName), js.consumerAssignment(acc.Name, streamName, consumerName)
|
isLeader, sa, ca := cc.isLeader(), js.streamAssignment(acc.Name, streamName), js.consumerAssignment(acc.Name, streamName, consumerName)
|
||||||
ourID := cc.meta.ID()
|
|
||||||
var rg *raftGroup
|
var rg *raftGroup
|
||||||
var offline, isMember bool
|
var offline, isMember bool
|
||||||
if ca != nil {
|
if ca != nil {
|
||||||
@@ -4279,7 +4289,7 @@ func (s *Server) jsConsumerInfoRequest(sub *subscription, c *client, _ *Account,
|
|||||||
// Also capture if we think there is no meta leader.
|
// Also capture if we think there is no meta leader.
|
||||||
var isLeaderLess bool
|
var isLeaderLess bool
|
||||||
if !isLeader {
|
if !isLeader {
|
||||||
isLeaderLess = cc.meta.GroupLeader() == _EMPTY_ && time.Since(cc.meta.Created()) > lostQuorumIntervalDefault
|
isLeaderLess = groupLeader == _EMPTY_ && time.Since(groupCreated) > lostQuorumIntervalDefault
|
||||||
}
|
}
|
||||||
js.mu.RUnlock()
|
js.mu.RUnlock()
|
||||||
|
|
||||||
@@ -4489,7 +4499,7 @@ func (s *Server) sendJetStreamAPIAuditAdvisory(ci *ClientInfo, acc *Account, sub
|
|||||||
Time: time.Now().UTC(),
|
Time: time.Now().UTC(),
|
||||||
},
|
},
|
||||||
Server: s.Name(),
|
Server: s.Name(),
|
||||||
Client: ci,
|
Client: ci.forAdvisory(),
|
||||||
Subject: subject,
|
Subject: subject,
|
||||||
Request: request,
|
Request: request,
|
||||||
Response: response,
|
Response: response,
|
||||||
|
|||||||
+88
-181
@@ -267,7 +267,12 @@ func (s *Server) JetStreamSnapshotMeta() error {
|
|||||||
return errNotLeader
|
return errNotLeader
|
||||||
}
|
}
|
||||||
|
|
||||||
return meta.InstallSnapshot(js.metaSnapshot())
|
snap, err := js.metaSnapshot()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return meta.InstallSnapshot(snap)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *Server) JetStreamStepdownStream(account, stream string) error {
|
func (s *Server) JetStreamStepdownStream(account, stream string) error {
|
||||||
@@ -437,73 +442,6 @@ func (cc *jetStreamCluster) isStreamCurrent(account, stream string) bool {
|
|||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
// Restart the stream in question.
|
|
||||||
// Should only be called when the stream is known to be in a bad state.
|
|
||||||
func (js *jetStream) restartStream(acc *Account, csa *streamAssignment) {
|
|
||||||
js.mu.Lock()
|
|
||||||
s, cc := js.srv, js.cluster
|
|
||||||
if cc == nil {
|
|
||||||
js.mu.Unlock()
|
|
||||||
return
|
|
||||||
}
|
|
||||||
// Need to lookup the one directly from the meta layer, what we get handed is a copy if coming from isStreamHealthy.
|
|
||||||
asa := cc.streams[acc.Name]
|
|
||||||
if asa == nil {
|
|
||||||
js.mu.Unlock()
|
|
||||||
return
|
|
||||||
}
|
|
||||||
sa := asa[csa.Config.Name]
|
|
||||||
if sa == nil {
|
|
||||||
js.mu.Unlock()
|
|
||||||
return
|
|
||||||
}
|
|
||||||
// Make sure to clear out the raft node if still present in the meta layer.
|
|
||||||
if rg := sa.Group; rg != nil && rg.node != nil {
|
|
||||||
if rg.node.State() != Closed {
|
|
||||||
rg.node.Stop()
|
|
||||||
}
|
|
||||||
rg.node = nil
|
|
||||||
}
|
|
||||||
sinceCreation := time.Since(sa.Created)
|
|
||||||
js.mu.Unlock()
|
|
||||||
|
|
||||||
// Process stream assignment to recreate.
|
|
||||||
// Check that we have given system enough time to start us up.
|
|
||||||
// This will be longer than obvious, and matches consumer logic in case system very busy.
|
|
||||||
if sinceCreation < 10*time.Second {
|
|
||||||
s.Debugf("Not restarting missing stream '%s > %s', too soon since creation %v",
|
|
||||||
acc, csa.Config.Name, sinceCreation)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
js.processStreamAssignment(sa)
|
|
||||||
|
|
||||||
// If we had consumers assigned to this server they will be present in the copy, csa.
|
|
||||||
// They also need to be processed. The csa consumers is a copy of only our consumers,
|
|
||||||
// those assigned to us, but the consumer assignment's there are direct from the meta
|
|
||||||
// layer to make this part much easier and avoid excessive lookups.
|
|
||||||
for _, cca := range csa.consumers {
|
|
||||||
if cca.deleted {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
// Need to look up original as well here to make sure node is nil.
|
|
||||||
js.mu.Lock()
|
|
||||||
ca := sa.consumers[cca.Name]
|
|
||||||
if ca != nil && ca.Group != nil {
|
|
||||||
// Make sure the node is stopped if still running.
|
|
||||||
if node := ca.Group.node; node != nil && node.State() != Closed {
|
|
||||||
node.Stop()
|
|
||||||
}
|
|
||||||
// Make sure node is wiped.
|
|
||||||
ca.Group.node = nil
|
|
||||||
}
|
|
||||||
js.mu.Unlock()
|
|
||||||
if ca != nil {
|
|
||||||
js.processConsumerAssignment(ca)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// isStreamHealthy will determine if the stream is up to date or very close.
|
// isStreamHealthy will determine if the stream is up to date or very close.
|
||||||
// For R1 it will make sure the stream is present on this server.
|
// For R1 it will make sure the stream is present on this server.
|
||||||
func (js *jetStream) isStreamHealthy(acc *Account, sa *streamAssignment) bool {
|
func (js *jetStream) isStreamHealthy(acc *Account, sa *streamAssignment) bool {
|
||||||
@@ -529,7 +467,6 @@ func (js *jetStream) isStreamHealthy(acc *Account, sa *streamAssignment) bool {
|
|||||||
// First lookup stream and make sure its there.
|
// First lookup stream and make sure its there.
|
||||||
mset, err := acc.lookupStream(streamName)
|
mset, err := acc.lookupStream(streamName)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
js.restartStream(acc, sa)
|
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -554,8 +491,6 @@ func (js *jetStream) isStreamHealthy(acc *Account, sa *streamAssignment) bool {
|
|||||||
s.Warnf("Detected stream cluster node skew '%s > %s'", acc.GetName(), streamName)
|
s.Warnf("Detected stream cluster node skew '%s > %s'", acc.GetName(), streamName)
|
||||||
node.Delete()
|
node.Delete()
|
||||||
mset.resetClusteredState(nil)
|
mset.resetClusteredState(nil)
|
||||||
} else if node.State() == Closed {
|
|
||||||
js.restartStream(acc, sa)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return false
|
return false
|
||||||
@@ -585,37 +520,9 @@ func (js *jetStream) isConsumerHealthy(mset *stream, consumer string, ca *consum
|
|||||||
node := ca.Group.node
|
node := ca.Group.node
|
||||||
js.mu.RUnlock()
|
js.mu.RUnlock()
|
||||||
|
|
||||||
// When we try to restart we nil out the node if applicable
|
|
||||||
// and reprocess the consumer assignment.
|
|
||||||
restartConsumer := func() {
|
|
||||||
mset.mu.RLock()
|
|
||||||
accName, streamName := mset.acc.GetName(), mset.cfg.Name
|
|
||||||
mset.mu.RUnlock()
|
|
||||||
|
|
||||||
js.mu.Lock()
|
|
||||||
deleted := ca.deleted
|
|
||||||
// Check that we have not just been created.
|
|
||||||
if !deleted && time.Since(ca.Created) < 10*time.Second {
|
|
||||||
s.Debugf("Not restarting missing consumer '%s > %s > %s', too soon since creation %v",
|
|
||||||
accName, streamName, consumer, time.Since(ca.Created))
|
|
||||||
js.mu.Unlock()
|
|
||||||
return
|
|
||||||
}
|
|
||||||
// Make sure the node is stopped if still running.
|
|
||||||
if node != nil && node.State() != Closed {
|
|
||||||
node.Stop()
|
|
||||||
}
|
|
||||||
ca.Group.node = nil
|
|
||||||
js.mu.Unlock()
|
|
||||||
if !deleted {
|
|
||||||
js.processConsumerAssignment(ca)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check if not running at all.
|
// Check if not running at all.
|
||||||
o := mset.lookupConsumer(consumer)
|
o := mset.lookupConsumer(consumer)
|
||||||
if o == nil {
|
if o == nil {
|
||||||
restartConsumer()
|
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -630,11 +537,12 @@ func (js *jetStream) isConsumerHealthy(mset *stream, consumer string, ca *consum
|
|||||||
s.Warnf("Detected consumer cluster node skew '%s > %s > %s'", accName, streamName, consumer)
|
s.Warnf("Detected consumer cluster node skew '%s > %s > %s'", accName, streamName, consumer)
|
||||||
node.Delete()
|
node.Delete()
|
||||||
o.deleteWithoutAdvisory()
|
o.deleteWithoutAdvisory()
|
||||||
restartConsumer()
|
|
||||||
} else if node.State() == Closed {
|
// When we try to restart we nil out the node and reprocess the consumer assignment.
|
||||||
// We have a consumer, and it should have a running node but it is closed.
|
js.mu.Lock()
|
||||||
o.stop()
|
ca.Group.node = nil
|
||||||
restartConsumer()
|
js.mu.Unlock()
|
||||||
|
js.processConsumerAssignment(ca)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return false
|
return false
|
||||||
@@ -901,15 +809,17 @@ func (js *jetStream) server() *Server {
|
|||||||
// Will respond if we do not think we have a metacontroller leader.
|
// Will respond if we do not think we have a metacontroller leader.
|
||||||
func (js *jetStream) isLeaderless() bool {
|
func (js *jetStream) isLeaderless() bool {
|
||||||
js.mu.RLock()
|
js.mu.RLock()
|
||||||
defer js.mu.RUnlock()
|
|
||||||
|
|
||||||
cc := js.cluster
|
cc := js.cluster
|
||||||
if cc == nil || cc.meta == nil {
|
if cc == nil || cc.meta == nil {
|
||||||
|
js.mu.RUnlock()
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
meta := cc.meta
|
||||||
|
js.mu.RUnlock()
|
||||||
|
|
||||||
// If we don't have a leader.
|
// If we don't have a leader.
|
||||||
// Make sure we have been running for enough time.
|
// Make sure we have been running for enough time.
|
||||||
if cc.meta.GroupLeader() == _EMPTY_ && time.Since(cc.meta.Created()) > lostQuorumIntervalDefault {
|
if meta.GroupLeader() == _EMPTY_ && time.Since(meta.Created()) > lostQuorumIntervalDefault {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
return false
|
return false
|
||||||
@@ -921,34 +831,38 @@ func (js *jetStream) isGroupLeaderless(rg *raftGroup) bool {
|
|||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
js.mu.RLock()
|
js.mu.RLock()
|
||||||
defer js.mu.RUnlock()
|
|
||||||
|
|
||||||
cc := js.cluster
|
cc := js.cluster
|
||||||
|
started := js.started
|
||||||
|
|
||||||
// If we are not a member we can not say..
|
// If we are not a member we can not say..
|
||||||
if cc.meta == nil {
|
if cc.meta == nil {
|
||||||
|
js.mu.RUnlock()
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
if !rg.isMember(cc.meta.ID()) {
|
if !rg.isMember(cc.meta.ID()) {
|
||||||
|
js.mu.RUnlock()
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
// Single peer groups always have a leader if we are here.
|
// Single peer groups always have a leader if we are here.
|
||||||
if rg.node == nil {
|
if rg.node == nil {
|
||||||
|
js.mu.RUnlock()
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
node := rg.node
|
||||||
|
js.mu.RUnlock()
|
||||||
// If we don't have a leader.
|
// If we don't have a leader.
|
||||||
if rg.node.GroupLeader() == _EMPTY_ {
|
if node.GroupLeader() == _EMPTY_ {
|
||||||
// Threshold for jetstream startup.
|
// Threshold for jetstream startup.
|
||||||
const startupThreshold = 10 * time.Second
|
const startupThreshold = 10 * time.Second
|
||||||
|
|
||||||
if rg.node.HadPreviousLeader() {
|
if node.HadPreviousLeader() {
|
||||||
// Make sure we have been running long enough to intelligently determine this.
|
// Make sure we have been running long enough to intelligently determine this.
|
||||||
if time.Since(js.started) > startupThreshold {
|
if time.Since(started) > startupThreshold {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// Make sure we have been running for enough time.
|
// Make sure we have been running for enough time.
|
||||||
if time.Since(rg.node.Created()) > lostQuorumIntervalDefault {
|
if time.Since(node.Created()) > lostQuorumIntervalDefault {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1334,7 +1248,10 @@ func (js *jetStream) monitorCluster() {
|
|||||||
}
|
}
|
||||||
// For the meta layer we want to snapshot when asked if we need one or have any entries that we can compact.
|
// For the meta layer we want to snapshot when asked if we need one or have any entries that we can compact.
|
||||||
if ne, _ := n.Size(); ne > 0 || n.NeedSnapshot() {
|
if ne, _ := n.Size(); ne > 0 || n.NeedSnapshot() {
|
||||||
if err := n.InstallSnapshot(js.metaSnapshot()); err == nil {
|
snap, err := js.metaSnapshot()
|
||||||
|
if err != nil {
|
||||||
|
s.Warnf("Error generating JetStream cluster snapshot: %v", err)
|
||||||
|
} else if err = n.InstallSnapshot(snap); err == nil {
|
||||||
lastSnapTime = time.Now()
|
lastSnapTime = time.Now()
|
||||||
} else if err != errNoSnapAvailable && err != errNodeClosed {
|
} else if err != errNoSnapAvailable && err != errNodeClosed {
|
||||||
s.Warnf("Error snapshotting JetStream cluster state: %v", err)
|
s.Warnf("Error snapshotting JetStream cluster state: %v", err)
|
||||||
@@ -1528,7 +1445,7 @@ func (js *jetStream) clusterStreamConfig(accName, streamName string) (StreamConf
|
|||||||
return StreamConfig{}, false
|
return StreamConfig{}, false
|
||||||
}
|
}
|
||||||
|
|
||||||
func (js *jetStream) metaSnapshot() []byte {
|
func (js *jetStream) metaSnapshot() ([]byte, error) {
|
||||||
start := time.Now()
|
start := time.Now()
|
||||||
js.mu.RLock()
|
js.mu.RLock()
|
||||||
s := js.srv
|
s := js.srv
|
||||||
@@ -1568,16 +1485,22 @@ func (js *jetStream) metaSnapshot() []byte {
|
|||||||
|
|
||||||
if len(streams) == 0 {
|
if len(streams) == 0 {
|
||||||
js.mu.RUnlock()
|
js.mu.RUnlock()
|
||||||
return nil
|
return nil, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Track how long it took to marshal the JSON
|
// Track how long it took to marshal the JSON
|
||||||
mstart := time.Now()
|
mstart := time.Now()
|
||||||
b, _ := json.Marshal(streams)
|
b, err := json.Marshal(streams)
|
||||||
mend := time.Since(mstart)
|
mend := time.Since(mstart)
|
||||||
|
|
||||||
js.mu.RUnlock()
|
js.mu.RUnlock()
|
||||||
|
|
||||||
|
// Must not be possible for a JSON marshaling error to result
|
||||||
|
// in an empty snapshot.
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
// Track how long it took to compress the JSON
|
// Track how long it took to compress the JSON
|
||||||
cstart := time.Now()
|
cstart := time.Now()
|
||||||
snap := s2.Encode(nil, b)
|
snap := s2.Encode(nil, b)
|
||||||
@@ -1587,7 +1510,7 @@ func (js *jetStream) metaSnapshot() []byte {
|
|||||||
s.rateLimitFormatWarnf("Metalayer snapshot took %.3fs (streams: %d, consumers: %d, marshal: %.3fs, s2: %.3fs, uncompressed: %d, compressed: %d)",
|
s.rateLimitFormatWarnf("Metalayer snapshot took %.3fs (streams: %d, consumers: %d, marshal: %.3fs, s2: %.3fs, uncompressed: %d, compressed: %d)",
|
||||||
took.Seconds(), nsa, nca, mend.Seconds(), cend.Seconds(), len(b), len(snap))
|
took.Seconds(), nsa, nca, mend.Seconds(), cend.Seconds(), len(b), len(snap))
|
||||||
}
|
}
|
||||||
return snap
|
return snap, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (js *jetStream) applyMetaSnapshot(buf []byte, ru *recoveryUpdates, isRecovering bool) error {
|
func (js *jetStream) applyMetaSnapshot(buf []byte, ru *recoveryUpdates, isRecovering bool) error {
|
||||||
@@ -2411,7 +2334,6 @@ func (js *jetStream) monitorStream(mset *stream, sa *streamAssignment, sendSnaps
|
|||||||
compactInterval = 2 * time.Minute
|
compactInterval = 2 * time.Minute
|
||||||
compactSizeMin = 8 * 1024 * 1024
|
compactSizeMin = 8 * 1024 * 1024
|
||||||
compactNumMin = 65536
|
compactNumMin = 65536
|
||||||
minSnapDelta = 10 * time.Second
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// Spread these out for large numbers on server restart.
|
// Spread these out for large numbers on server restart.
|
||||||
@@ -2435,16 +2357,15 @@ func (js *jetStream) monitorStream(mset *stream, sa *streamAssignment, sendSnaps
|
|||||||
// a complete and detailed state which could be costly in terms of memory, cpu and GC.
|
// a complete and detailed state which could be costly in terms of memory, cpu and GC.
|
||||||
// This only entails how many messages, and the first and last sequence of the stream.
|
// This only entails how many messages, and the first and last sequence of the stream.
|
||||||
// This is all that is needed to detect a change, and we can get this from FilteredState()
|
// This is all that is needed to detect a change, and we can get this from FilteredState()
|
||||||
// with and empty filter.
|
// with an empty filter.
|
||||||
var lastState SimpleState
|
var lastState SimpleState
|
||||||
var lastSnapTime time.Time
|
|
||||||
|
|
||||||
// Don't allow the upper layer to install snapshots until we have
|
// Don't allow the upper layer to install snapshots until we have
|
||||||
// fully recovered from disk.
|
// fully recovered from disk.
|
||||||
isRecovering := true
|
isRecovering := true
|
||||||
|
|
||||||
doSnapshot := func() {
|
doSnapshot := func() {
|
||||||
if mset == nil || isRecovering || isRestore || time.Since(lastSnapTime) < minSnapDelta {
|
if mset == nil || isRecovering || isRestore {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -2462,7 +2383,7 @@ func (js *jetStream) monitorStream(mset *stream, sa *streamAssignment, sendSnaps
|
|||||||
}
|
}
|
||||||
|
|
||||||
if err := n.InstallSnapshot(mset.stateSnapshot()); err == nil {
|
if err := n.InstallSnapshot(mset.stateSnapshot()); err == nil {
|
||||||
lastState, lastSnapTime = curState, time.Now()
|
lastState = curState
|
||||||
} else if err != errNoSnapAvailable && err != errNodeClosed && err != errCatchupsRunning {
|
} else if err != errNoSnapAvailable && err != errNodeClosed && err != errCatchupsRunning {
|
||||||
s.RateLimitWarnf("Failed to install snapshot for '%s > %s' [%s]: %v", mset.acc.Name, mset.name(), n.Group(), err)
|
s.RateLimitWarnf("Failed to install snapshot for '%s > %s' [%s]: %v", mset.acc.Name, mset.name(), n.Group(), err)
|
||||||
}
|
}
|
||||||
@@ -2541,10 +2462,14 @@ func (js *jetStream) monitorStream(mset *stream, sa *streamAssignment, sendSnaps
|
|||||||
for {
|
for {
|
||||||
select {
|
select {
|
||||||
case <-s.quitCh:
|
case <-s.quitCh:
|
||||||
|
// Server shutting down, but we might receive this before qch, so try to snapshot.
|
||||||
|
doSnapshot()
|
||||||
return
|
return
|
||||||
case <-mqch:
|
case <-mqch:
|
||||||
return
|
return
|
||||||
case <-qch:
|
case <-qch:
|
||||||
|
// Clean signal from shutdown routine so do best effort attempt to snapshot.
|
||||||
|
doSnapshot()
|
||||||
return
|
return
|
||||||
case <-aq.ch:
|
case <-aq.ch:
|
||||||
var ne, nb uint64
|
var ne, nb uint64
|
||||||
@@ -2603,12 +2528,6 @@ func (js *jetStream) monitorStream(mset *stream, sa *streamAssignment, sendSnaps
|
|||||||
// Check about snapshotting
|
// Check about snapshotting
|
||||||
// If we have at least min entries to compact, go ahead and try to snapshot/compact.
|
// If we have at least min entries to compact, go ahead and try to snapshot/compact.
|
||||||
if ne >= compactNumMin || nb > compactSizeMin || mset.getCLFS() > pclfs {
|
if ne >= compactNumMin || nb > compactSizeMin || mset.getCLFS() > pclfs {
|
||||||
// We want to make sure we do not short circuit if transistioning from no clfs.
|
|
||||||
if pclfs == 0 {
|
|
||||||
// This is always false by default.
|
|
||||||
lastState.firstNeedsUpdate = true
|
|
||||||
lastSnapTime = time.Time{}
|
|
||||||
}
|
|
||||||
doSnapshot()
|
doSnapshot()
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -2711,8 +2630,6 @@ func (js *jetStream) monitorStream(mset *stream, sa *streamAssignment, sendSnaps
|
|||||||
// keep stream assignment current
|
// keep stream assignment current
|
||||||
sa = mset.streamAssignment()
|
sa = mset.streamAssignment()
|
||||||
|
|
||||||
// keep peer list up to date with config
|
|
||||||
js.checkPeers(mset.raftGroup())
|
|
||||||
// We get this when we have a new stream assignment caused by an update.
|
// We get this when we have a new stream assignment caused by an update.
|
||||||
// We want to know if we are migrating.
|
// We want to know if we are migrating.
|
||||||
if migrating := mset.isMigrating(); migrating {
|
if migrating := mset.isMigrating(); migrating {
|
||||||
@@ -2800,7 +2717,7 @@ func (js *jetStream) monitorStream(mset *stream, sa *streamAssignment, sendSnaps
|
|||||||
// Check if we have a quorom.
|
// Check if we have a quorom.
|
||||||
if current >= neededCurrent {
|
if current >= neededCurrent {
|
||||||
s.Noticef("Transfer of stream leader for '%s > %s' to '%s'", accName, sa.Config.Name, newLeader)
|
s.Noticef("Transfer of stream leader for '%s > %s' to '%s'", accName, sa.Config.Name, newLeader)
|
||||||
n.UpdateKnownPeers(newPeers)
|
n.ProposeKnownPeers(newPeers)
|
||||||
n.StepDown(newLeaderPeer)
|
n.StepDown(newLeaderPeer)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -3090,8 +3007,10 @@ func (js *jetStream) applyStreamEntries(mset *stream, ce *CommittedEntry, isReco
|
|||||||
if subject == _EMPTY_ && ts == 0 && len(msg) == 0 && len(hdr) == 0 {
|
if subject == _EMPTY_ && ts == 0 && len(msg) == 0 && len(hdr) == 0 {
|
||||||
// Skip and update our lseq.
|
// Skip and update our lseq.
|
||||||
last := mset.store.SkipMsg()
|
last := mset.store.SkipMsg()
|
||||||
|
mset.mu.Lock()
|
||||||
mset.setLastSeq(last)
|
mset.setLastSeq(last)
|
||||||
mset.clearAllPreAcks(last)
|
mset.clearAllPreAcks(last)
|
||||||
|
mset.mu.Unlock()
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -3318,22 +3237,6 @@ func (s *Server) replicas(node RaftNode) []*PeerInfo {
|
|||||||
return replicas
|
return replicas
|
||||||
}
|
}
|
||||||
|
|
||||||
// Will check our node peers and see if we should remove a peer.
|
|
||||||
func (js *jetStream) checkPeers(rg *raftGroup) {
|
|
||||||
js.mu.Lock()
|
|
||||||
defer js.mu.Unlock()
|
|
||||||
|
|
||||||
// FIXME(dlc) - Single replicas?
|
|
||||||
if rg == nil || rg.node == nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
for _, peer := range rg.node.Peers() {
|
|
||||||
if !rg.isMember(peer.ID) {
|
|
||||||
rg.node.ProposeRemovePeer(peer.ID)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Process a leader change for the clustered stream.
|
// Process a leader change for the clustered stream.
|
||||||
func (js *jetStream) processStreamLeaderChange(mset *stream, isLeader bool) {
|
func (js *jetStream) processStreamLeaderChange(mset *stream, isLeader bool) {
|
||||||
if mset == nil {
|
if mset == nil {
|
||||||
@@ -3362,8 +3265,6 @@ func (js *jetStream) processStreamLeaderChange(mset *stream, isLeader bool) {
|
|||||||
if isLeader {
|
if isLeader {
|
||||||
s.Noticef("JetStream cluster new stream leader for '%s > %s'", account, streamName)
|
s.Noticef("JetStream cluster new stream leader for '%s > %s'", account, streamName)
|
||||||
s.sendStreamLeaderElectAdvisory(mset)
|
s.sendStreamLeaderElectAdvisory(mset)
|
||||||
// Check for peer removal and process here if needed.
|
|
||||||
js.checkPeers(sa.Group)
|
|
||||||
mset.checkAllowMsgCompress(peers)
|
mset.checkAllowMsgCompress(peers)
|
||||||
} else {
|
} else {
|
||||||
// We are stepping down.
|
// We are stepping down.
|
||||||
@@ -3579,7 +3480,7 @@ func (js *jetStream) processStreamAssignment(sa *streamAssignment) bool {
|
|||||||
js.processClusterCreateStream(acc, sa)
|
js.processClusterCreateStream(acc, sa)
|
||||||
} else if mset, _ := acc.lookupStream(sa.Config.Name); mset != nil {
|
} else if mset, _ := acc.lookupStream(sa.Config.Name); mset != nil {
|
||||||
// We have one here even though we are not a member. This can happen on re-assignment.
|
// We have one here even though we are not a member. This can happen on re-assignment.
|
||||||
s.removeStream(ourID, mset, sa)
|
s.removeStream(mset, sa)
|
||||||
}
|
}
|
||||||
|
|
||||||
// If this stream assignment does not have a sync subject (bug) set that the meta-leader should check when elected.
|
// If this stream assignment does not have a sync subject (bug) set that the meta-leader should check when elected.
|
||||||
@@ -3667,13 +3568,13 @@ func (js *jetStream) processUpdateStreamAssignment(sa *streamAssignment) {
|
|||||||
js.processClusterUpdateStream(acc, osa, sa)
|
js.processClusterUpdateStream(acc, osa, sa)
|
||||||
} else if mset, _ := acc.lookupStream(sa.Config.Name); mset != nil {
|
} else if mset, _ := acc.lookupStream(sa.Config.Name); mset != nil {
|
||||||
// We have one here even though we are not a member. This can happen on re-assignment.
|
// We have one here even though we are not a member. This can happen on re-assignment.
|
||||||
s.removeStream(ourID, mset, sa)
|
s.removeStream(mset, sa)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Common function to remove ourself from this server.
|
// Common function to remove ourselves from this server.
|
||||||
// This can happen on re-assignment, move, etc
|
// This can happen on re-assignment, move, etc
|
||||||
func (s *Server) removeStream(ourID string, mset *stream, nsa *streamAssignment) {
|
func (s *Server) removeStream(mset *stream, nsa *streamAssignment) {
|
||||||
if mset == nil {
|
if mset == nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -3683,7 +3584,6 @@ func (s *Server) removeStream(ourID string, mset *stream, nsa *streamAssignment)
|
|||||||
if node.Leader() {
|
if node.Leader() {
|
||||||
node.StepDown(nsa.Group.Preferred)
|
node.StepDown(nsa.Group.Preferred)
|
||||||
}
|
}
|
||||||
node.ProposeRemovePeer(ourID)
|
|
||||||
// shutdown monitor by shutting down raft.
|
// shutdown monitor by shutting down raft.
|
||||||
node.Delete()
|
node.Delete()
|
||||||
}
|
}
|
||||||
@@ -4472,10 +4372,11 @@ func (js *jetStream) processClusterCreateConsumer(ca *consumerAssignment, state
|
|||||||
} else {
|
} else {
|
||||||
// If we are clustered update the known peers.
|
// If we are clustered update the known peers.
|
||||||
js.mu.RLock()
|
js.mu.RLock()
|
||||||
if node := rg.node; node != nil {
|
node := rg.node
|
||||||
|
js.mu.RUnlock()
|
||||||
|
if node != nil {
|
||||||
node.UpdateKnownPeers(ca.Group.Peers)
|
node.UpdateKnownPeers(ca.Group.Peers)
|
||||||
}
|
}
|
||||||
js.mu.RUnlock()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check if we already have this consumer running.
|
// Check if we already have this consumer running.
|
||||||
@@ -4943,8 +4844,12 @@ func (js *jetStream) monitorConsumer(o *consumer, ca *consumerAssignment) {
|
|||||||
for {
|
for {
|
||||||
select {
|
select {
|
||||||
case <-s.quitCh:
|
case <-s.quitCh:
|
||||||
|
// Server shutting down, but we might receive this before qch, so try to snapshot.
|
||||||
|
doSnapshot(false)
|
||||||
return
|
return
|
||||||
case <-qch:
|
case <-qch:
|
||||||
|
// Clean signal from shutdown routine so do best effort attempt to snapshot.
|
||||||
|
doSnapshot(false)
|
||||||
return
|
return
|
||||||
case <-aq.ch:
|
case <-aq.ch:
|
||||||
ces := aq.pop()
|
ces := aq.pop()
|
||||||
@@ -5009,8 +4914,6 @@ func (js *jetStream) monitorConsumer(o *consumer, ca *consumerAssignment) {
|
|||||||
// We get this when we have a new consumer assignment caused by an update.
|
// We get this when we have a new consumer assignment caused by an update.
|
||||||
// We want to know if we are migrating.
|
// We want to know if we are migrating.
|
||||||
rg := o.raftGroup()
|
rg := o.raftGroup()
|
||||||
// keep peer list up to date with config
|
|
||||||
js.checkPeers(rg)
|
|
||||||
// If we are migrating, monitor for the new peers to be caught up.
|
// If we are migrating, monitor for the new peers to be caught up.
|
||||||
replicas, err := o.replica()
|
replicas, err := o.replica()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -5327,8 +5230,6 @@ func (js *jetStream) processConsumerLeaderChange(o *consumer, isLeader bool) err
|
|||||||
if isLeader {
|
if isLeader {
|
||||||
s.Noticef("JetStream cluster new consumer leader for '%s > %s > %s'", ca.Client.serviceAccount(), streamName, consumerName)
|
s.Noticef("JetStream cluster new consumer leader for '%s > %s > %s'", ca.Client.serviceAccount(), streamName, consumerName)
|
||||||
s.sendConsumerLeaderElectAdvisory(o)
|
s.sendConsumerLeaderElectAdvisory(o)
|
||||||
// Check for peer removal and process here if needed.
|
|
||||||
js.checkPeers(ca.Group)
|
|
||||||
} else {
|
} else {
|
||||||
// We are stepping down.
|
// We are stepping down.
|
||||||
// Make sure if we are doing so because we have lost quorum that we send the appropriate advisories.
|
// Make sure if we are doing so because we have lost quorum that we send the appropriate advisories.
|
||||||
@@ -7267,23 +7168,29 @@ func (s *Server) jsClusteredMsgDeleteRequest(ci *ClientInfo, acc *Account, mset
|
|||||||
}
|
}
|
||||||
|
|
||||||
func encodeAddStreamAssignment(sa *streamAssignment) []byte {
|
func encodeAddStreamAssignment(sa *streamAssignment) []byte {
|
||||||
|
csa := *sa
|
||||||
|
csa.Client = csa.Client.forProposal()
|
||||||
var bb bytes.Buffer
|
var bb bytes.Buffer
|
||||||
bb.WriteByte(byte(assignStreamOp))
|
bb.WriteByte(byte(assignStreamOp))
|
||||||
json.NewEncoder(&bb).Encode(sa)
|
json.NewEncoder(&bb).Encode(csa)
|
||||||
return bb.Bytes()
|
return bb.Bytes()
|
||||||
}
|
}
|
||||||
|
|
||||||
func encodeUpdateStreamAssignment(sa *streamAssignment) []byte {
|
func encodeUpdateStreamAssignment(sa *streamAssignment) []byte {
|
||||||
|
csa := *sa
|
||||||
|
csa.Client = csa.Client.forProposal()
|
||||||
var bb bytes.Buffer
|
var bb bytes.Buffer
|
||||||
bb.WriteByte(byte(updateStreamOp))
|
bb.WriteByte(byte(updateStreamOp))
|
||||||
json.NewEncoder(&bb).Encode(sa)
|
json.NewEncoder(&bb).Encode(csa)
|
||||||
return bb.Bytes()
|
return bb.Bytes()
|
||||||
}
|
}
|
||||||
|
|
||||||
func encodeDeleteStreamAssignment(sa *streamAssignment) []byte {
|
func encodeDeleteStreamAssignment(sa *streamAssignment) []byte {
|
||||||
|
csa := *sa
|
||||||
|
csa.Client = csa.Client.forProposal()
|
||||||
var bb bytes.Buffer
|
var bb bytes.Buffer
|
||||||
bb.WriteByte(byte(removeStreamOp))
|
bb.WriteByte(byte(removeStreamOp))
|
||||||
json.NewEncoder(&bb).Encode(sa)
|
json.NewEncoder(&bb).Encode(csa)
|
||||||
return bb.Bytes()
|
return bb.Bytes()
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -7671,16 +7578,20 @@ func (s *Server) jsClusteredConsumerRequest(ci *ClientInfo, acc *Account, subjec
|
|||||||
}
|
}
|
||||||
|
|
||||||
func encodeAddConsumerAssignment(ca *consumerAssignment) []byte {
|
func encodeAddConsumerAssignment(ca *consumerAssignment) []byte {
|
||||||
|
cca := *ca
|
||||||
|
cca.Client = cca.Client.forProposal()
|
||||||
var bb bytes.Buffer
|
var bb bytes.Buffer
|
||||||
bb.WriteByte(byte(assignConsumerOp))
|
bb.WriteByte(byte(assignConsumerOp))
|
||||||
json.NewEncoder(&bb).Encode(ca)
|
json.NewEncoder(&bb).Encode(cca)
|
||||||
return bb.Bytes()
|
return bb.Bytes()
|
||||||
}
|
}
|
||||||
|
|
||||||
func encodeDeleteConsumerAssignment(ca *consumerAssignment) []byte {
|
func encodeDeleteConsumerAssignment(ca *consumerAssignment) []byte {
|
||||||
|
cca := *ca
|
||||||
|
cca.Client = cca.Client.forProposal()
|
||||||
var bb bytes.Buffer
|
var bb bytes.Buffer
|
||||||
bb.WriteByte(byte(removeConsumerOp))
|
bb.WriteByte(byte(removeConsumerOp))
|
||||||
json.NewEncoder(&bb).Encode(ca)
|
json.NewEncoder(&bb).Encode(cca)
|
||||||
return bb.Bytes()
|
return bb.Bytes()
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -7691,25 +7602,21 @@ func decodeConsumerAssignment(buf []byte) (*consumerAssignment, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func encodeAddConsumerAssignmentCompressed(ca *consumerAssignment) []byte {
|
func encodeAddConsumerAssignmentCompressed(ca *consumerAssignment) []byte {
|
||||||
b, err := json.Marshal(ca)
|
cca := *ca
|
||||||
if err != nil {
|
cca.Client = cca.Client.forProposal()
|
||||||
return nil
|
|
||||||
}
|
|
||||||
// TODO(dlc) - Streaming better approach here probably.
|
|
||||||
var bb bytes.Buffer
|
var bb bytes.Buffer
|
||||||
bb.WriteByte(byte(assignCompressedConsumerOp))
|
bb.WriteByte(byte(assignCompressedConsumerOp))
|
||||||
bb.Write(s2.Encode(nil, b))
|
s2e := s2.NewWriter(&bb)
|
||||||
|
json.NewEncoder(s2e).Encode(cca)
|
||||||
|
s2e.Close()
|
||||||
return bb.Bytes()
|
return bb.Bytes()
|
||||||
}
|
}
|
||||||
|
|
||||||
func decodeConsumerAssignmentCompressed(buf []byte) (*consumerAssignment, error) {
|
func decodeConsumerAssignmentCompressed(buf []byte) (*consumerAssignment, error) {
|
||||||
var ca consumerAssignment
|
var ca consumerAssignment
|
||||||
js, err := s2.Decode(nil, buf)
|
bb := bytes.NewBuffer(buf)
|
||||||
if err != nil {
|
s2d := s2.NewReader(bb)
|
||||||
return nil, err
|
return &ca, json.NewDecoder(s2d).Decode(&ca)
|
||||||
}
|
|
||||||
err = json.Unmarshal(js, &ca)
|
|
||||||
return &ca, err
|
|
||||||
}
|
}
|
||||||
|
|
||||||
var errBadStreamMsg = errors.New("jetstream cluster bad replicated stream msg")
|
var errBadStreamMsg = errors.New("jetstream cluster bad replicated stream msg")
|
||||||
@@ -8654,6 +8561,8 @@ func (mset *stream) processCatchupMsg(msg []byte) (uint64, error) {
|
|||||||
return 0, err
|
return 0, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
mset.mu.Lock()
|
||||||
|
defer mset.mu.Unlock()
|
||||||
// Update our lseq.
|
// Update our lseq.
|
||||||
mset.setLastSeq(seq)
|
mset.setLastSeq(seq)
|
||||||
|
|
||||||
@@ -8661,11 +8570,9 @@ func (mset *stream) processCatchupMsg(msg []byte) (uint64, error) {
|
|||||||
if len(hdr) > 0 {
|
if len(hdr) > 0 {
|
||||||
if msgId := getMsgId(hdr); msgId != _EMPTY_ {
|
if msgId := getMsgId(hdr); msgId != _EMPTY_ {
|
||||||
if !ddloaded {
|
if !ddloaded {
|
||||||
mset.mu.Lock()
|
|
||||||
mset.rebuildDedupe()
|
mset.rebuildDedupe()
|
||||||
mset.mu.Unlock()
|
|
||||||
}
|
}
|
||||||
mset.storeMsgId(&ddentry{msgId, seq, ts})
|
mset.storeMsgIdLocked(&ddentry{msgId, seq, ts})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+12
-2
@@ -18,13 +18,22 @@ import (
|
|||||||
"time"
|
"time"
|
||||||
)
|
)
|
||||||
|
|
||||||
func (s *Server) publishAdvisory(acc *Account, subject string, adv any) {
|
// publishAdvisory sends the given advisory into the account. Returns true if
|
||||||
|
// it was sent, false if not (i.e. due to lack of interest or a marshal error).
|
||||||
|
func (s *Server) publishAdvisory(acc *Account, subject string, adv any) bool {
|
||||||
if acc == nil {
|
if acc == nil {
|
||||||
acc = s.SystemAccount()
|
acc = s.SystemAccount()
|
||||||
if acc == nil {
|
if acc == nil {
|
||||||
return
|
return false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// If there is no one listening for this advisory then save ourselves the effort
|
||||||
|
// and don't bother encoding the JSON or sending it.
|
||||||
|
if sl := acc.sl; (sl != nil && !sl.HasInterest(subject)) && !s.hasGatewayInterest(acc.Name, subject) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
ej, err := json.Marshal(adv)
|
ej, err := json.Marshal(adv)
|
||||||
if err == nil {
|
if err == nil {
|
||||||
err = s.sendInternalAccountMsg(acc, subject, ej)
|
err = s.sendInternalAccountMsg(acc, subject, ej)
|
||||||
@@ -34,6 +43,7 @@ func (s *Server) publishAdvisory(acc *Account, subject string, adv any) {
|
|||||||
} else {
|
} else {
|
||||||
s.Warnf("Advisory could not be serialized for account %q: %v", acc.Name, err)
|
s.Warnf("Advisory could not be serialized for account %q: %v", acc.Name, err)
|
||||||
}
|
}
|
||||||
|
return err == nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// JSAPIAudit is an advisory about administrative actions taken on JetStream
|
// JSAPIAudit is an advisory about administrative actions taken on JetStream
|
||||||
|
|||||||
+19
-13
@@ -2247,8 +2247,16 @@ func (c *client) sendLeafNodeSubUpdate(key string, n int32) {
|
|||||||
checkPerms = false
|
checkPerms = false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if checkPerms && !c.canSubscribe(key) {
|
if checkPerms {
|
||||||
return
|
var subject string
|
||||||
|
if sep := strings.IndexByte(key, ' '); sep != -1 {
|
||||||
|
subject = key[:sep]
|
||||||
|
} else {
|
||||||
|
subject = key
|
||||||
|
}
|
||||||
|
if !c.canSubscribe(subject) {
|
||||||
|
return
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// If we are here we can send over to the other side.
|
// If we are here we can send over to the other side.
|
||||||
@@ -2435,7 +2443,6 @@ func (c *client) processLeafSub(argo []byte) (err error) {
|
|||||||
}
|
}
|
||||||
key := bytesToString(sub.sid)
|
key := bytesToString(sub.sid)
|
||||||
osub := c.subs[key]
|
osub := c.subs[key]
|
||||||
updateGWs := false
|
|
||||||
if osub == nil {
|
if osub == nil {
|
||||||
c.subs[key] = sub
|
c.subs[key] = sub
|
||||||
// Now place into the account sl.
|
// Now place into the account sl.
|
||||||
@@ -2446,7 +2453,6 @@ func (c *client) processLeafSub(argo []byte) (err error) {
|
|||||||
c.sendErr("Invalid Subscription")
|
c.sendErr("Invalid Subscription")
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
updateGWs = srv.gateway.enabled
|
|
||||||
} else if sub.queue != nil {
|
} else if sub.queue != nil {
|
||||||
// For a queue we need to update the weight.
|
// For a queue we need to update the weight.
|
||||||
delta = sub.qw - atomic.LoadInt32(&osub.qw)
|
delta = sub.qw - atomic.LoadInt32(&osub.qw)
|
||||||
@@ -2469,7 +2475,7 @@ func (c *client) processLeafSub(argo []byte) (err error) {
|
|||||||
if !spoke {
|
if !spoke {
|
||||||
// If we are routing add to the route map for the associated account.
|
// If we are routing add to the route map for the associated account.
|
||||||
srv.updateRouteSubscriptionMap(acc, sub, delta)
|
srv.updateRouteSubscriptionMap(acc, sub, delta)
|
||||||
if updateGWs {
|
if srv.gateway.enabled {
|
||||||
srv.gatewayUpdateSubInterest(acc.Name, sub, delta)
|
srv.gatewayUpdateSubInterest(acc.Name, sub, delta)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -2511,27 +2517,27 @@ func (c *client) processLeafUnsub(arg []byte) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
updateGWs := false
|
|
||||||
spoke := c.isSpokeLeafNode()
|
spoke := c.isSpokeLeafNode()
|
||||||
// We store local subs by account and subject and optionally queue name.
|
// We store local subs by account and subject and optionally queue name.
|
||||||
// LS- will have the arg exactly as the key.
|
// LS- will have the arg exactly as the key.
|
||||||
sub, ok := c.subs[string(arg)]
|
sub, ok := c.subs[string(arg)]
|
||||||
|
if !ok {
|
||||||
|
// If not found, don't try to update routes/gws/leaf nodes.
|
||||||
|
c.mu.Unlock()
|
||||||
|
return nil
|
||||||
|
}
|
||||||
delta := int32(1)
|
delta := int32(1)
|
||||||
if ok && len(sub.queue) > 0 {
|
if len(sub.queue) > 0 {
|
||||||
delta = sub.qw
|
delta = sub.qw
|
||||||
}
|
}
|
||||||
c.mu.Unlock()
|
c.mu.Unlock()
|
||||||
|
|
||||||
if ok {
|
c.unsubscribe(acc, sub, true, true)
|
||||||
c.unsubscribe(acc, sub, true, true)
|
|
||||||
updateGWs = srv.gateway.enabled
|
|
||||||
}
|
|
||||||
|
|
||||||
if !spoke {
|
if !spoke {
|
||||||
// If we are routing subtract from the route map for the associated account.
|
// If we are routing subtract from the route map for the associated account.
|
||||||
srv.updateRouteSubscriptionMap(acc, sub, -delta)
|
srv.updateRouteSubscriptionMap(acc, sub, -delta)
|
||||||
// Gateways
|
// Gateways
|
||||||
if updateGWs {
|
if srv.gateway.enabled {
|
||||||
srv.gatewayUpdateSubInterest(acc.Name, sub, -delta)
|
srv.gatewayUpdateSubInterest(acc.Name, sub, -delta)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+53
-37
@@ -143,8 +143,8 @@ func (ms *memStore) storeRawMsg(subj string, hdr, msg []byte, seq uint64, ts int
|
|||||||
return ErrMaxBytes
|
return ErrMaxBytes
|
||||||
}
|
}
|
||||||
// If we are here we are at a subject maximum, need to determine if dropping last message gives us enough room.
|
// If we are here we are at a subject maximum, need to determine if dropping last message gives us enough room.
|
||||||
if ss.firstNeedsUpdate {
|
if ss.firstNeedsUpdate || ss.lastNeedsUpdate {
|
||||||
ms.recalculateFirstForSubj(subj, ss.First, ss)
|
ms.recalculateForSubj(subj, ss)
|
||||||
}
|
}
|
||||||
sm, ok := ms.msgs[ss.First]
|
sm, ok := ms.msgs[ss.First]
|
||||||
if !ok || memStoreMsgSize(sm.subj, sm.hdr, sm.msg) < memStoreMsgSize(subj, hdr, msg) {
|
if !ok || memStoreMsgSize(sm.subj, sm.hdr, sm.msg) < memStoreMsgSize(subj, hdr, msg) {
|
||||||
@@ -430,8 +430,8 @@ func (ms *memStore) filteredStateLocked(sseq uint64, filter string, lastPerSubje
|
|||||||
var totalSkipped uint64
|
var totalSkipped uint64
|
||||||
// We will track start and end sequences as we go.
|
// We will track start and end sequences as we go.
|
||||||
ms.fss.Match(stringToBytes(filter), func(subj []byte, fss *SimpleState) {
|
ms.fss.Match(stringToBytes(filter), func(subj []byte, fss *SimpleState) {
|
||||||
if fss.firstNeedsUpdate {
|
if fss.firstNeedsUpdate || fss.lastNeedsUpdate {
|
||||||
ms.recalculateFirstForSubj(bytesToString(subj), fss.First, fss)
|
ms.recalculateForSubj(bytesToString(subj), fss)
|
||||||
}
|
}
|
||||||
if sseq <= fss.First {
|
if sseq <= fss.First {
|
||||||
update(fss)
|
update(fss)
|
||||||
@@ -585,8 +585,8 @@ func (ms *memStore) SubjectsState(subject string) map[string]SimpleState {
|
|||||||
fss := make(map[string]SimpleState)
|
fss := make(map[string]SimpleState)
|
||||||
ms.fss.Match(stringToBytes(subject), func(subj []byte, ss *SimpleState) {
|
ms.fss.Match(stringToBytes(subject), func(subj []byte, ss *SimpleState) {
|
||||||
subjs := string(subj)
|
subjs := string(subj)
|
||||||
if ss.firstNeedsUpdate {
|
if ss.firstNeedsUpdate || ss.lastNeedsUpdate {
|
||||||
ms.recalculateFirstForSubj(subjs, ss.First, ss)
|
ms.recalculateForSubj(subjs, ss)
|
||||||
}
|
}
|
||||||
oss := fss[subjs]
|
oss := fss[subjs]
|
||||||
if oss.First == 0 { // New
|
if oss.First == 0 { // New
|
||||||
@@ -675,8 +675,8 @@ func (ms *memStore) NumPendingMulti(sseq uint64, sl *Sublist, lastPerSubject boo
|
|||||||
var totalSkipped uint64
|
var totalSkipped uint64
|
||||||
// We will track start and end sequences as we go.
|
// We will track start and end sequences as we go.
|
||||||
IntersectStree[SimpleState](ms.fss, sl, func(subj []byte, fss *SimpleState) {
|
IntersectStree[SimpleState](ms.fss, sl, func(subj []byte, fss *SimpleState) {
|
||||||
if fss.firstNeedsUpdate {
|
if fss.firstNeedsUpdate || fss.lastNeedsUpdate {
|
||||||
ms.recalculateFirstForSubj(bytesToString(subj), fss.First, fss)
|
ms.recalculateForSubj(bytesToString(subj), fss)
|
||||||
}
|
}
|
||||||
if sseq <= fss.First {
|
if sseq <= fss.First {
|
||||||
update(fss)
|
update(fss)
|
||||||
@@ -793,8 +793,8 @@ func (ms *memStore) enforcePerSubjectLimit(subj string, ss *SimpleState) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
for nmsgs := ss.Msgs; nmsgs > uint64(ms.maxp); nmsgs = ss.Msgs {
|
for nmsgs := ss.Msgs; nmsgs > uint64(ms.maxp); nmsgs = ss.Msgs {
|
||||||
if ss.firstNeedsUpdate {
|
if ss.firstNeedsUpdate || ss.lastNeedsUpdate {
|
||||||
ms.recalculateFirstForSubj(subj, ss.First, ss)
|
ms.recalculateForSubj(subj, ss)
|
||||||
}
|
}
|
||||||
if !ms.removeMsg(ss.First, false) {
|
if !ms.removeMsg(ss.First, false) {
|
||||||
break
|
break
|
||||||
@@ -1009,8 +1009,9 @@ func (ms *memStore) Compact(seq uint64) (uint64, error) {
|
|||||||
if sm := ms.msgs[seq]; sm != nil {
|
if sm := ms.msgs[seq]; sm != nil {
|
||||||
bytes += memStoreMsgSize(sm.subj, sm.hdr, sm.msg)
|
bytes += memStoreMsgSize(sm.subj, sm.hdr, sm.msg)
|
||||||
purged++
|
purged++
|
||||||
delete(ms.msgs, seq)
|
|
||||||
ms.removeSeqPerSubject(sm.subj, seq)
|
ms.removeSeqPerSubject(sm.subj, seq)
|
||||||
|
// Must delete message after updating per-subject info, to be consistent with file store.
|
||||||
|
delete(ms.msgs, seq)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if purged > ms.state.Msgs {
|
if purged > ms.state.Msgs {
|
||||||
@@ -1098,8 +1099,9 @@ func (ms *memStore) Truncate(seq uint64) error {
|
|||||||
if sm := ms.msgs[i]; sm != nil {
|
if sm := ms.msgs[i]; sm != nil {
|
||||||
purged++
|
purged++
|
||||||
bytes += memStoreMsgSize(sm.subj, sm.hdr, sm.msg)
|
bytes += memStoreMsgSize(sm.subj, sm.hdr, sm.msg)
|
||||||
delete(ms.msgs, i)
|
|
||||||
ms.removeSeqPerSubject(sm.subj, i)
|
ms.removeSeqPerSubject(sm.subj, i)
|
||||||
|
// Must delete message after updating per-subject info, to be consistent with file store.
|
||||||
|
delete(ms.msgs, i)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// Reset last.
|
// Reset last.
|
||||||
@@ -1265,8 +1267,8 @@ func (ms *memStore) LoadNextMsg(filter string, wc bool, start uint64, smp *Store
|
|||||||
if !ok {
|
if !ok {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
if ss.firstNeedsUpdate {
|
if ss.firstNeedsUpdate || ss.lastNeedsUpdate {
|
||||||
ms.recalculateFirstForSubj(subj, ss.First, ss)
|
ms.recalculateForSubj(subj, ss)
|
||||||
}
|
}
|
||||||
if ss.First < fseq {
|
if ss.First < fseq {
|
||||||
fseq = ss.First
|
fseq = ss.First
|
||||||
@@ -1360,34 +1362,47 @@ func (ms *memStore) removeSeqPerSubject(subj string, seq uint64) {
|
|||||||
}
|
}
|
||||||
ss.Msgs--
|
ss.Msgs--
|
||||||
|
|
||||||
// If we know we only have 1 msg left don't need to search for next first.
|
// We can lazily calculate the first/last sequence when needed.
|
||||||
if ss.Msgs == 1 {
|
ss.firstNeedsUpdate = seq == ss.First || ss.firstNeedsUpdate
|
||||||
if seq == ss.Last {
|
ss.lastNeedsUpdate = seq == ss.Last || ss.lastNeedsUpdate
|
||||||
ss.Last = ss.First
|
|
||||||
} else {
|
|
||||||
ss.First = ss.Last
|
|
||||||
}
|
|
||||||
ss.firstNeedsUpdate = false
|
|
||||||
} else {
|
|
||||||
ss.firstNeedsUpdate = seq == ss.First || ss.firstNeedsUpdate
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Will recalculate the first sequence for this subject in this block.
|
// Will recalculate the first and/or last sequence for this subject.
|
||||||
// Lock should be held.
|
// Lock should be held.
|
||||||
func (ms *memStore) recalculateFirstForSubj(subj string, startSeq uint64, ss *SimpleState) {
|
func (ms *memStore) recalculateForSubj(subj string, ss *SimpleState) {
|
||||||
tseq := startSeq + 1
|
if ss.firstNeedsUpdate {
|
||||||
if tseq < ms.state.FirstSeq {
|
tseq := ss.First + 1
|
||||||
tseq = ms.state.FirstSeq
|
if tseq < ms.state.FirstSeq {
|
||||||
|
tseq = ms.state.FirstSeq
|
||||||
|
}
|
||||||
|
for ; tseq <= ss.Last; tseq++ {
|
||||||
|
if sm := ms.msgs[tseq]; sm != nil && sm.subj == subj {
|
||||||
|
ss.First = tseq
|
||||||
|
ss.firstNeedsUpdate = false
|
||||||
|
if ss.Msgs == 1 {
|
||||||
|
ss.Last = tseq
|
||||||
|
ss.lastNeedsUpdate = false
|
||||||
|
return
|
||||||
|
}
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
for ; tseq <= ss.Last; tseq++ {
|
if ss.lastNeedsUpdate {
|
||||||
if sm := ms.msgs[tseq]; sm != nil && sm.subj == subj {
|
tseq := ss.Last - 1
|
||||||
ss.First = tseq
|
if tseq > ms.state.LastSeq {
|
||||||
if ss.Msgs == 1 {
|
tseq = ms.state.LastSeq
|
||||||
|
}
|
||||||
|
for ; tseq >= ss.First; tseq-- {
|
||||||
|
if sm := ms.msgs[tseq]; sm != nil && sm.subj == subj {
|
||||||
ss.Last = tseq
|
ss.Last = tseq
|
||||||
|
ss.lastNeedsUpdate = false
|
||||||
|
if ss.Msgs == 1 {
|
||||||
|
ss.First = tseq
|
||||||
|
ss.firstNeedsUpdate = false
|
||||||
|
}
|
||||||
|
return
|
||||||
}
|
}
|
||||||
ss.firstNeedsUpdate = false
|
|
||||||
return
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1403,7 +1418,6 @@ func (ms *memStore) removeMsg(seq uint64, secure bool) bool {
|
|||||||
|
|
||||||
ss = memStoreMsgSize(sm.subj, sm.hdr, sm.msg)
|
ss = memStoreMsgSize(sm.subj, sm.hdr, sm.msg)
|
||||||
|
|
||||||
delete(ms.msgs, seq)
|
|
||||||
if ms.state.Msgs > 0 {
|
if ms.state.Msgs > 0 {
|
||||||
ms.state.Msgs--
|
ms.state.Msgs--
|
||||||
if ss > ms.state.Bytes {
|
if ss > ms.state.Bytes {
|
||||||
@@ -1428,6 +1442,8 @@ func (ms *memStore) removeMsg(seq uint64, secure bool) bool {
|
|||||||
|
|
||||||
// Remove any per subject tracking.
|
// Remove any per subject tracking.
|
||||||
ms.removeSeqPerSubject(sm.subj, seq)
|
ms.removeSeqPerSubject(sm.subj, seq)
|
||||||
|
// Must delete message after updating per-subject info, to be consistent with file store.
|
||||||
|
delete(ms.msgs, seq)
|
||||||
|
|
||||||
if ms.scb != nil {
|
if ms.scb != nil {
|
||||||
// We do not want to hold any locks here.
|
// We do not want to hold any locks here.
|
||||||
|
|||||||
+25
-13
@@ -61,6 +61,7 @@ type RaftNode interface {
|
|||||||
ID() string
|
ID() string
|
||||||
Group() string
|
Group() string
|
||||||
Peers() []*Peer
|
Peers() []*Peer
|
||||||
|
ProposeKnownPeers(knownPeers []string)
|
||||||
UpdateKnownPeers(knownPeers []string)
|
UpdateKnownPeers(knownPeers []string)
|
||||||
ProposeAddPeer(peer string) error
|
ProposeAddPeer(peer string) error
|
||||||
ProposeRemovePeer(peer string) error
|
ProposeRemovePeer(peer string) error
|
||||||
@@ -1326,6 +1327,12 @@ func (n *raft) isCurrent(includeForwardProgress bool) bool {
|
|||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if n.paused && n.hcommit > n.commit {
|
||||||
|
// We're currently paused, waiting to be resumed to apply pending commits.
|
||||||
|
n.debug("Not current, waiting to resume applies commit=%d, hcommit=%d", n.commit, n.hcommit)
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
if n.commit == n.applied {
|
if n.commit == n.applied {
|
||||||
// At this point if we are current, we can return saying so.
|
// At this point if we are current, we can return saying so.
|
||||||
clearBehindState()
|
clearBehindState()
|
||||||
@@ -1556,14 +1563,12 @@ func (n *raft) ID() string {
|
|||||||
if n == nil {
|
if n == nil {
|
||||||
return _EMPTY_
|
return _EMPTY_
|
||||||
}
|
}
|
||||||
n.RLock()
|
// Lock not needed as n.id is never changed after creation.
|
||||||
defer n.RUnlock()
|
|
||||||
return n.id
|
return n.id
|
||||||
}
|
}
|
||||||
|
|
||||||
func (n *raft) Group() string {
|
func (n *raft) Group() string {
|
||||||
n.RLock()
|
// Lock not needed as n.group is never changed after creation.
|
||||||
defer n.RUnlock()
|
|
||||||
return n.group
|
return n.group
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1588,19 +1593,23 @@ func (n *raft) Peers() []*Peer {
|
|||||||
return peers
|
return peers
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Update and propose our known set of peers.
|
||||||
|
func (n *raft) ProposeKnownPeers(knownPeers []string) {
|
||||||
|
// If we are the leader update and send this update out.
|
||||||
|
if n.State() != Leader {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
n.UpdateKnownPeers(knownPeers)
|
||||||
|
n.sendPeerState()
|
||||||
|
}
|
||||||
|
|
||||||
// Update our known set of peers.
|
// Update our known set of peers.
|
||||||
func (n *raft) UpdateKnownPeers(knownPeers []string) {
|
func (n *raft) UpdateKnownPeers(knownPeers []string) {
|
||||||
n.Lock()
|
n.Lock()
|
||||||
// Process like peer state update.
|
// Process like peer state update.
|
||||||
ps := &peerState{knownPeers, len(knownPeers), n.extSt}
|
ps := &peerState{knownPeers, len(knownPeers), n.extSt}
|
||||||
n.processPeerState(ps)
|
n.processPeerState(ps)
|
||||||
isLeader := n.State() == Leader
|
|
||||||
n.Unlock()
|
n.Unlock()
|
||||||
|
|
||||||
// If we are the leader send this update out as well.
|
|
||||||
if isLeader {
|
|
||||||
n.sendPeerState()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// ApplyQ returns the apply queue that new commits will be sent to for the
|
// ApplyQ returns the apply queue that new commits will be sent to for the
|
||||||
@@ -1615,8 +1624,7 @@ func (n *raft) LeadChangeC() <-chan bool { return n.leadc }
|
|||||||
func (n *raft) QuitC() <-chan struct{} { return n.quit }
|
func (n *raft) QuitC() <-chan struct{} { return n.quit }
|
||||||
|
|
||||||
func (n *raft) Created() time.Time {
|
func (n *raft) Created() time.Time {
|
||||||
n.RLock()
|
// Lock not needed as n.created is never changed after creation.
|
||||||
defer n.RUnlock()
|
|
||||||
return n.created
|
return n.created
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1840,7 +1848,7 @@ runner:
|
|||||||
// just will remove them from the central monitoring map
|
// just will remove them from the central monitoring map
|
||||||
queues := []interface {
|
queues := []interface {
|
||||||
unregister()
|
unregister()
|
||||||
drain()
|
drain() int
|
||||||
}{n.reqs, n.votes, n.prop, n.entry, n.resp, n.apply}
|
}{n.reqs, n.votes, n.prop, n.entry, n.resp, n.apply}
|
||||||
for _, q := range queues {
|
for _, q := range queues {
|
||||||
q.drain()
|
q.drain()
|
||||||
@@ -3853,6 +3861,10 @@ func (n *raft) setWriteErrLocked(err error) {
|
|||||||
n.error("Critical write error: %v", err)
|
n.error("Critical write error: %v", err)
|
||||||
n.werr = err
|
n.werr = err
|
||||||
|
|
||||||
|
if isPermissionError(err) {
|
||||||
|
go n.s.handleWritePermissionError()
|
||||||
|
}
|
||||||
|
|
||||||
if isOutOfSpaceErr(err) {
|
if isOutOfSpaceErr(err) {
|
||||||
// For now since this can be happening all under the covers, we will call up and disable JetStream.
|
// For now since this can be happening all under the covers, we will call up and disable JetStream.
|
||||||
go n.s.handleOutOfSpace(nil)
|
go n.s.handleOutOfSpace(nil)
|
||||||
|
|||||||
+9
-11
@@ -1348,8 +1348,6 @@ func (c *client) processRemoteUnsub(arg []byte, leafUnsub bool) (err error) {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
updateGWs := false
|
|
||||||
|
|
||||||
_keya := [128]byte{}
|
_keya := [128]byte{}
|
||||||
_key := _keya[:0]
|
_key := _keya[:0]
|
||||||
|
|
||||||
@@ -1373,19 +1371,21 @@ func (c *client) processRemoteUnsub(arg []byte, leafUnsub bool) (err error) {
|
|||||||
if ok {
|
if ok {
|
||||||
delete(c.subs, key)
|
delete(c.subs, key)
|
||||||
acc.sl.Remove(sub)
|
acc.sl.Remove(sub)
|
||||||
updateGWs = srv.gateway.enabled
|
|
||||||
if len(sub.queue) > 0 {
|
if len(sub.queue) > 0 {
|
||||||
delta = sub.qw
|
delta = sub.qw
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
c.mu.Unlock()
|
c.mu.Unlock()
|
||||||
|
|
||||||
if updateGWs {
|
// Update gateways and leaf nodes only if the subscription was found.
|
||||||
srv.gatewayUpdateSubInterest(accountName, sub, -delta)
|
if ok {
|
||||||
}
|
if srv.gateway.enabled {
|
||||||
|
srv.gatewayUpdateSubInterest(accountName, sub, -delta)
|
||||||
|
}
|
||||||
|
|
||||||
// Now check on leafnode updates.
|
// Now check on leafnode updates.
|
||||||
acc.updateLeafNodes(sub, -delta)
|
acc.updateLeafNodes(sub, -delta)
|
||||||
|
}
|
||||||
|
|
||||||
if c.opts.Verbose {
|
if c.opts.Verbose {
|
||||||
c.sendOK()
|
c.sendOK()
|
||||||
@@ -1600,7 +1600,6 @@ func (c *client) processRemoteSub(argo []byte, hasOrigin bool) (err error) {
|
|||||||
// We use the sub.sid for the key of the c.subs map.
|
// We use the sub.sid for the key of the c.subs map.
|
||||||
key := bytesToString(sub.sid)
|
key := bytesToString(sub.sid)
|
||||||
osub := c.subs[key]
|
osub := c.subs[key]
|
||||||
updateGWs := false
|
|
||||||
if osub == nil {
|
if osub == nil {
|
||||||
c.subs[key] = sub
|
c.subs[key] = sub
|
||||||
// Now place into the account sl.
|
// Now place into the account sl.
|
||||||
@@ -1611,7 +1610,6 @@ func (c *client) processRemoteSub(argo []byte, hasOrigin bool) (err error) {
|
|||||||
c.sendErr("Invalid Subscription")
|
c.sendErr("Invalid Subscription")
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
updateGWs = srv.gateway.enabled
|
|
||||||
} else if sub.queue != nil {
|
} else if sub.queue != nil {
|
||||||
// For a queue we need to update the weight.
|
// For a queue we need to update the weight.
|
||||||
delta = sub.qw - atomic.LoadInt32(&osub.qw)
|
delta = sub.qw - atomic.LoadInt32(&osub.qw)
|
||||||
@@ -1620,7 +1618,7 @@ func (c *client) processRemoteSub(argo []byte, hasOrigin bool) (err error) {
|
|||||||
}
|
}
|
||||||
c.mu.Unlock()
|
c.mu.Unlock()
|
||||||
|
|
||||||
if updateGWs {
|
if srv.gateway.enabled {
|
||||||
srv.gatewayUpdateSubInterest(acc.Name, sub, delta)
|
srv.gatewayUpdateSubInterest(acc.Name, sub, delta)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+7
@@ -18,6 +18,7 @@ import (
|
|||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
|
"os"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
"unsafe"
|
"unsafe"
|
||||||
@@ -166,6 +167,8 @@ type SimpleState struct {
|
|||||||
|
|
||||||
// Internal usage for when the first needs to be updated before use.
|
// Internal usage for when the first needs to be updated before use.
|
||||||
firstNeedsUpdate bool
|
firstNeedsUpdate bool
|
||||||
|
// Internal usage for when the last needs to be updated before use.
|
||||||
|
lastNeedsUpdate bool
|
||||||
}
|
}
|
||||||
|
|
||||||
// LostStreamData indicates msgs that have been lost.
|
// LostStreamData indicates msgs that have been lost.
|
||||||
@@ -778,3 +781,7 @@ func copyString(s string) string {
|
|||||||
copy(b, s)
|
copy(b, s)
|
||||||
return bytesToString(b)
|
return bytesToString(b)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func isPermissionError(err error) bool {
|
||||||
|
return err != nil && os.IsPermission(err)
|
||||||
|
}
|
||||||
|
|||||||
+32
-12
@@ -1039,10 +1039,10 @@ func (mset *stream) lastSeq() uint64 {
|
|||||||
return mset.lseq
|
return mset.lseq
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Set last seq.
|
||||||
|
// Write lock should be held.
|
||||||
func (mset *stream) setLastSeq(lseq uint64) {
|
func (mset *stream) setLastSeq(lseq uint64) {
|
||||||
mset.mu.Lock()
|
|
||||||
mset.lseq = lseq
|
mset.lseq = lseq
|
||||||
mset.mu.Unlock()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (mset *stream) sendCreateAdvisory() {
|
func (mset *stream) sendCreateAdvisory() {
|
||||||
@@ -2051,11 +2051,16 @@ func (mset *stream) purge(preq *JSApiStreamPurgeRequest) (purged uint64, err err
|
|||||||
store.FastState(&state)
|
store.FastState(&state)
|
||||||
fseq, lseq := state.FirstSeq, state.LastSeq
|
fseq, lseq := state.FirstSeq, state.LastSeq
|
||||||
|
|
||||||
|
mset.mu.Lock()
|
||||||
// Check if our last has moved past what our original last sequence was, if so reset.
|
// Check if our last has moved past what our original last sequence was, if so reset.
|
||||||
if lseq > mlseq {
|
if lseq > mlseq {
|
||||||
mset.setLastSeq(lseq)
|
mset.setLastSeq(lseq)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Clear any pending acks below first seq.
|
||||||
|
mset.clearAllPreAcksBelowFloor(fseq)
|
||||||
|
mset.mu.Unlock()
|
||||||
|
|
||||||
// Purge consumers.
|
// Purge consumers.
|
||||||
// Check for filtered purge.
|
// Check for filtered purge.
|
||||||
if preq != nil && preq.Subject != _EMPTY_ {
|
if preq != nil && preq.Subject != _EMPTY_ {
|
||||||
@@ -2102,7 +2107,14 @@ func (mset *stream) deleteMsg(seq uint64) (bool, error) {
|
|||||||
if mset.closed.Load() {
|
if mset.closed.Load() {
|
||||||
return false, errStreamClosed
|
return false, errStreamClosed
|
||||||
}
|
}
|
||||||
return mset.store.RemoveMsg(seq)
|
removed, err := mset.store.RemoveMsg(seq)
|
||||||
|
if err != nil {
|
||||||
|
return removed, err
|
||||||
|
}
|
||||||
|
mset.mu.Lock()
|
||||||
|
mset.clearAllPreAcks(seq)
|
||||||
|
mset.mu.Unlock()
|
||||||
|
return removed, err
|
||||||
}
|
}
|
||||||
|
|
||||||
// EraseMsg will securely remove a message and rewrite the data with random data.
|
// EraseMsg will securely remove a message and rewrite the data with random data.
|
||||||
@@ -2110,7 +2122,14 @@ func (mset *stream) eraseMsg(seq uint64) (bool, error) {
|
|||||||
if mset.closed.Load() {
|
if mset.closed.Load() {
|
||||||
return false, errStreamClosed
|
return false, errStreamClosed
|
||||||
}
|
}
|
||||||
return mset.store.EraseMsg(seq)
|
removed, err := mset.store.EraseMsg(seq)
|
||||||
|
if err != nil {
|
||||||
|
return removed, err
|
||||||
|
}
|
||||||
|
mset.mu.Lock()
|
||||||
|
mset.clearAllPreAcks(seq)
|
||||||
|
mset.mu.Unlock()
|
||||||
|
return removed, err
|
||||||
}
|
}
|
||||||
|
|
||||||
// Are we a mirror?
|
// Are we a mirror?
|
||||||
@@ -4000,15 +4019,8 @@ func (mset *stream) purgeMsgIds() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// storeMsgId will store the message id for duplicate detection.
|
|
||||||
func (mset *stream) storeMsgId(dde *ddentry) {
|
|
||||||
mset.mu.Lock()
|
|
||||||
defer mset.mu.Unlock()
|
|
||||||
mset.storeMsgIdLocked(dde)
|
|
||||||
}
|
|
||||||
|
|
||||||
// storeMsgIdLocked will store the message id for duplicate detection.
|
// storeMsgIdLocked will store the message id for duplicate detection.
|
||||||
// Lock should he held.
|
// Lock should be held.
|
||||||
func (mset *stream) storeMsgIdLocked(dde *ddentry) {
|
func (mset *stream) storeMsgIdLocked(dde *ddentry) {
|
||||||
if mset.ddmap == nil {
|
if mset.ddmap == nil {
|
||||||
mset.ddmap = make(map[string]*ddentry)
|
mset.ddmap = make(map[string]*ddentry)
|
||||||
@@ -4628,6 +4640,14 @@ func (mset *stream) processJetStreamMsg(subject, reply string, hdr, msg []byte,
|
|||||||
}
|
}
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
if isPermissionError(err) {
|
||||||
|
mset.mu.Unlock()
|
||||||
|
// messages in block cache could be lost in the worst case.
|
||||||
|
// In the clustered mode it is very highly unlikely as a result of replication.
|
||||||
|
mset.srv.DisableJetStream()
|
||||||
|
mset.srv.Warnf("Filesystem permission denied while writing msg, disabling JetStream: %v", err)
|
||||||
|
return err
|
||||||
|
}
|
||||||
// If we did not succeed put those values back and increment clfs in case we are clustered.
|
// If we did not succeed put those values back and increment clfs in case we are clustered.
|
||||||
var state StreamState
|
var state StreamState
|
||||||
mset.store.FastState(&state)
|
mset.store.FastState(&state)
|
||||||
|
|||||||
Vendored
+2
-2
@@ -984,7 +984,7 @@ github.com/munnerz/goautoneg
|
|||||||
# github.com/nats-io/jwt/v2 v2.7.3
|
# github.com/nats-io/jwt/v2 v2.7.3
|
||||||
## explicit; go 1.22
|
## explicit; go 1.22
|
||||||
github.com/nats-io/jwt/v2
|
github.com/nats-io/jwt/v2
|
||||||
# github.com/nats-io/nats-server/v2 v2.10.24
|
# github.com/nats-io/nats-server/v2 v2.10.25
|
||||||
## explicit; go 1.22
|
## explicit; go 1.22
|
||||||
github.com/nats-io/nats-server/v2/conf
|
github.com/nats-io/nats-server/v2/conf
|
||||||
github.com/nats-io/nats-server/v2/internal/fastrand
|
github.com/nats-io/nats-server/v2/internal/fastrand
|
||||||
@@ -2208,7 +2208,7 @@ 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.8.0
|
# golang.org/x/time v0.9.0
|
||||||
## explicit; go 1.18
|
## explicit; go 1.18
|
||||||
golang.org/x/time/rate
|
golang.org/x/time/rate
|
||||||
# golang.org/x/tools v0.28.0
|
# golang.org/x/tools v0.28.0
|
||||||
|
|||||||
Reference in New Issue
Block a user