7060 lines
196 KiB
Go
7060 lines
196 KiB
Go
// Copyright 2019-2026 The NATS Authors
|
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
|
// you may not use this file except in compliance with the License.
|
|
// You may obtain a copy of the License at
|
|
//
|
|
// http://www.apache.org/licenses/LICENSE-2.0
|
|
//
|
|
// Unless required by applicable law or agreed to in writing, software
|
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
// See the License for the specific language governing permissions and
|
|
// limitations under the License.
|
|
|
|
package server
|
|
|
|
import (
|
|
"bytes"
|
|
"encoding/binary"
|
|
"encoding/json"
|
|
"errors"
|
|
"fmt"
|
|
"math"
|
|
"math/rand"
|
|
"os"
|
|
"path/filepath"
|
|
"reflect"
|
|
"regexp"
|
|
"slices"
|
|
"strconv"
|
|
"strings"
|
|
"sync"
|
|
"sync/atomic"
|
|
"time"
|
|
|
|
"github.com/nats-io/nats-server/v2/server/avl"
|
|
"github.com/nats-io/nats-server/v2/server/gsl"
|
|
"github.com/nats-io/nuid"
|
|
"golang.org/x/time/rate"
|
|
)
|
|
|
|
// Headers sent with Request Timeout
|
|
const (
|
|
JSPullRequestPendingMsgs = "Nats-Pending-Messages"
|
|
JSPullRequestPendingBytes = "Nats-Pending-Bytes"
|
|
JSPullRequestNatsPinId = "Nats-Pin-Id"
|
|
)
|
|
|
|
var (
|
|
validGroupName = regexp.MustCompile(`^[a-zA-Z0-9/_=-]{1,16}$`)
|
|
)
|
|
|
|
// Headers sent when batch size was completed, but there were remaining bytes.
|
|
const JsPullRequestRemainingBytesT = "NATS/1.0 409 Batch Completed\r\n%s: %d\r\n%s: %d\r\n\r\n"
|
|
|
|
type ConsumerInfo struct {
|
|
Stream string `json:"stream_name"`
|
|
Name string `json:"name"`
|
|
Created time.Time `json:"created"`
|
|
Config *ConsumerConfig `json:"config,omitempty"`
|
|
Delivered SequenceInfo `json:"delivered"`
|
|
AckFloor SequenceInfo `json:"ack_floor"`
|
|
NumAckPending int `json:"num_ack_pending"`
|
|
NumRedelivered int `json:"num_redelivered"`
|
|
NumWaiting int `json:"num_waiting"`
|
|
NumPending uint64 `json:"num_pending"`
|
|
Cluster *ClusterInfo `json:"cluster,omitempty"`
|
|
PushBound bool `json:"push_bound,omitempty"`
|
|
Paused bool `json:"paused,omitempty"`
|
|
PauseRemaining time.Duration `json:"pause_remaining,omitempty"`
|
|
// TimeStamp indicates when the info was gathered
|
|
TimeStamp time.Time `json:"ts"`
|
|
PriorityGroups []PriorityGroupState `json:"priority_groups,omitempty"`
|
|
}
|
|
|
|
// consumerInfoClusterResponse is a response used in a cluster to communicate the consumer info
|
|
// back to the meta leader as part of a consumer list request.
|
|
type consumerInfoClusterResponse struct {
|
|
ConsumerInfo
|
|
OfflineReason string `json:"offline_reason,omitempty"` // Reporting when a consumer is offline.
|
|
}
|
|
|
|
type PriorityGroupState struct {
|
|
Group string `json:"group"`
|
|
PinnedClientID string `json:"pinned_client_id,omitempty"`
|
|
PinnedTS time.Time `json:"pinned_ts,omitempty"`
|
|
}
|
|
|
|
type ConsumerConfig struct {
|
|
Durable string `json:"durable_name,omitempty"`
|
|
Name string `json:"name,omitempty"`
|
|
Description string `json:"description,omitempty"`
|
|
DeliverPolicy DeliverPolicy `json:"deliver_policy"`
|
|
OptStartSeq uint64 `json:"opt_start_seq,omitempty"`
|
|
OptStartTime *time.Time `json:"opt_start_time,omitempty"`
|
|
AckPolicy AckPolicy `json:"ack_policy"`
|
|
AckWait time.Duration `json:"ack_wait,omitempty"`
|
|
MaxDeliver int `json:"max_deliver,omitempty"`
|
|
BackOff []time.Duration `json:"backoff,omitempty"`
|
|
FilterSubject string `json:"filter_subject,omitempty"`
|
|
FilterSubjects []string `json:"filter_subjects,omitempty"`
|
|
ReplayPolicy ReplayPolicy `json:"replay_policy"`
|
|
RateLimit uint64 `json:"rate_limit_bps,omitempty"` // Bits per sec
|
|
SampleFrequency string `json:"sample_freq,omitempty"`
|
|
MaxWaiting int `json:"max_waiting,omitempty"`
|
|
MaxAckPending int `json:"max_ack_pending,omitempty"`
|
|
FlowControl bool `json:"flow_control,omitempty"`
|
|
HeadersOnly bool `json:"headers_only,omitempty"`
|
|
|
|
// Pull based options.
|
|
MaxRequestBatch int `json:"max_batch,omitempty"`
|
|
MaxRequestExpires time.Duration `json:"max_expires,omitempty"`
|
|
MaxRequestMaxBytes int `json:"max_bytes,omitempty"`
|
|
|
|
// Push based consumers.
|
|
DeliverSubject string `json:"deliver_subject,omitempty"`
|
|
DeliverGroup string `json:"deliver_group,omitempty"`
|
|
Heartbeat time.Duration `json:"idle_heartbeat,omitempty"`
|
|
|
|
// Ephemeral inactivity threshold.
|
|
InactiveThreshold time.Duration `json:"inactive_threshold,omitempty"`
|
|
|
|
// Generally inherited by parent stream and other markers, now can be configured directly.
|
|
Replicas int `json:"num_replicas"`
|
|
// Force memory storage.
|
|
MemoryStorage bool `json:"mem_storage,omitempty"`
|
|
|
|
// Don't add to general clients.
|
|
Direct bool `json:"direct,omitempty"`
|
|
Sourcing bool `json:"sourcing,omitempty"`
|
|
|
|
// Metadata is additional metadata for the Consumer.
|
|
Metadata map[string]string `json:"metadata,omitempty"`
|
|
|
|
// PauseUntil is for suspending the consumer until the deadline.
|
|
PauseUntil *time.Time `json:"pause_until,omitempty"`
|
|
|
|
// Priority groups
|
|
PriorityGroups []string `json:"priority_groups,omitempty"`
|
|
PriorityPolicy PriorityPolicy `json:"priority_policy,omitempty"`
|
|
PinnedTTL time.Duration `json:"priority_timeout,omitempty"`
|
|
}
|
|
|
|
// SequenceInfo has both the consumer and the stream sequence and last activity.
|
|
type SequenceInfo struct {
|
|
Consumer uint64 `json:"consumer_seq"`
|
|
Stream uint64 `json:"stream_seq"`
|
|
Last *time.Time `json:"last_active,omitempty"`
|
|
}
|
|
|
|
type CreateConsumerRequest struct {
|
|
Stream string `json:"stream_name"`
|
|
Config ConsumerConfig `json:"config"`
|
|
Action ConsumerAction `json:"action"`
|
|
Pedantic bool `json:"pedantic,omitempty"`
|
|
}
|
|
|
|
type ConsumerAction int
|
|
|
|
const (
|
|
ActionCreateOrUpdate ConsumerAction = iota
|
|
ActionUpdate
|
|
ActionCreate
|
|
)
|
|
|
|
const (
|
|
actionUpdateJSONString = `"update"`
|
|
actionCreateJSONString = `"create"`
|
|
actionCreateOrUpdateJSONString = `""`
|
|
)
|
|
|
|
var (
|
|
actionUpdateJSONBytes = []byte(actionUpdateJSONString)
|
|
actionCreateJSONBytes = []byte(actionCreateJSONString)
|
|
actionCreateOrUpdateJSONBytes = []byte(actionCreateOrUpdateJSONString)
|
|
)
|
|
|
|
func (a ConsumerAction) String() string {
|
|
switch a {
|
|
case ActionCreateOrUpdate:
|
|
return actionCreateOrUpdateJSONString
|
|
case ActionCreate:
|
|
return actionCreateJSONString
|
|
case ActionUpdate:
|
|
return actionUpdateJSONString
|
|
}
|
|
return actionCreateOrUpdateJSONString
|
|
}
|
|
|
|
func (a ConsumerAction) MarshalJSON() ([]byte, error) {
|
|
switch a {
|
|
case ActionCreate:
|
|
return actionCreateJSONBytes, nil
|
|
case ActionUpdate:
|
|
return actionUpdateJSONBytes, nil
|
|
case ActionCreateOrUpdate:
|
|
return actionCreateOrUpdateJSONBytes, nil
|
|
default:
|
|
return nil, fmt.Errorf("can not marshal %v", a)
|
|
}
|
|
}
|
|
|
|
func (a *ConsumerAction) UnmarshalJSON(data []byte) error {
|
|
switch string(data) {
|
|
case actionCreateJSONString:
|
|
*a = ActionCreate
|
|
case actionUpdateJSONString:
|
|
*a = ActionUpdate
|
|
case actionCreateOrUpdateJSONString:
|
|
*a = ActionCreateOrUpdate
|
|
default:
|
|
return fmt.Errorf("unknown consumer action: %v", string(data))
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// ConsumerNakOptions is for optional NAK values, e.g. delay.
|
|
type ConsumerNakOptions struct {
|
|
Delay time.Duration `json:"delay"`
|
|
}
|
|
|
|
// PriorityPolicy determines policy for selecting messages based on priority.
|
|
type PriorityPolicy int
|
|
|
|
const (
|
|
// No priority policy.
|
|
PriorityNone PriorityPolicy = iota
|
|
// Clients will get the messages only if certain criteria are specified.
|
|
PriorityOverflow
|
|
// Single client takes over handling of the messages, while others are on standby.
|
|
PriorityPinnedClient
|
|
// Clients with lowest priority will be selected first.
|
|
PriorityPrioritized
|
|
)
|
|
|
|
const (
|
|
PriorityNoneJSONString = `"none"`
|
|
PriorityOverflowJSONString = `"overflow"`
|
|
PriorityPinnedClientJSONString = `"pinned_client"`
|
|
PriorityPrioritizedJSONString = `"prioritized"`
|
|
)
|
|
|
|
var (
|
|
PriorityNoneJSONBytes = []byte(PriorityNoneJSONString)
|
|
PriorityOverflowJSONBytes = []byte(PriorityOverflowJSONString)
|
|
PriorityPinnedClientJSONBytes = []byte(PriorityPinnedClientJSONString)
|
|
PriorityPrioritizedJSONBytes = []byte(PriorityPrioritizedJSONString)
|
|
)
|
|
|
|
func (pp PriorityPolicy) String() string {
|
|
switch pp {
|
|
case PriorityOverflow:
|
|
return PriorityOverflowJSONString
|
|
case PriorityPinnedClient:
|
|
return PriorityPinnedClientJSONString
|
|
case PriorityPrioritized:
|
|
return PriorityPrioritizedJSONString
|
|
default:
|
|
return PriorityNoneJSONString
|
|
}
|
|
}
|
|
|
|
func (pp PriorityPolicy) MarshalJSON() ([]byte, error) {
|
|
switch pp {
|
|
case PriorityOverflow:
|
|
return PriorityOverflowJSONBytes, nil
|
|
case PriorityPinnedClient:
|
|
return PriorityPinnedClientJSONBytes, nil
|
|
case PriorityPrioritized:
|
|
return PriorityPrioritizedJSONBytes, nil
|
|
case PriorityNone:
|
|
return PriorityNoneJSONBytes, nil
|
|
default:
|
|
return nil, fmt.Errorf("unknown priority policy: %v", pp)
|
|
}
|
|
}
|
|
|
|
func (pp *PriorityPolicy) UnmarshalJSON(data []byte) error {
|
|
switch string(data) {
|
|
case PriorityOverflowJSONString:
|
|
*pp = PriorityOverflow
|
|
case PriorityPinnedClientJSONString:
|
|
*pp = PriorityPinnedClient
|
|
case PriorityPrioritizedJSONString:
|
|
*pp = PriorityPrioritized
|
|
case PriorityNoneJSONString:
|
|
*pp = PriorityNone
|
|
default:
|
|
return fmt.Errorf("unknown priority policy: %v", string(data))
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// DeliverPolicy determines how the consumer should select the first message to deliver.
|
|
type DeliverPolicy int
|
|
|
|
const (
|
|
// DeliverAll will be the default so can be omitted from the request.
|
|
DeliverAll DeliverPolicy = iota
|
|
// DeliverLast will start the consumer with the last sequence received.
|
|
DeliverLast
|
|
// DeliverNew will only deliver new messages that are sent after the consumer is created.
|
|
DeliverNew
|
|
// DeliverByStartSequence will look for a defined starting sequence to start.
|
|
DeliverByStartSequence
|
|
// DeliverByStartTime will select the first messsage with a timestamp >= to StartTime.
|
|
DeliverByStartTime
|
|
// DeliverLastPerSubject will start the consumer with the last message for all subjects received.
|
|
DeliverLastPerSubject
|
|
)
|
|
|
|
func (dp DeliverPolicy) String() string {
|
|
switch dp {
|
|
case DeliverAll:
|
|
return "all"
|
|
case DeliverLast:
|
|
return "last"
|
|
case DeliverNew:
|
|
return "new"
|
|
case DeliverByStartSequence:
|
|
return "by_start_sequence"
|
|
case DeliverByStartTime:
|
|
return "by_start_time"
|
|
case DeliverLastPerSubject:
|
|
return "last_per_subject"
|
|
default:
|
|
return "undefined"
|
|
}
|
|
}
|
|
|
|
// AckPolicy determines how the consumer should acknowledge delivered messages.
|
|
type AckPolicy int
|
|
|
|
const (
|
|
// AckNone requires no acks for delivered messages.
|
|
AckNone AckPolicy = iota
|
|
// AckAll when acking a sequence number, this implicitly acks all sequences below this one as well.
|
|
AckAll
|
|
// AckExplicit requires ack or nack for all messages.
|
|
AckExplicit
|
|
// AckFlowControl functions like AckAll, but acks based on responses to flow control.
|
|
AckFlowControl
|
|
)
|
|
|
|
func (a AckPolicy) String() string {
|
|
switch a {
|
|
case AckNone:
|
|
return "none"
|
|
case AckAll:
|
|
return "all"
|
|
case AckFlowControl:
|
|
return "flow_control"
|
|
default:
|
|
return "explicit"
|
|
}
|
|
}
|
|
|
|
// ReplayPolicy determines how the consumer should replay messages it already has queued in the stream.
|
|
type ReplayPolicy int
|
|
|
|
const (
|
|
// ReplayInstant will replay messages as fast as possible.
|
|
ReplayInstant ReplayPolicy = iota
|
|
// ReplayOriginal will maintain the same timing as the messages were received.
|
|
ReplayOriginal
|
|
)
|
|
|
|
func (r ReplayPolicy) String() string {
|
|
switch r {
|
|
case ReplayInstant:
|
|
return replayInstantPolicyJSONString
|
|
default:
|
|
return replayOriginalPolicyJSONString
|
|
}
|
|
}
|
|
|
|
// OK
|
|
const OK = "+OK"
|
|
|
|
// Ack responses. Note that a nil or no payload is same as AckAck
|
|
var (
|
|
// Ack
|
|
AckAck = []byte("+ACK") // nil or no payload to ack subject also means ACK
|
|
AckOK = []byte(OK) // deprecated but +OK meant ack as well.
|
|
|
|
// Nack
|
|
AckNak = []byte("-NAK")
|
|
// Progress indicator
|
|
AckProgress = []byte("+WPI")
|
|
// Ack + Deliver the next message(s).
|
|
AckNext = []byte("+NXT")
|
|
// Terminate delivery of the message.
|
|
AckTerm = []byte("+TERM")
|
|
)
|
|
|
|
const (
|
|
// reasons to supply when terminating messages using limits
|
|
ackTermLimitsReason = "Message deleted by stream limits"
|
|
ackTermUnackedLimitsReason = "Unacknowledged message was deleted"
|
|
)
|
|
|
|
// Calculate accurate replicas for the consumer config with the parent stream config.
|
|
func (consCfg ConsumerConfig) replicas(strCfg *StreamConfig) int {
|
|
if consCfg.Replicas == 0 || consCfg.Replicas > strCfg.Replicas {
|
|
if !isDurableConsumer(&consCfg) && strCfg.Retention == LimitsPolicy && consCfg.Replicas == 0 {
|
|
// Matches old-school ephemerals only, where the replica count is 0.
|
|
return 1
|
|
}
|
|
return strCfg.Replicas
|
|
}
|
|
return consCfg.Replicas
|
|
}
|
|
|
|
// Consumer is a jetstream consumer.
|
|
type consumer struct {
|
|
// Atomic used to notify that we want to process an ack.
|
|
// This will be checked in checkPending to abort processing
|
|
// and let ack be processed in priority.
|
|
awl int64
|
|
leader atomic.Bool
|
|
mu sync.RWMutex
|
|
js *jetStream
|
|
mset *stream
|
|
acc *Account
|
|
srv *Server
|
|
client *client
|
|
sysc *client
|
|
sid int
|
|
name string
|
|
stream string
|
|
sseq uint64 // next stream sequence
|
|
subjf subjectFilters // subject filters and their sequences
|
|
filters *gsl.SimpleSublist // When we have multiple filters we will use LoadNextMsgMulti and pass this in.
|
|
dseq uint64 // delivered consumer sequence
|
|
adflr uint64 // ack delivery floor
|
|
asflr uint64 // ack store floor
|
|
chkflr uint64 // our check floor, interest streams only.
|
|
npc int64 // Num Pending Count
|
|
npf uint64 // Num Pending Floor Sequence
|
|
dsubj string
|
|
qgroup string
|
|
lss *lastSeqSkipList
|
|
rlimit *rate.Limiter
|
|
reqSub *subscription
|
|
resetSub *subscription
|
|
ackSubOld *subscription
|
|
ackReplyOldT string
|
|
ackSubjOld string
|
|
ackSub *subscription
|
|
ackReplyT string
|
|
ackSubj string
|
|
fcPreOld string
|
|
fcSubjOld string
|
|
fcPre string
|
|
fcSubj string
|
|
nextMsgSubj string
|
|
nextMsgReqs *ipQueue[*nextMsgReq]
|
|
resetSubj string
|
|
maxp int
|
|
pblimit int
|
|
maxpb int
|
|
pbytes int
|
|
fcsz int
|
|
fcid string
|
|
fcSubOld *subscription
|
|
fcSub *subscription
|
|
outq *jsOutQ
|
|
pending map[uint64]*Pending
|
|
ptmr *time.Timer
|
|
ptmrEnd time.Time
|
|
rdq []uint64
|
|
rdqi avl.SequenceSet
|
|
rdc map[uint64]uint64
|
|
replies map[uint64]string
|
|
pendingDeliveries map[uint64]*jsPubMsg // Messages that can be delivered after achieving quorum.
|
|
waitingDeliveries map[string]*waitingDelivery // (Optional) request timeout messages that need to wait for replicated deliveries first.
|
|
maxdc uint64
|
|
waiting *waitQueue
|
|
cfg ConsumerConfig
|
|
ici *ConsumerInfo
|
|
store ConsumerStore
|
|
active bool
|
|
replay bool
|
|
useV2Ack bool
|
|
dtmr *time.Timer
|
|
uptmr *time.Timer // Unpause timer
|
|
gwdtmr *time.Timer
|
|
dthresh time.Duration
|
|
mch chan struct{} // Message channel
|
|
qch chan struct{} // Quit channel
|
|
mqch chan struct{} // The monitor's quit channel.
|
|
inch chan bool // Interest change channel
|
|
sfreq int32
|
|
ackEventT string
|
|
nakEventT string
|
|
deliveryExcEventT string
|
|
created time.Time
|
|
ldt time.Time
|
|
lat time.Time
|
|
lwqic time.Time
|
|
closed bool
|
|
|
|
// Clustered.
|
|
ca *consumerAssignment
|
|
node RaftNode
|
|
infoSub *subscription
|
|
lqsent time.Time
|
|
prm map[string]struct{}
|
|
rsm map[string]bool // Reset requests that need to be responded to on the internal sys account (if true).
|
|
prOk bool
|
|
uch chan struct{}
|
|
retention RetentionPolicy
|
|
|
|
monitorWg sync.WaitGroup
|
|
inMonitor bool
|
|
|
|
// R>1 proposals
|
|
pch chan struct{}
|
|
phead *proposal
|
|
ptail *proposal
|
|
|
|
// Ack queue
|
|
ackMsgs *ipQueue[*jsAckMsg]
|
|
|
|
// for stream signaling when multiple filters are set.
|
|
sigSubs []string
|
|
|
|
// Priority groups
|
|
// Details described in ADR-42.
|
|
|
|
// currentPinId is the current nuid for the pinned consumer.
|
|
// If the Consumer is running in `PriorityPinnedClient` mode, server will
|
|
// pick up a new nuid and assign it to first pending pull request.
|
|
currentPinId string
|
|
/// pinnedTtl is the remaining time before the current PinId expires.
|
|
pinnedTtl *time.Timer
|
|
pinnedTS time.Time
|
|
|
|
// If standalone/single-server, the offline reason needs to be stored directly in the consumer.
|
|
// Otherwise, if clustered it will be part of the consumer assignment.
|
|
offlineReason string
|
|
}
|
|
|
|
// A single subject filter.
|
|
type subjectFilter struct {
|
|
subject string
|
|
tokenizedSubject []string
|
|
hasWildcard bool
|
|
}
|
|
|
|
type subjectFilters []*subjectFilter
|
|
|
|
// subjects is a helper function used for updating consumers.
|
|
// It is not used and should not be used in hotpath.
|
|
func (s subjectFilters) subjects() []string {
|
|
subjects := make([]string, 0, len(s))
|
|
for _, filter := range s {
|
|
subjects = append(subjects, filter.subject)
|
|
}
|
|
return subjects
|
|
}
|
|
|
|
type proposal struct {
|
|
data []byte
|
|
next *proposal
|
|
}
|
|
|
|
const (
|
|
// JsAckWaitDefault is the default AckWait, only applicable on explicit ack policy consumers.
|
|
JsAckWaitDefault = 30 * time.Second
|
|
// JsDeleteWaitTimeDefault is the default amount of time we will wait for non-durable
|
|
// consumers to be in an inactive state before deleting them.
|
|
JsDeleteWaitTimeDefault = 5 * time.Second
|
|
// JsFlowControlMaxPending specifies default pending bytes during flow control that can be outstanding.
|
|
JsFlowControlMaxPending = 32 * 1024 * 1024
|
|
// JsDefaultMaxAckPending is set for consumers with explicit ack that do not set the max ack pending.
|
|
JsDefaultMaxAckPending = 1000
|
|
// JsDefaultPinnedTTL is the default grace period for the pinned consumer to send a new request before a new pin
|
|
// is picked by a server.
|
|
JsDefaultPinnedTTL = 2 * time.Minute
|
|
)
|
|
|
|
// Helper function to set consumer config defaults from above.
|
|
func setConsumerConfigDefaults(config *ConsumerConfig, streamCfg *StreamConfig, lim *JSLimitOpts, accLim *JetStreamAccountLimits, pedantic bool) *ApiError {
|
|
// Setup default of -1, meaning no limit for MaxDeliver.
|
|
if config.MaxDeliver == 0 || config.MaxDeliver < -1 {
|
|
if pedantic && config.MaxDeliver < -1 {
|
|
return NewJSPedanticError(errors.New("max_deliver must be set to -1"))
|
|
}
|
|
config.MaxDeliver = -1
|
|
}
|
|
// Setup zero defaults.
|
|
if config.MaxWaiting < 0 {
|
|
if pedantic {
|
|
return NewJSPedanticError(errors.New("max_waiting must not be negative"))
|
|
}
|
|
config.MaxWaiting = 0
|
|
}
|
|
if config.MaxAckPending < -1 {
|
|
if pedantic {
|
|
return NewJSPedanticError(errors.New("max_ack_pending must be set to -1"))
|
|
}
|
|
config.MaxAckPending = -1
|
|
}
|
|
if config.MaxRequestBatch < 0 {
|
|
if pedantic {
|
|
return NewJSPedanticError(errors.New("max_batch must not be negative"))
|
|
}
|
|
config.MaxRequestBatch = 0
|
|
}
|
|
if config.MaxRequestExpires < 0 {
|
|
if pedantic {
|
|
return NewJSPedanticError(errors.New("max_expires must not be negative"))
|
|
}
|
|
config.MaxRequestExpires = 0
|
|
}
|
|
if config.MaxRequestMaxBytes < 0 {
|
|
if pedantic {
|
|
return NewJSPedanticError(errors.New("max_bytes must not be negative"))
|
|
}
|
|
config.MaxRequestMaxBytes = 0
|
|
}
|
|
if config.Heartbeat < 0 {
|
|
if pedantic {
|
|
return NewJSPedanticError(errors.New("idle_heartbeat must not be negative"))
|
|
}
|
|
config.Heartbeat = 0
|
|
}
|
|
if config.InactiveThreshold < 0 {
|
|
if pedantic {
|
|
return NewJSPedanticError(errors.New("inactive_threshold must not be negative"))
|
|
}
|
|
config.InactiveThreshold = 0
|
|
}
|
|
if config.PinnedTTL < 0 {
|
|
if pedantic {
|
|
return NewJSPedanticError(errors.New("priority_timeout must not be negative"))
|
|
}
|
|
config.PinnedTTL = 0
|
|
}
|
|
|
|
// Set to default if not specified.
|
|
if config.DeliverSubject == _EMPTY_ && config.MaxWaiting == 0 {
|
|
config.MaxWaiting = JSWaitQueueDefaultMax
|
|
}
|
|
// Setup proper default for ack wait if we are in explicit ack mode.
|
|
if config.AckWait == 0 && (config.AckPolicy == AckExplicit || config.AckPolicy == AckAll) {
|
|
config.AckWait = JsAckWaitDefault
|
|
}
|
|
// If BackOff was specified that will override the AckWait and the MaxDeliver.
|
|
if len(config.BackOff) > 0 {
|
|
if pedantic && config.AckWait != config.BackOff[0] {
|
|
return NewJSPedanticError(errors.New("first backoff value has to equal batch AckWait"))
|
|
}
|
|
config.AckWait = config.BackOff[0]
|
|
}
|
|
if config.MaxAckPending == 0 {
|
|
if pedantic && streamCfg.ConsumerLimits.MaxAckPending > 0 {
|
|
return NewJSPedanticError(errors.New("max_ack_pending must be set if it's configured in stream limits"))
|
|
}
|
|
config.MaxAckPending = streamCfg.ConsumerLimits.MaxAckPending
|
|
}
|
|
if config.InactiveThreshold == 0 {
|
|
if pedantic && streamCfg.ConsumerLimits.InactiveThreshold > 0 {
|
|
return NewJSPedanticError(errors.New("inactive_threshold must be set if it's configured in stream limits"))
|
|
}
|
|
config.InactiveThreshold = streamCfg.ConsumerLimits.InactiveThreshold
|
|
}
|
|
// Set proper default for max ack pending if we are ack explicit and none has been set.
|
|
if config.MaxAckPending == 0 && config.AckPolicy != AckNone {
|
|
ackPending := JsDefaultMaxAckPending
|
|
if lim.MaxAckPending > 0 && lim.MaxAckPending < ackPending {
|
|
ackPending = lim.MaxAckPending
|
|
}
|
|
if accLim.MaxAckPending > 0 && accLim.MaxAckPending < ackPending {
|
|
ackPending = accLim.MaxAckPending
|
|
}
|
|
config.MaxAckPending = ackPending
|
|
}
|
|
// if applicable set max request batch size
|
|
if config.DeliverSubject == _EMPTY_ && config.MaxRequestBatch == 0 && lim.MaxRequestBatch > 0 {
|
|
if pedantic {
|
|
return NewJSPedanticError(errors.New("max_request_batch must be set if it's JetStream limits are set"))
|
|
}
|
|
config.MaxRequestBatch = lim.MaxRequestBatch
|
|
}
|
|
|
|
// set the default value only if pinned policy is used.
|
|
if config.PriorityPolicy == PriorityPinnedClient && config.PinnedTTL == 0 {
|
|
config.PinnedTTL = JsDefaultPinnedTTL
|
|
}
|
|
|
|
// Set default values for flow control policy.
|
|
if config.AckPolicy == AckFlowControl && !pedantic {
|
|
config.FlowControl = true
|
|
config.Heartbeat = sourceHealthHB
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// Check the consumer config. If we are recovering don't check filter subjects.
|
|
func checkConsumerCfg(
|
|
config *ConsumerConfig,
|
|
srvLim *JSLimitOpts,
|
|
cfg *StreamConfig,
|
|
_ *Account,
|
|
accLim *JetStreamAccountLimits,
|
|
isRecovering bool,
|
|
) *ApiError {
|
|
|
|
if config.Name != _EMPTY_ && !isValidAssetName(config.Name) {
|
|
return NewJSStreamInvalidConfigError(errors.New("consumer name can not contain '.', '*', '>', '\\', '/'"))
|
|
}
|
|
if config.Durable != _EMPTY_ && !isValidAssetName(config.Durable) {
|
|
return NewJSStreamInvalidConfigError(errors.New("consumer durable name can not contain '.', '*', '>', '\\', '/'"))
|
|
}
|
|
|
|
// Check if replicas is defined but exceeds parent stream.
|
|
if config.Replicas > 0 && config.Replicas > cfg.Replicas {
|
|
return NewJSConsumerReplicasExceedsStreamError()
|
|
}
|
|
// Check that it is not negative
|
|
if config.Replicas < 0 {
|
|
return NewJSReplicasCountCannotBeNegativeError()
|
|
}
|
|
// If the stream is interest or workqueue retention make sure the replicas
|
|
// match that of the stream. This is REQUIRED for now.
|
|
if cfg.Retention == InterestPolicy || cfg.Retention == WorkQueuePolicy {
|
|
// Only error here if not recovering.
|
|
// We handle recovering in a different spot to allow consumer to come up
|
|
// if previous version allowed it to be created. We do not want it to not come up.
|
|
if !isRecovering && config.Replicas != 0 && config.Replicas != cfg.Replicas {
|
|
return NewJSConsumerReplicasShouldMatchStreamError()
|
|
}
|
|
}
|
|
|
|
if _, err := config.AckPolicy.MarshalJSON(); err != nil {
|
|
return NewJSConsumerAckPolicyInvalidError()
|
|
}
|
|
if _, err := config.ReplayPolicy.MarshalJSON(); err != nil {
|
|
return NewJSConsumerReplayPolicyInvalidError()
|
|
}
|
|
|
|
// Check not negative AckWait/BackOff
|
|
for _, backoff := range config.BackOff {
|
|
if backoff < 0 {
|
|
return NewJSConsumerBackOffNegativeError()
|
|
}
|
|
}
|
|
if config.AckWait < 0 {
|
|
return NewJSConsumerAckWaitNegativeError()
|
|
}
|
|
|
|
// Ack Flow Control policy requires push-based flow-controlled consumer.
|
|
if config.AckPolicy == AckFlowControl {
|
|
if config.DeliverSubject == _EMPTY_ {
|
|
return NewJSConsumerAckFCRequiresPushError()
|
|
}
|
|
if !config.FlowControl {
|
|
return NewJSConsumerAckFCRequiresFCError()
|
|
}
|
|
// We currently limit using heartbeat of 1s, since those are used for ephemeral sourcing consumers as well.
|
|
// We could decide to relax this in the future, but need to be careful to not allow a heartbeat larger
|
|
// than the stalled source timeout.
|
|
if config.Heartbeat != sourceHealthHB {
|
|
return NewJSStreamInvalidConfigError(fmt.Errorf("flow control ack policy heartbeat needs to be 1s"))
|
|
}
|
|
if config.MaxAckPending <= 0 {
|
|
return NewJSConsumerAckFCRequiresMaxAckPendingError()
|
|
}
|
|
if config.AckWait != 0 || len(config.BackOff) > 0 {
|
|
return NewJSConsumerAckFCRequiresNoAckWaitError()
|
|
}
|
|
if config.MaxDeliver > 0 {
|
|
return NewJSConsumerAckFCRequiresNoMaxDeliverError()
|
|
}
|
|
}
|
|
|
|
// Check if we have a BackOff defined that MaxDeliver is within range etc.
|
|
if lbo := len(config.BackOff); lbo > 0 && config.MaxDeliver != -1 && lbo > config.MaxDeliver {
|
|
return NewJSConsumerMaxDeliverBackoffError()
|
|
}
|
|
|
|
if len(config.Description) > JSMaxDescriptionLen {
|
|
return NewJSConsumerDescriptionTooLongError(JSMaxDescriptionLen)
|
|
}
|
|
|
|
// For now expect a literal subject if its not empty. Empty means work queue mode (pull mode).
|
|
if config.DeliverSubject != _EMPTY_ {
|
|
if !subjectIsLiteral(config.DeliverSubject) {
|
|
return NewJSConsumerDeliverToWildcardsError()
|
|
}
|
|
if !IsValidSubject(config.DeliverSubject) {
|
|
return NewJSConsumerInvalidDeliverSubjectError()
|
|
}
|
|
if deliveryFormsCycle(cfg, config.DeliverSubject) {
|
|
return NewJSConsumerDeliverCycleError()
|
|
}
|
|
if config.MaxWaiting != 0 {
|
|
return NewJSConsumerPushMaxWaitingError()
|
|
}
|
|
if config.MaxAckPending > 0 && config.AckPolicy == AckNone {
|
|
return NewJSConsumerMaxPendingAckPolicyRequiredError()
|
|
}
|
|
if config.Heartbeat > 0 && config.Heartbeat < 100*time.Millisecond {
|
|
return NewJSConsumerSmallHeartbeatError()
|
|
}
|
|
} else {
|
|
// Pull mode with work queue retention from the stream requires an explicit ack.
|
|
if config.AckPolicy == AckNone && cfg.Retention == WorkQueuePolicy {
|
|
return NewJSConsumerPullRequiresAckError()
|
|
}
|
|
if config.RateLimit > 0 {
|
|
return NewJSConsumerPullWithRateLimitError()
|
|
}
|
|
if config.MaxWaiting < 0 {
|
|
return NewJSConsumerMaxWaitingNegativeError()
|
|
}
|
|
if config.Heartbeat > 0 {
|
|
return NewJSConsumerHBRequiresPushError()
|
|
}
|
|
if config.FlowControl {
|
|
return NewJSConsumerFCRequiresPushError()
|
|
}
|
|
if config.MaxRequestBatch < 0 {
|
|
return NewJSConsumerMaxRequestBatchNegativeError()
|
|
}
|
|
if config.MaxRequestExpires != 0 && config.MaxRequestExpires < time.Millisecond {
|
|
return NewJSConsumerMaxRequestExpiresTooSmallError()
|
|
}
|
|
if srvLim.MaxRequestBatch > 0 && config.MaxRequestBatch > srvLim.MaxRequestBatch {
|
|
return NewJSConsumerMaxRequestBatchExceededError(srvLim.MaxRequestBatch)
|
|
}
|
|
}
|
|
if srvLim.MaxAckPending > 0 && config.MaxAckPending > srvLim.MaxAckPending {
|
|
return NewJSConsumerMaxPendingAckExcessError(srvLim.MaxAckPending)
|
|
}
|
|
if accLim.MaxAckPending > 0 && config.MaxAckPending > accLim.MaxAckPending {
|
|
return NewJSConsumerMaxPendingAckExcessError(accLim.MaxAckPending)
|
|
}
|
|
if cfg.ConsumerLimits.MaxAckPending > 0 && config.MaxAckPending > cfg.ConsumerLimits.MaxAckPending {
|
|
return NewJSConsumerMaxPendingAckExcessError(cfg.ConsumerLimits.MaxAckPending)
|
|
}
|
|
if cfg.ConsumerLimits.InactiveThreshold > 0 && config.InactiveThreshold > cfg.ConsumerLimits.InactiveThreshold {
|
|
return NewJSConsumerInactiveThresholdExcessError(cfg.ConsumerLimits.InactiveThreshold)
|
|
}
|
|
|
|
// Direct need to be non-mapped ephemerals.
|
|
if config.Direct {
|
|
if config.DeliverSubject == _EMPTY_ {
|
|
return NewJSConsumerDirectRequiresPushError()
|
|
}
|
|
if isDurableConsumer(config) {
|
|
return NewJSConsumerDirectRequiresEphemeralError()
|
|
}
|
|
}
|
|
|
|
// Do not allow specifying both FilterSubject and FilterSubjects,
|
|
// as that's probably unintentional without any difference from passing
|
|
// all filters in FilterSubjects.
|
|
if config.FilterSubject != _EMPTY_ && len(config.FilterSubjects) > 0 {
|
|
return NewJSConsumerDuplicateFilterSubjectsError()
|
|
}
|
|
|
|
if config.FilterSubject != _EMPTY_ && !IsValidSubject(config.FilterSubject) {
|
|
return NewJSStreamInvalidConfigError(ErrBadSubject)
|
|
}
|
|
|
|
// We treat FilterSubjects: []string{""} as a misconfig, so we validate against it.
|
|
for _, filter := range config.FilterSubjects {
|
|
if filter == _EMPTY_ {
|
|
return NewJSConsumerEmptyFilterError()
|
|
}
|
|
}
|
|
subjectFilters := gatherSubjectFilters(config.FilterSubject, config.FilterSubjects)
|
|
|
|
// Check subject filters do not overlap.
|
|
for outer, subject := range subjectFilters {
|
|
if !IsValidSubject(subject) {
|
|
return NewJSStreamInvalidConfigError(ErrBadSubject)
|
|
}
|
|
for inner, ssubject := range subjectFilters {
|
|
if inner != outer && subjectIsSubsetMatch(subject, ssubject) {
|
|
return NewJSConsumerOverlappingSubjectFiltersError()
|
|
}
|
|
}
|
|
}
|
|
|
|
// Helper function to formulate similar errors.
|
|
badStart := func(dp, start string) error {
|
|
return fmt.Errorf("consumer delivery policy is deliver %s, but optional start %s is also set", dp, start)
|
|
}
|
|
notSet := func(dp, notSet string) error {
|
|
return fmt.Errorf("consumer delivery policy is deliver %s, but optional %s is not set", dp, notSet)
|
|
}
|
|
|
|
// Check on start position conflicts.
|
|
switch config.DeliverPolicy {
|
|
case DeliverAll:
|
|
if config.OptStartSeq > 0 {
|
|
return NewJSConsumerInvalidPolicyError(badStart("all", "sequence"))
|
|
}
|
|
if config.OptStartTime != nil {
|
|
return NewJSConsumerInvalidPolicyError(badStart("all", "time"))
|
|
}
|
|
case DeliverLast:
|
|
if config.OptStartSeq > 0 {
|
|
return NewJSConsumerInvalidPolicyError(badStart("last", "sequence"))
|
|
}
|
|
if config.OptStartTime != nil {
|
|
return NewJSConsumerInvalidPolicyError(badStart("last", "time"))
|
|
}
|
|
case DeliverLastPerSubject:
|
|
if config.OptStartSeq > 0 {
|
|
return NewJSConsumerInvalidPolicyError(badStart("last per subject", "sequence"))
|
|
}
|
|
if config.OptStartTime != nil {
|
|
return NewJSConsumerInvalidPolicyError(badStart("last per subject", "time"))
|
|
}
|
|
if config.FilterSubject == _EMPTY_ && len(config.FilterSubjects) == 0 {
|
|
return NewJSConsumerInvalidPolicyError(notSet("last per subject", "filter subject"))
|
|
}
|
|
case DeliverNew:
|
|
if config.OptStartSeq > 0 {
|
|
return NewJSConsumerInvalidPolicyError(badStart("new", "sequence"))
|
|
}
|
|
if config.OptStartTime != nil {
|
|
return NewJSConsumerInvalidPolicyError(badStart("new", "time"))
|
|
}
|
|
case DeliverByStartSequence:
|
|
if config.OptStartSeq == 0 {
|
|
return NewJSConsumerInvalidPolicyError(notSet("by start sequence", "start sequence"))
|
|
}
|
|
if config.OptStartTime != nil {
|
|
return NewJSConsumerInvalidPolicyError(badStart("by start sequence", "time"))
|
|
}
|
|
case DeliverByStartTime:
|
|
if config.OptStartTime == nil {
|
|
return NewJSConsumerInvalidPolicyError(notSet("by start time", "start time"))
|
|
}
|
|
if config.OptStartSeq != 0 {
|
|
return NewJSConsumerInvalidPolicyError(badStart("by start time", "start sequence"))
|
|
}
|
|
}
|
|
|
|
if config.SampleFrequency != _EMPTY_ {
|
|
s := strings.TrimSuffix(config.SampleFrequency, "%")
|
|
if sampleFreq, err := strconv.Atoi(s); err != nil || sampleFreq < 0 {
|
|
return NewJSConsumerInvalidSamplingError(err)
|
|
}
|
|
}
|
|
|
|
// We reject if flow control is set without heartbeats.
|
|
if config.FlowControl && config.Heartbeat == 0 {
|
|
return NewJSConsumerWithFlowControlNeedsHeartbeatsError()
|
|
}
|
|
|
|
if config.Durable != _EMPTY_ && config.Name != _EMPTY_ {
|
|
if config.Name != config.Durable {
|
|
return NewJSConsumerCreateDurableAndNameMismatchError()
|
|
}
|
|
}
|
|
|
|
var metadataLen int
|
|
for k, v := range config.Metadata {
|
|
metadataLen += len(k) + len(v)
|
|
}
|
|
if metadataLen > JSMaxMetadataLen {
|
|
return NewJSConsumerMetadataLengthError(fmt.Sprintf("%dKB", JSMaxMetadataLen/1024))
|
|
}
|
|
|
|
if config.PriorityPolicy != PriorityNone {
|
|
if config.DeliverSubject != "" {
|
|
return NewJSConsumerPushWithPriorityGroupError()
|
|
}
|
|
if len(config.PriorityGroups) == 0 {
|
|
return NewJSConsumerPriorityPolicyWithoutGroupError()
|
|
}
|
|
|
|
for _, group := range config.PriorityGroups {
|
|
if group == _EMPTY_ {
|
|
return NewJSConsumerEmptyGroupNameError()
|
|
}
|
|
if !validGroupName.MatchString(group) {
|
|
return NewJSConsumerInvalidGroupNameError()
|
|
}
|
|
}
|
|
} else {
|
|
// If PriorityPolicy is None or not set, reject if PriorityGroups or PinnedTTL are set
|
|
if len(config.PriorityGroups) > 0 {
|
|
return NewJSConsumerPriorityGroupWithPolicyNoneError()
|
|
}
|
|
if config.PinnedTTL > 0 {
|
|
return NewJSConsumerPinnedTTLWithoutPriorityPolicyNoneError()
|
|
}
|
|
}
|
|
|
|
// For now don't allow preferred server in placement.
|
|
if cfg.Placement != nil && cfg.Placement.Preferred != _EMPTY_ {
|
|
return NewJSStreamInvalidConfigError(fmt.Errorf("preferred server not permitted in placement"))
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func (mset *stream) addConsumerWithAction(config *ConsumerConfig, action ConsumerAction, pedantic bool) (*consumer, error) {
|
|
return mset.addConsumerWithAssignment(config, _EMPTY_, nil, false, action, pedantic)
|
|
}
|
|
|
|
func (mset *stream) addConsumer(config *ConsumerConfig) (*consumer, error) {
|
|
return mset.addConsumerWithAction(config, ActionCreateOrUpdate, false)
|
|
}
|
|
|
|
func (mset *stream) addConsumerWithAssignment(config *ConsumerConfig, oname string, ca *consumerAssignment, isRecovering bool, action ConsumerAction, pedantic bool) (*consumer, error) {
|
|
// Check if this stream has closed.
|
|
if mset.closed.Load() {
|
|
return nil, NewJSStreamInvalidError()
|
|
}
|
|
|
|
mset.mu.RLock()
|
|
s, js, jsa, cfg, acc, lseq := mset.srv, mset.js, mset.jsa, mset.cfg, mset.acc, mset.lseq
|
|
mset.mu.RUnlock()
|
|
|
|
// If we do not have the consumer currently assigned to us in cluster mode we will proceed but warn.
|
|
// This can happen on startup with restored state where on meta replay we still do not have
|
|
// the assignment. Running in single server mode this always returns true.
|
|
if oname != _EMPTY_ && !jsa.consumerAssigned(mset.name(), oname) {
|
|
s.Debugf("Consumer %q > %q does not seem to be assigned to this server", mset.name(), oname)
|
|
}
|
|
|
|
if config == nil {
|
|
return nil, NewJSConsumerConfigRequiredError()
|
|
}
|
|
|
|
selectedLimits, _, _, _ := acc.selectLimits(config.replicas(&cfg))
|
|
if selectedLimits == nil {
|
|
return nil, NewJSNoLimitsError()
|
|
}
|
|
|
|
srvLim := &s.getOpts().JetStreamLimits
|
|
// Make sure we have sane defaults. Do so with the JS lock, otherwise a
|
|
// badly timed meta snapshot can result in a race condition.
|
|
mset.js.mu.Lock()
|
|
err := setConsumerConfigDefaults(config, &cfg, srvLim, selectedLimits, pedantic)
|
|
mset.js.mu.Unlock()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
if err := checkConsumerCfg(config, srvLim, &cfg, acc, selectedLimits, isRecovering); err != nil {
|
|
return nil, err
|
|
}
|
|
sampleFreq := 0
|
|
if config.SampleFrequency != _EMPTY_ {
|
|
// Can't fail as checkConsumerCfg checks correct format
|
|
sampleFreq, _ = strconv.Atoi(strings.TrimSuffix(config.SampleFrequency, "%"))
|
|
}
|
|
|
|
// Grab the client, account and server reference.
|
|
c := mset.client
|
|
if c == nil {
|
|
return nil, NewJSStreamInvalidError()
|
|
}
|
|
var accName string
|
|
c.mu.Lock()
|
|
s, a := c.srv, c.acc
|
|
if a != nil {
|
|
accName = a.Name
|
|
}
|
|
c.mu.Unlock()
|
|
|
|
// Hold mset lock here.
|
|
mset.mu.Lock()
|
|
if mset.client == nil || mset.store == nil || mset.consumers == nil {
|
|
mset.mu.Unlock()
|
|
return nil, NewJSStreamInvalidError()
|
|
}
|
|
|
|
// If this one is durable and already exists, we let that be ok as long as only updating what should be allowed.
|
|
var cName string
|
|
if isDurableConsumer(config) {
|
|
cName = config.Durable
|
|
} else if config.Name != _EMPTY_ {
|
|
cName = config.Name
|
|
}
|
|
if cName != _EMPTY_ {
|
|
if eo, ok := mset.consumers[cName]; ok {
|
|
if action == ActionCreate {
|
|
ocfg := eo.config()
|
|
copyConsumerMetadata(config, &ocfg)
|
|
if !reflect.DeepEqual(config, &ocfg) {
|
|
mset.mu.Unlock()
|
|
return nil, NewJSConsumerAlreadyExistsError()
|
|
}
|
|
}
|
|
// Check for overlapping subjects if we are a workqueue
|
|
if cfg.Retention == WorkQueuePolicy {
|
|
subjects := gatherSubjectFilters(config.FilterSubject, config.FilterSubjects)
|
|
if !mset.partitionUnique(cName, subjects) {
|
|
mset.mu.Unlock()
|
|
return nil, NewJSConsumerWQConsumerNotUniqueError()
|
|
}
|
|
}
|
|
mset.mu.Unlock()
|
|
err := eo.updateConfig(config)
|
|
if err == nil {
|
|
return eo, nil
|
|
}
|
|
return nil, NewJSConsumerCreateError(err, Unless(err))
|
|
}
|
|
}
|
|
if action == ActionUpdate {
|
|
mset.mu.Unlock()
|
|
return nil, NewJSConsumerDoesNotExistError()
|
|
}
|
|
|
|
standalone := !s.JetStreamIsClustered() && s.standAloneMode()
|
|
|
|
// If we're clustered we've already done this check, only do this if we're a standalone server.
|
|
// But if we're standalone, only enforce if we're not recovering, since the MaxConsumers could've
|
|
// been updated while we already had more consumers on disk.
|
|
if standalone && !isRecovering {
|
|
// Check for any limits, if the config for the consumer sets a limit we check against that
|
|
// but if not we use the value from account limits, if account limits is more restrictive
|
|
// than stream config we prefer the account limits to handle cases where account limits are
|
|
// updated during the lifecycle of the stream
|
|
maxc := cfg.MaxConsumers
|
|
if maxc <= 0 || (selectedLimits.MaxConsumers > 0 && selectedLimits.MaxConsumers < maxc) {
|
|
maxc = selectedLimits.MaxConsumers
|
|
}
|
|
if maxc > 0 && mset.numLimitableConsumers() >= maxc {
|
|
mset.mu.Unlock()
|
|
return nil, NewJSMaximumConsumersLimitError()
|
|
}
|
|
}
|
|
|
|
// Check on stream type conflicts with WorkQueues.
|
|
if cfg.Retention == WorkQueuePolicy && !config.Direct && !config.Sourcing {
|
|
// Force explicit acks here.
|
|
if config.AckPolicy != AckExplicit && config.AckPolicy != AckFlowControl {
|
|
mset.mu.Unlock()
|
|
return nil, NewJSConsumerWQRequiresExplicitAckError()
|
|
}
|
|
|
|
if mset.numLimitableConsumers() > 0 {
|
|
subjects := gatherSubjectFilters(config.FilterSubject, config.FilterSubjects)
|
|
if len(subjects) == 0 {
|
|
mset.mu.Unlock()
|
|
return nil, NewJSConsumerWQMultipleUnfilteredError()
|
|
} else if !mset.partitionUnique(cName, subjects) {
|
|
// Prior to v2.9.7, on a stream with WorkQueue policy, the servers
|
|
// were not catching the error of having multiple consumers with
|
|
// overlapping filter subjects depending on the scope, for instance
|
|
// creating "foo.*.bar" and then "foo.>" was not detected, while
|
|
// "foo.>" and then "foo.*.bar" would have been. Failing here
|
|
// in recovery mode would leave the rejected consumer in a bad state,
|
|
// so we will simply warn here, asking the user to remove this
|
|
// consumer administratively. Otherwise, if this is the creation
|
|
// of a new consumer, we will return the error.
|
|
if isRecovering {
|
|
s.Warnf("Consumer %q > %q has a filter subject that overlaps "+
|
|
"with other consumers, which is not allowed for a stream "+
|
|
"with WorkQueue policy, it should be administratively deleted",
|
|
cfg.Name, cName)
|
|
} else {
|
|
// We have a partition but it is not unique amongst the others.
|
|
mset.mu.Unlock()
|
|
return nil, NewJSConsumerWQConsumerNotUniqueError()
|
|
}
|
|
}
|
|
}
|
|
if config.DeliverPolicy != DeliverAll {
|
|
mset.mu.Unlock()
|
|
return nil, NewJSConsumerWQConsumerNotDeliverAllError()
|
|
}
|
|
}
|
|
|
|
// Set name, which will be durable name if set, otherwise we create one at random.
|
|
o := &consumer{
|
|
mset: mset,
|
|
js: s.getJetStream(),
|
|
acc: a,
|
|
srv: s,
|
|
client: s.createInternalJetStreamClient(),
|
|
sysc: s.createInternalJetStreamClient(),
|
|
cfg: *config,
|
|
dsubj: config.DeliverSubject,
|
|
outq: mset.outq,
|
|
active: true,
|
|
qch: make(chan struct{}),
|
|
mqch: make(chan struct{}),
|
|
uch: make(chan struct{}, 1),
|
|
mch: make(chan struct{}, 1),
|
|
sfreq: int32(sampleFreq),
|
|
maxdc: uint64(max(config.MaxDeliver, 0)), // MaxDeliver is negative (-1) when infinite.
|
|
maxp: config.MaxAckPending,
|
|
retention: cfg.Retention,
|
|
created: time.Now().UTC(),
|
|
}
|
|
|
|
// Add created timestamp used for the store, must match that of the consumer assignment if it exists.
|
|
if ca != nil {
|
|
js.mu.RLock()
|
|
o.created = ca.Created
|
|
js.mu.RUnlock()
|
|
}
|
|
|
|
// Bind internal client to the user account.
|
|
o.client.registerWithAccount(a)
|
|
// Bind to the system account.
|
|
o.sysc.registerWithAccount(s.SystemAccount())
|
|
|
|
if isDurableConsumer(config) {
|
|
if len(config.Durable) > JSMaxNameLen {
|
|
mset.mu.Unlock()
|
|
o.deleteWithoutAdvisory()
|
|
return nil, NewJSConsumerNameTooLongError(JSMaxNameLen)
|
|
}
|
|
o.name = config.Durable
|
|
} else if oname != _EMPTY_ {
|
|
o.name = oname
|
|
} else {
|
|
if config.Name != _EMPTY_ {
|
|
o.name = config.Name
|
|
} else {
|
|
// Legacy ephemeral auto-generated.
|
|
for {
|
|
o.name = createConsumerName()
|
|
if _, ok := mset.consumers[o.name]; !ok {
|
|
break
|
|
}
|
|
}
|
|
config.Name = o.name
|
|
}
|
|
}
|
|
// Create ackMsgs queue now that we have a consumer name
|
|
o.ackMsgs = newIPQueue[*jsAckMsg](s, fmt.Sprintf("[ACC:%s] consumer '%s' on stream '%s' ackMsgs", accName, o.name, cfg.Name))
|
|
|
|
// Create our request waiting queue.
|
|
if o.isPullMode() {
|
|
o.waiting = newWaitQueue(config.MaxWaiting)
|
|
// Create our internal queue for next msg requests.
|
|
o.nextMsgReqs = newIPQueue[*nextMsgReq](s, fmt.Sprintf("[ACC:%s] consumer '%s' on stream '%s' pull requests", accName, o.name, cfg.Name))
|
|
}
|
|
|
|
// already under lock, mset.Name() would deadlock
|
|
o.stream = cfg.Name
|
|
o.ackEventT = JSMetricConsumerAckPre + "." + o.stream + "." + o.name
|
|
o.nakEventT = JSAdvisoryConsumerMsgNakPre + "." + o.stream + "." + o.name
|
|
o.deliveryExcEventT = JSAdvisoryConsumerMaxDeliveryExceedPre + "." + o.stream + "." + o.name
|
|
|
|
if !isValidAssetName(o.name) {
|
|
mset.mu.Unlock()
|
|
o.deleteWithoutAdvisory()
|
|
return nil, NewJSConsumerBadDurableNameError()
|
|
}
|
|
|
|
// Setup our storage if not a direct consumer.
|
|
if !config.Direct {
|
|
store, err := mset.store.ConsumerStore(o.name, o.created, config)
|
|
if err != nil {
|
|
mset.mu.Unlock()
|
|
o.deleteWithoutAdvisory()
|
|
return nil, NewJSConsumerStoreFailedError(err)
|
|
}
|
|
o.store = store
|
|
}
|
|
|
|
for _, filter := range gatherSubjectFilters(o.cfg.FilterSubject, o.cfg.FilterSubjects) {
|
|
sub := &subjectFilter{
|
|
subject: filter,
|
|
hasWildcard: subjectHasWildcard(filter),
|
|
tokenizedSubject: tokenizeSubjectIntoSlice(nil, filter),
|
|
}
|
|
o.subjf = append(o.subjf, sub)
|
|
}
|
|
|
|
// If we have multiple filter subjects, create a sublist which we will use
|
|
// in calling store.LoadNextMsgMulti.
|
|
if len(o.subjf) <= 1 {
|
|
o.filters = nil
|
|
} else {
|
|
o.filters = gsl.NewSublist[struct{}]()
|
|
for _, filter := range o.subjf {
|
|
o.filters.Insert(filter.subject, struct{}{})
|
|
}
|
|
}
|
|
|
|
if o.store != nil && o.store.HasState() {
|
|
// Restore our saved state.
|
|
o.mu.Lock()
|
|
o.readStoredState()
|
|
|
|
replicas := o.cfg.replicas(&mset.cfg)
|
|
|
|
// Starting sequence represents the next sequence to be delivered, so decrement it
|
|
// since that's the minimum amount the stream should have as its last sequence.
|
|
sseq := o.sseq
|
|
if sseq > 0 {
|
|
sseq--
|
|
}
|
|
|
|
o.mu.Unlock()
|
|
|
|
// A stream observing data loss rolls back in its sequence. Check if we need to reconcile the consumer state
|
|
// to ensure new messages aren't skipped.
|
|
// Only performed for non-replicated consumers for now.
|
|
if replicas == 1 && lseq < sseq && isRecovering {
|
|
s.Warnf("JetStream consumer '%s > %s > %s' delivered sequence %d past last stream sequence of %d",
|
|
o.acc.Name, o.stream, o.name, sseq, lseq)
|
|
|
|
o.mu.Lock()
|
|
o.reconcileStateWithStream(lseq)
|
|
|
|
// Save the reconciled state
|
|
state := &ConsumerState{
|
|
Delivered: SequencePair{
|
|
Stream: o.sseq - 1,
|
|
Consumer: o.dseq - 1,
|
|
},
|
|
AckFloor: SequencePair{
|
|
Stream: o.asflr,
|
|
Consumer: o.adflr,
|
|
},
|
|
Pending: o.pending,
|
|
Redelivered: o.rdc,
|
|
}
|
|
err := o.store.ForceUpdate(state)
|
|
o.mu.Unlock()
|
|
if err != nil {
|
|
s.Errorf("JetStream consumer '%s > %s > %s' errored while updating state: %v", o.acc.Name, o.stream, o.name, err)
|
|
mset.mu.Unlock()
|
|
return nil, NewJSConsumerStoreFailedError(err)
|
|
}
|
|
}
|
|
} else if config.Direct || standalone {
|
|
// Clustered non-direct consumers defer this to setLeader so the
|
|
// expensive store scans don't block the meta apply goroutine.
|
|
if err := o.selectStartingSeqNo(); err != nil {
|
|
return nil, err
|
|
}
|
|
}
|
|
|
|
// Now register with mset and create the ack subscription.
|
|
// Check if we already have this one registered.
|
|
if eo, ok := mset.consumers[o.name]; ok {
|
|
mset.mu.Unlock()
|
|
if !o.isDurable() || !o.isPushMode() {
|
|
o.name = _EMPTY_ // Prevent removal since same name.
|
|
o.deleteWithoutAdvisory()
|
|
return nil, NewJSConsumerNameExistError()
|
|
}
|
|
// If we are here we have already registered this durable. If it is still active that is an error.
|
|
if eo.isActive() {
|
|
o.name = _EMPTY_ // Prevent removal since same name.
|
|
o.deleteWithoutAdvisory()
|
|
return nil, NewJSConsumerExistingActiveError()
|
|
}
|
|
// Since we are here this means we have a potentially new durable so we should update here.
|
|
// Check that configs are the same.
|
|
if !configsEqualSansDelivery(o.cfg, eo.cfg) {
|
|
o.name = _EMPTY_ // Prevent removal since same name.
|
|
o.deleteWithoutAdvisory()
|
|
return nil, NewJSConsumerReplacementWithDifferentNameError()
|
|
}
|
|
// Once we are here we have a replacement push-based durable.
|
|
eo.updateDeliverSubject(o.cfg.DeliverSubject)
|
|
return eo, nil
|
|
}
|
|
|
|
// Set up the ack subscription for this consumer. Will use wildcard for all acks.
|
|
// We will remember the template to generate replies with sequence numbers and use
|
|
// that to scanf them back in.
|
|
// Escape '%' in consumer and stream names, as `pre` is used as a template later
|
|
// in consumer.ackReply(), resulting in erroneous formatting of the ack subject.
|
|
mn := strings.ReplaceAll(cfg.Name, "%", "%%")
|
|
on := strings.ReplaceAll(o.name, "%", "%%")
|
|
domain := strings.ReplaceAll(o.srv.getOpts().JetStreamDomain, "%", "%%")
|
|
if domain == _EMPTY_ {
|
|
domain = "_"
|
|
}
|
|
accHash := getHash(accName)
|
|
|
|
o.useV2Ack = s.getOpts().getFeatureFlag(FeatureFlagJsAckFormatV2)
|
|
|
|
// v1 format: $JS.(ACK|FC).<stream>.<consumer>.etc.
|
|
o.fcPreOld = jsFlowControlPre
|
|
o.fcSubjOld = fmt.Sprintf(jsFlowControl, cfg.Name, o.name)
|
|
preOld := fmt.Sprintf(jsAckT, mn, on)
|
|
o.ackReplyOldT = fmt.Sprintf("%s.%%d.%%d.%%d.%%d.%%d", preOld)
|
|
o.ackSubjOld = fmt.Sprintf("%s.*.*.*.*.*", preOld)
|
|
|
|
// v2 format: $JS.(ACK|FC).<domain>.<accHash>.<stream>.<consumer>.etc.
|
|
o.fcPre = fmt.Sprintf("%s%s.%s.", jsFlowControlPre, domain, accHash)
|
|
o.fcSubj = fmt.Sprintf(jsFlowControlV2, domain, accHash, cfg.Name, o.name)
|
|
pre := fmt.Sprintf(jsAckTv2, domain, accHash, mn, on)
|
|
o.ackReplyT = fmt.Sprintf("%s.%%d.%%d.%%d.%%d.%%d", pre)
|
|
// Subscribe on this ack subject for v2, we require 11 tokens, but allow for more tokens/extension.
|
|
o.ackSubj = fmt.Sprintf("%s.*.*.*.*.>", pre)
|
|
|
|
o.nextMsgSubj = fmt.Sprintf(JSApiRequestNextT, mn, o.name)
|
|
o.resetSubj = fmt.Sprintf(JSApiConsumerResetT, mn, o.name)
|
|
|
|
// Check/update the inactive threshold
|
|
o.updateInactiveThreshold(&o.cfg)
|
|
|
|
if o.isPushMode() {
|
|
// Check if we are running only 1 replica and that the delivery subject has interest.
|
|
// Check in place here for interest. Will setup properly in setLeader.
|
|
if config.replicas(&cfg) == 1 {
|
|
interest := o.acc.sl.HasInterest(o.cfg.DeliverSubject)
|
|
if !o.hasDeliveryInterest(interest) {
|
|
// Let the interest come to us eventually, but setup delete timer.
|
|
o.updateDeliveryInterest(false)
|
|
}
|
|
}
|
|
}
|
|
|
|
// Set our ca.
|
|
if ca != nil {
|
|
o.setConsumerAssignment(ca)
|
|
}
|
|
|
|
// Check if we have a rate limit set.
|
|
if config.RateLimit != 0 {
|
|
o.setRateLimit(config.RateLimit)
|
|
}
|
|
|
|
mset.setConsumer(o)
|
|
mset.mu.Unlock()
|
|
|
|
if config.Sourcing && standalone {
|
|
o.resetStartingSeq(0, _EMPTY_, false)
|
|
}
|
|
if config.Direct || standalone {
|
|
o.setLeader(true)
|
|
}
|
|
|
|
// This is always true in single server mode.
|
|
if o.IsLeader() {
|
|
// Send advisory.
|
|
var suppress bool
|
|
if !s.standAloneMode() && ca == nil {
|
|
suppress = true
|
|
} else if ca != nil {
|
|
suppress = ca.hasResponded()
|
|
}
|
|
if !suppress {
|
|
o.sendCreateAdvisory()
|
|
}
|
|
}
|
|
|
|
return o, nil
|
|
}
|
|
|
|
// Updates the consumer `dthresh` delete timer duration and set
|
|
// cfg.InactiveThreshold to JsDeleteWaitTimeDefault for ephemerals
|
|
// if not explicitly already specified by the user.
|
|
// Lock should be held.
|
|
func (o *consumer) updateInactiveThreshold(cfg *ConsumerConfig) {
|
|
// Ephemerals will always have inactive thresholds.
|
|
if !o.isDurable() && cfg.InactiveThreshold <= 0 {
|
|
// Add in 1 sec of jitter above and beyond the default of 5s.
|
|
o.dthresh = JsDeleteWaitTimeDefault + 100*time.Millisecond + time.Duration(rand.Int63n(900))*time.Millisecond
|
|
// Only stamp config with default sans jitter.
|
|
cfg.InactiveThreshold = JsDeleteWaitTimeDefault
|
|
} else if cfg.InactiveThreshold > 0 {
|
|
// Add in up to 1 sec of jitter if pull mode.
|
|
if o.isPullMode() {
|
|
o.dthresh = cfg.InactiveThreshold + 100*time.Millisecond + time.Duration(rand.Int63n(900))*time.Millisecond
|
|
} else {
|
|
o.dthresh = cfg.InactiveThreshold
|
|
}
|
|
} else if cfg.InactiveThreshold <= 0 {
|
|
// We accept InactiveThreshold be set to 0 (for durables)
|
|
o.dthresh = 0
|
|
}
|
|
}
|
|
|
|
// Updates the paused state. If we are the leader and the pause deadline
|
|
// hasn't passed yet then we will start a timer to kick the consumer once
|
|
// that deadline is reached. Lock should be held.
|
|
func (o *consumer) updatePauseState(cfg *ConsumerConfig) {
|
|
if o.uptmr != nil {
|
|
stopAndClearTimer(&o.uptmr)
|
|
}
|
|
if !o.isLeader() {
|
|
// Only the leader will run the timer as only the leader will run
|
|
// loopAndGatherMsgs.
|
|
return
|
|
}
|
|
if cfg.PauseUntil == nil || cfg.PauseUntil.IsZero() || cfg.PauseUntil.Before(time.Now()) {
|
|
// Either the PauseUntil is unset (is effectively zero) or the
|
|
// deadline has already passed, in which case there is nothing
|
|
// to do.
|
|
return
|
|
}
|
|
o.uptmr = time.AfterFunc(time.Until(*cfg.PauseUntil), func() {
|
|
o.mu.Lock()
|
|
defer o.mu.Unlock()
|
|
|
|
stopAndClearTimer(&o.uptmr)
|
|
o.sendPauseAdvisoryLocked(&o.cfg)
|
|
o.signalNewMessages()
|
|
})
|
|
}
|
|
|
|
func (o *consumer) consumerAssignment() *consumerAssignment {
|
|
o.mu.RLock()
|
|
defer o.mu.RUnlock()
|
|
return o.ca
|
|
}
|
|
|
|
func (o *consumer) setConsumerAssignment(ca *consumerAssignment) {
|
|
o.mu.Lock()
|
|
defer o.mu.Unlock()
|
|
|
|
o.ca = ca
|
|
if ca == nil {
|
|
return
|
|
}
|
|
// Set our node.
|
|
o.node = ca.Group.node
|
|
|
|
// Trigger update chan.
|
|
select {
|
|
case o.uch <- struct{}{}:
|
|
default:
|
|
}
|
|
}
|
|
|
|
func (o *consumer) monitorQuitC() <-chan struct{} {
|
|
if o == nil {
|
|
return nil
|
|
}
|
|
o.mu.Lock()
|
|
defer o.mu.Unlock()
|
|
// Recreate if a prior monitor routine was stopped.
|
|
if o.mqch == nil {
|
|
o.mqch = make(chan struct{})
|
|
}
|
|
return o.mqch
|
|
}
|
|
|
|
// signalMonitorQuit signals to exit the monitor loop. If there's no Raft node,
|
|
// this will be the only way to stop the monitor goroutine.
|
|
func (o *consumer) signalMonitorQuit() {
|
|
o.mu.Lock()
|
|
defer o.mu.Unlock()
|
|
if o.mqch != nil {
|
|
close(o.mqch)
|
|
o.mqch = nil
|
|
}
|
|
}
|
|
|
|
func (o *consumer) updateC() <-chan struct{} {
|
|
o.mu.RLock()
|
|
defer o.mu.RUnlock()
|
|
return o.uch
|
|
}
|
|
|
|
// checkQueueInterest will check on our interest's queue group status.
|
|
// Lock should be held.
|
|
func (o *consumer) checkQueueInterest() {
|
|
if !o.active || o.cfg.DeliverSubject == _EMPTY_ {
|
|
return
|
|
}
|
|
subj := o.dsubj
|
|
if subj == _EMPTY_ {
|
|
subj = o.cfg.DeliverSubject
|
|
}
|
|
|
|
if rr := o.acc.sl.Match(subj); len(rr.qsubs) > 0 {
|
|
// Just grab first
|
|
if qsubs := rr.qsubs[0]; len(qsubs) > 0 {
|
|
if sub := rr.qsubs[0][0]; len(sub.queue) > 0 {
|
|
o.qgroup = string(sub.queue)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// clears our node if we have one. When we scale down to 1.
|
|
func (o *consumer) clearNode() {
|
|
o.mu.Lock()
|
|
defer o.mu.Unlock()
|
|
if o.node != nil {
|
|
o.node.Delete()
|
|
o.node = nil
|
|
}
|
|
}
|
|
|
|
// IsLeader will return if we are the current leader.
|
|
func (o *consumer) IsLeader() bool {
|
|
return o.isLeader()
|
|
}
|
|
|
|
// Lock should be held.
|
|
func (o *consumer) isLeader() bool {
|
|
return o.leader.Load()
|
|
}
|
|
|
|
func (o *consumer) setLeader(isLeader bool) error {
|
|
o.mu.RLock()
|
|
mset, closed := o.mset, o.closed
|
|
movingToClustered := o.node != nil && o.pch == nil
|
|
movingToNonClustered := o.node == nil && o.pch != nil
|
|
wasLeader := o.leader.Swap(isLeader)
|
|
|
|
// For clustered new consumers, starting seq selection was deferred from
|
|
// addConsumerWithAssignment so the scan wouldn't block the meta apply
|
|
// goroutine, run it here on leader-elect instead.
|
|
needsSelect := isLeader && !wasLeader && o.dseq == 0 && (o.store == nil || !o.store.HasState())
|
|
o.mu.RUnlock()
|
|
|
|
// If we are here we have a change in leader status.
|
|
if isLeader {
|
|
if closed || mset == nil {
|
|
return nil
|
|
}
|
|
|
|
if needsSelect {
|
|
o.mu.Lock()
|
|
if err := o.selectStartingSeqNo(); err != nil {
|
|
o.srv.Errorf("JetStream consumer '%s > %s > %s' select starting seq failed: %v",
|
|
o.acc.Name, o.stream, o.name, err)
|
|
o.leader.Store(false)
|
|
node := o.node
|
|
o.mu.Unlock()
|
|
if node != nil {
|
|
_ = node.StepDown()
|
|
}
|
|
return err
|
|
}
|
|
o.mu.Unlock()
|
|
}
|
|
|
|
if wasLeader {
|
|
// If we detect we are scaling up, make sure to create clustered routines and channels.
|
|
if movingToClustered {
|
|
o.mu.Lock()
|
|
// We are moving from R1 to clustered.
|
|
o.pch = make(chan struct{}, 1)
|
|
go o.loopAndForwardProposals(o.qch)
|
|
if o.phead != nil {
|
|
select {
|
|
case o.pch <- struct{}{}:
|
|
default:
|
|
}
|
|
}
|
|
o.mu.Unlock()
|
|
} else if movingToNonClustered {
|
|
// We are moving from clustered to non-clustered now.
|
|
// Set pch to nil so if we scale back up we will recreate the loopAndForward from above.
|
|
o.mu.Lock()
|
|
pch := o.pch
|
|
o.pch = nil
|
|
select {
|
|
case pch <- struct{}{}:
|
|
default:
|
|
}
|
|
o.mu.Unlock()
|
|
}
|
|
return nil
|
|
}
|
|
|
|
mset.mu.RLock()
|
|
s, jsa, stream := mset.srv, mset.jsa, mset.getCfgName()
|
|
mset.mu.RUnlock()
|
|
|
|
o.mu.Lock()
|
|
o.rdq = nil
|
|
o.rdqi.Empty()
|
|
|
|
// Restore our saved state.
|
|
// During non-leader status we just update our underlying store when not clustered.
|
|
// If clustered we need to propose our initial (possibly skipped ahead) o.sseq to the group.
|
|
if o.node == nil || o.dseq > 1 || (o.store != nil && o.store.HasState()) {
|
|
o.readStoredState()
|
|
} else if o.node != nil && o.sseq >= 1 {
|
|
o.updateSkipped(o.sseq)
|
|
}
|
|
|
|
// Setup initial num pending.
|
|
o.streamNumPending()
|
|
|
|
// Cleanup lss when we take over in clustered mode.
|
|
if o.hasSkipListPending() && o.sseq >= o.lss.resume {
|
|
o.lss = nil
|
|
}
|
|
|
|
// Do info sub.
|
|
if o.infoSub == nil && jsa != nil {
|
|
isubj := fmt.Sprintf(clusterConsumerInfoT, jsa.acc(), stream, o.name)
|
|
// Note below the way we subscribe here is so that we can send requests to ourselves.
|
|
o.infoSub, _ = s.systemSubscribe(isubj, _EMPTY_, false, o.sysc, o.handleClusterConsumerInfoRequest)
|
|
}
|
|
|
|
var err error
|
|
if o.cfg.AckPolicy != AckNone && o.cfg.AckPolicy != AckFlowControl {
|
|
if o.ackSubOld, err = o.subscribeInternal(o.ackSubjOld, o.pushAck); err != nil {
|
|
o.mu.Unlock()
|
|
return nil
|
|
}
|
|
if o.ackSub, err = o.subscribeInternal(o.ackSubj, o.pushAck); err != nil {
|
|
o.mu.Unlock()
|
|
return nil
|
|
}
|
|
}
|
|
|
|
// Setup the internal sub for next message requests regardless.
|
|
// Will error if wrong mode to provide feedback to users.
|
|
if o.reqSub, err = o.subscribeInternal(o.nextMsgSubj, o.processNextMsgReq); err != nil {
|
|
o.mu.Unlock()
|
|
return nil
|
|
}
|
|
if o.resetSub, err = o.subscribeInternal(o.resetSubj, o.processResetReq); err != nil {
|
|
o.mu.Unlock()
|
|
return nil
|
|
}
|
|
|
|
// Check on flow control settings.
|
|
if o.cfg.FlowControl {
|
|
o.setMaxPendingBytes(JsFlowControlMaxPending)
|
|
if o.fcSubOld, err = o.subscribeInternal(o.fcSubjOld, o.processFlowControl); err != nil {
|
|
o.mu.Unlock()
|
|
return nil
|
|
}
|
|
if o.fcSub, err = o.subscribeInternal(o.fcSubj, o.processFlowControl); err != nil {
|
|
o.mu.Unlock()
|
|
return nil
|
|
}
|
|
}
|
|
|
|
// If push mode, register for notifications on interest.
|
|
if o.isPushMode() {
|
|
o.inch = make(chan bool, 8)
|
|
o.acc.sl.registerNotification(o.cfg.DeliverSubject, o.cfg.DeliverGroup, o.inch)
|
|
if o.active = <-o.inch; o.active {
|
|
o.checkQueueInterest()
|
|
}
|
|
|
|
// Check gateways in case they are enabled.
|
|
if s.gateway.enabled {
|
|
if !o.active {
|
|
o.active = s.hasGatewayInterest(o.acc.Name, o.cfg.DeliverSubject)
|
|
}
|
|
stopAndClearTimer(&o.gwdtmr)
|
|
o.gwdtmr = time.AfterFunc(time.Second, func() { o.watchGWinterest() })
|
|
}
|
|
}
|
|
|
|
if o.dthresh > 0 && (o.isPullMode() || !o.active) {
|
|
// Pull consumer. We run the dtmr all the time for this one.
|
|
stopAndClearTimer(&o.dtmr)
|
|
o.dtmr = time.AfterFunc(o.dthresh, o.deleteNotActive)
|
|
}
|
|
|
|
// Update the consumer pause tracking.
|
|
o.updatePauseState(&o.cfg)
|
|
|
|
// If we are not in ReplayInstant mode mark us as in replay state until resolved.
|
|
if o.cfg.ReplayPolicy != ReplayInstant {
|
|
o.replay = true
|
|
}
|
|
|
|
// Recreate quit channel.
|
|
o.qch = make(chan struct{})
|
|
qch := o.qch
|
|
node := o.node
|
|
if node != nil && o.pch == nil {
|
|
o.pch = make(chan struct{}, 1)
|
|
}
|
|
pullMode := o.isPullMode()
|
|
o.mu.Unlock()
|
|
|
|
// Check if there are any pending we might need to clean up etc.
|
|
o.checkPending()
|
|
|
|
// Snapshot initial info.
|
|
o.infoWithSnap(true)
|
|
|
|
// These are the labels we will use to annotate our goroutines.
|
|
labels := pprofLabels{
|
|
"type": "consumer",
|
|
"account": mset.accName(),
|
|
"stream": mset.name(),
|
|
"consumer": o.name,
|
|
}
|
|
|
|
// Now start up Go routine to deliver msgs.
|
|
go func() {
|
|
setGoRoutineLabels(labels)
|
|
o.loopAndGatherMsgs(qch)
|
|
}()
|
|
|
|
// Now start up Go routine to process acks.
|
|
go func() {
|
|
setGoRoutineLabels(labels)
|
|
o.processInboundAcks(qch)
|
|
}()
|
|
|
|
if pullMode {
|
|
// Now start up Go routine to process inbound next message requests.
|
|
go func() {
|
|
setGoRoutineLabels(labels)
|
|
o.processInboundNextMsgReqs(qch)
|
|
}()
|
|
}
|
|
|
|
// If we are R>1 spin up our proposal loop.
|
|
if node != nil {
|
|
// Determine if we can send pending requests info to the group.
|
|
// They must be on server versions >= 2.7.1
|
|
o.checkAndSetPendingRequestsOk()
|
|
o.checkPendingRequests()
|
|
go func() {
|
|
setGoRoutineLabels(labels)
|
|
o.loopAndForwardProposals(qch)
|
|
}()
|
|
}
|
|
|
|
} else {
|
|
// Shutdown the go routines and the subscriptions.
|
|
o.mu.Lock()
|
|
if o.qch != nil {
|
|
close(o.qch)
|
|
o.qch = nil
|
|
}
|
|
// Stop any inactivity timers. Should only be running on leaders.
|
|
stopAndClearTimer(&o.dtmr)
|
|
// Stop any unpause timers. Should only be running on leaders.
|
|
stopAndClearTimer(&o.uptmr)
|
|
// Make sure to clear out any re-deliver queues
|
|
o.stopAndClearPtmr()
|
|
o.rdq = nil
|
|
o.rdqi.Empty()
|
|
o.pending = nil
|
|
o.rsm = nil
|
|
o.resetPendingDeliveries()
|
|
// ok if they are nil, we protect inside unsubscribe()
|
|
o.unsubscribe(o.ackSubOld)
|
|
o.unsubscribe(o.ackSub)
|
|
o.unsubscribe(o.reqSub)
|
|
o.unsubscribe(o.resetSub)
|
|
o.unsubscribe(o.fcSubOld)
|
|
o.unsubscribe(o.fcSub)
|
|
o.ackSubOld, o.ackSub, o.reqSub, o.resetSub, o.fcSubOld, o.fcSub = nil, nil, nil, nil, nil, nil
|
|
if o.infoSub != nil {
|
|
o.srv.sysUnsubscribe(o.infoSub)
|
|
o.infoSub = nil
|
|
}
|
|
// Reset waiting if we are in pull mode.
|
|
if o.isPullMode() {
|
|
o.waiting = newWaitQueue(o.cfg.MaxWaiting)
|
|
o.nextMsgReqs.drain()
|
|
} else if o.srv.gateway.enabled {
|
|
stopAndClearTimer(&o.gwdtmr)
|
|
}
|
|
o.unassignPinId()
|
|
// If we were the leader make sure to drain queued up acks.
|
|
if wasLeader {
|
|
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.
|
|
o.replies = nil
|
|
}
|
|
o.mu.Unlock()
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// This is coming on the wire so do not block here.
|
|
func (o *consumer) handleClusterConsumerInfoRequest(sub *subscription, c *client, _ *Account, subject, reply string, msg []byte) {
|
|
go o.infoWithSnapAndReply(false, reply)
|
|
}
|
|
|
|
// Lock should be held.
|
|
func (o *consumer) subscribeInternal(subject string, cb msgHandler) (*subscription, error) {
|
|
c := o.client
|
|
if c == nil {
|
|
return nil, fmt.Errorf("invalid consumer")
|
|
}
|
|
if !c.srv.EventsEnabled() {
|
|
return nil, ErrNoSysAccount
|
|
}
|
|
if cb == nil {
|
|
return nil, fmt.Errorf("undefined message handler")
|
|
}
|
|
|
|
o.sid++
|
|
|
|
// Now create the subscription
|
|
return c.processSub([]byte(subject), nil, []byte(strconv.Itoa(o.sid)), cb, false)
|
|
}
|
|
|
|
// Unsubscribe from our subscription.
|
|
// Lock should be held.
|
|
func (o *consumer) unsubscribe(sub *subscription) {
|
|
if sub == nil || o.client == nil {
|
|
return
|
|
}
|
|
o.client.processUnsub(sub.sid)
|
|
}
|
|
|
|
// We need to make sure we protect access to the outq.
|
|
// Do all advisory sends here.
|
|
func (o *consumer) sendAdvisory(subject string, e any) {
|
|
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() {
|
|
e := JSConsumerActionAdvisory{
|
|
TypedEvent: TypedEvent{
|
|
Type: JSConsumerActionAdvisoryType,
|
|
ID: nuid.Next(),
|
|
Time: time.Now().UTC(),
|
|
},
|
|
Stream: o.stream,
|
|
Consumer: o.name,
|
|
Action: DeleteEvent,
|
|
Domain: o.srv.getOpts().JetStreamDomain,
|
|
}
|
|
|
|
subj := JSAdvisoryConsumerDeletedPre + "." + o.stream + "." + o.name
|
|
o.sendAdvisory(subj, e)
|
|
}
|
|
|
|
func (o *consumer) sendPinnedAdvisoryLocked(group string) {
|
|
e := JSConsumerGroupPinnedAdvisory{
|
|
TypedEvent: TypedEvent{
|
|
Type: JSConsumerGroupPinnedAdvisoryType,
|
|
ID: nuid.Next(),
|
|
Time: time.Now().UTC(),
|
|
},
|
|
Account: o.acc.Name,
|
|
Stream: o.stream,
|
|
Consumer: o.name,
|
|
Domain: o.srv.getOpts().JetStreamDomain,
|
|
PinnedClientId: o.currentPinId,
|
|
Group: group,
|
|
}
|
|
|
|
subj := JSAdvisoryConsumerPinnedPre + "." + o.stream + "." + o.name
|
|
o.sendAdvisory(subj, e)
|
|
|
|
}
|
|
func (o *consumer) sendUnpinnedAdvisoryLocked(group string, reason string) {
|
|
e := JSConsumerGroupUnpinnedAdvisory{
|
|
TypedEvent: TypedEvent{
|
|
Type: JSConsumerGroupUnpinnedAdvisoryType,
|
|
ID: nuid.Next(),
|
|
Time: time.Now().UTC(),
|
|
},
|
|
Account: o.acc.Name,
|
|
Stream: o.stream,
|
|
Consumer: o.name,
|
|
Domain: o.srv.getOpts().JetStreamDomain,
|
|
Group: group,
|
|
Reason: reason,
|
|
}
|
|
|
|
subj := JSAdvisoryConsumerUnpinnedPre + "." + o.stream + "." + o.name
|
|
o.sendAdvisory(subj, e)
|
|
|
|
}
|
|
|
|
func (o *consumer) sendCreateAdvisory() {
|
|
o.mu.Lock()
|
|
defer o.mu.Unlock()
|
|
|
|
e := JSConsumerActionAdvisory{
|
|
TypedEvent: TypedEvent{
|
|
Type: JSConsumerActionAdvisoryType,
|
|
ID: nuid.Next(),
|
|
Time: time.Now().UTC(),
|
|
},
|
|
Stream: o.stream,
|
|
Consumer: o.name,
|
|
Action: CreateEvent,
|
|
Domain: o.srv.getOpts().JetStreamDomain,
|
|
}
|
|
|
|
subj := JSAdvisoryConsumerCreatedPre + "." + o.stream + "." + o.name
|
|
o.sendAdvisory(subj, e)
|
|
}
|
|
|
|
func (o *consumer) sendPauseAdvisoryLocked(cfg *ConsumerConfig) {
|
|
e := JSConsumerPauseAdvisory{
|
|
TypedEvent: TypedEvent{
|
|
Type: JSConsumerPauseAdvisoryType,
|
|
ID: nuid.Next(),
|
|
Time: time.Now().UTC(),
|
|
},
|
|
Stream: o.stream,
|
|
Consumer: o.name,
|
|
Domain: o.srv.getOpts().JetStreamDomain,
|
|
}
|
|
|
|
if cfg.PauseUntil != nil {
|
|
e.PauseUntil = *cfg.PauseUntil
|
|
e.Paused = time.Now().Before(e.PauseUntil)
|
|
}
|
|
|
|
subj := JSAdvisoryConsumerPausePre + "." + o.stream + "." + o.name
|
|
o.sendAdvisory(subj, e)
|
|
}
|
|
|
|
// Created returns created time.
|
|
func (o *consumer) createdTime() time.Time {
|
|
o.mu.Lock()
|
|
created := o.created
|
|
o.mu.Unlock()
|
|
return created
|
|
}
|
|
|
|
// Internal to allow creation time to be restored.
|
|
func (o *consumer) setCreatedTime(created time.Time) {
|
|
o.mu.Lock()
|
|
o.created = created
|
|
o.mu.Unlock()
|
|
}
|
|
|
|
// This will check for extended interest in a subject. If we have local interest we just return
|
|
// that, but in the absence of local interest and presence of gateways or service imports we need
|
|
// to check those as well.
|
|
func (o *consumer) hasDeliveryInterest(localInterest bool) bool {
|
|
o.mu.RLock()
|
|
mset := o.mset
|
|
if mset == nil {
|
|
o.mu.RUnlock()
|
|
return false
|
|
}
|
|
acc := o.acc
|
|
deliver := o.cfg.DeliverSubject
|
|
o.mu.RUnlock()
|
|
|
|
if localInterest {
|
|
return true
|
|
}
|
|
|
|
// If we are here check gateways.
|
|
if s := acc.srv; s != nil && s.hasGatewayInterest(acc.Name, deliver) {
|
|
return true
|
|
}
|
|
return false
|
|
}
|
|
|
|
func (s *Server) hasGatewayInterest(account, subject string) bool {
|
|
gw := s.gateway
|
|
if !gw.enabled {
|
|
return false
|
|
}
|
|
gw.RLock()
|
|
defer gw.RUnlock()
|
|
for _, gwc := range gw.outo {
|
|
psi, qr := gwc.gatewayInterest(account, stringToBytes(subject))
|
|
if psi || qr != nil {
|
|
return true
|
|
}
|
|
}
|
|
return false
|
|
}
|
|
|
|
// This processes an update to the local interest for a deliver subject.
|
|
func (o *consumer) updateDeliveryInterest(localInterest bool) bool {
|
|
interest := o.hasDeliveryInterest(localInterest)
|
|
|
|
o.mu.Lock()
|
|
defer o.mu.Unlock()
|
|
|
|
mset := o.mset
|
|
if mset == nil || o.isPullMode() {
|
|
return false
|
|
}
|
|
|
|
if interest && !o.active {
|
|
o.signalNewMessages()
|
|
}
|
|
// Update active status, if not active clear any queue group we captured.
|
|
if o.active = interest; !o.active {
|
|
o.qgroup = _EMPTY_
|
|
} else {
|
|
o.checkQueueInterest()
|
|
}
|
|
|
|
// If the delete timer has already been set do not clear here and return.
|
|
// Note that durable can now have an inactive threshold, so don't check
|
|
// for durable status, instead check for dthresh > 0.
|
|
if o.dtmr != nil && o.dthresh > 0 && !interest {
|
|
return true
|
|
}
|
|
|
|
// Stop and clear the delete timer always.
|
|
stopAndClearTimer(&o.dtmr)
|
|
|
|
// If we do not have interest anymore and have a delete threshold set, then set
|
|
// a timer to delete us. We wait for a bit in case of server reconnect.
|
|
if !interest && o.dthresh > 0 {
|
|
o.dtmr = time.AfterFunc(o.dthresh, o.deleteNotActive)
|
|
return true
|
|
}
|
|
return false
|
|
}
|
|
|
|
const (
|
|
defaultConsumerNotActiveStartInterval = 30 * time.Second
|
|
defaultConsumerNotActiveMaxInterval = 5 * time.Minute
|
|
)
|
|
|
|
var (
|
|
consumerNotActiveStartInterval = defaultConsumerNotActiveStartInterval
|
|
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() {
|
|
// Take a copy of these when the goroutine starts, mostly it avoids a
|
|
// race condition with tests that modify these consts, such as
|
|
// TestJetStreamClusterGhostEphemeralsAfterRestart.
|
|
cnaMax := consumerNotActiveMaxInterval
|
|
cnaStart := consumerNotActiveStartInterval
|
|
|
|
o.mu.Lock()
|
|
if o.mset == nil {
|
|
o.mu.Unlock()
|
|
return
|
|
}
|
|
// Push mode just look at active.
|
|
if o.isPushMode() {
|
|
// If we are active simply return.
|
|
if o.active {
|
|
o.mu.Unlock()
|
|
return
|
|
}
|
|
} else {
|
|
// Pull mode.
|
|
elapsed := time.Since(o.waiting.last)
|
|
if elapsed < o.dthresh {
|
|
// These need to keep firing so reset but use delta.
|
|
if o.dtmr != nil {
|
|
o.dtmr.Reset(o.dthresh - elapsed)
|
|
} else {
|
|
o.dtmr = time.AfterFunc(o.dthresh-elapsed, o.deleteNotActive)
|
|
}
|
|
o.mu.Unlock()
|
|
return
|
|
}
|
|
// Check if we still have valid requests waiting.
|
|
if o.checkWaitingForInterest() {
|
|
if o.dtmr != nil {
|
|
o.dtmr.Reset(o.dthresh)
|
|
} else {
|
|
o.dtmr = time.AfterFunc(o.dthresh, o.deleteNotActive)
|
|
}
|
|
o.mu.Unlock()
|
|
return
|
|
}
|
|
|
|
// We now know we have no waiting requests, and our last request was long ago.
|
|
// However, based on AckWait the consumer could still be actively processing,
|
|
// even if we haven't been informed if there were no acks in the meantime.
|
|
// We must wait for the message that expires last and start counting down the
|
|
// inactive threshold from there.
|
|
now := time.Now().UnixNano()
|
|
l := len(o.cfg.BackOff)
|
|
var delay time.Duration
|
|
var ackWait time.Duration
|
|
for _, p := range o.pending {
|
|
if l == 0 {
|
|
ackWait = o.ackWait(0)
|
|
} else {
|
|
bi := int(o.rdc[p.Sequence])
|
|
if bi < 0 {
|
|
bi = 0
|
|
} else if bi >= l {
|
|
bi = l - 1
|
|
}
|
|
ackWait = o.ackWait(o.cfg.BackOff[bi])
|
|
}
|
|
if ts := p.Timestamp + ackWait.Nanoseconds() + o.dthresh.Nanoseconds(); ts > now {
|
|
delay = max(delay, time.Duration(ts-now))
|
|
}
|
|
}
|
|
// We'll wait for the latest time we expect an ack, plus the inactive threshold.
|
|
// Acknowledging a message will reset this back down to just the inactive threshold.
|
|
if delay > 0 {
|
|
if o.dtmr != nil {
|
|
o.dtmr.Reset(delay)
|
|
} else {
|
|
o.dtmr = time.AfterFunc(delay, o.deleteNotActive)
|
|
}
|
|
o.mu.Unlock()
|
|
return
|
|
}
|
|
}
|
|
|
|
s, js := o.mset.srv, o.srv.js.Load()
|
|
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
|
|
}
|
|
oqch := o.qch
|
|
o.mu.Unlock()
|
|
if js != nil {
|
|
cqch = js.clusterQuitC()
|
|
}
|
|
|
|
// 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 do forward a proposal to delete ourselves to the metacontroller leader.
|
|
if !isDirect && s.JetStreamIsClustered() {
|
|
js.mu.RLock()
|
|
var (
|
|
meta RaftNode
|
|
removeEntry []byte
|
|
)
|
|
ca, cc := js.consumerAssignment(acc, stream, name), js.cluster
|
|
if ca != nil && cc != nil {
|
|
meta = cc.meta
|
|
cca := ca.clone()
|
|
cca.Reply = _EMPTY_
|
|
removeEntry = encodeDeleteConsumerAssignment(cca)
|
|
meta.ForwardProposal(removeEntry)
|
|
}
|
|
js.mu.RUnlock()
|
|
|
|
if ca != nil && cc != nil {
|
|
// Check to make sure we went away.
|
|
// Don't think this needs to be a monitored go routine.
|
|
jitter := time.Duration(rand.Int63n(int64(cnaStart)))
|
|
interval := cnaStart + jitter
|
|
ticker := time.NewTicker(interval)
|
|
defer ticker.Stop()
|
|
for {
|
|
select {
|
|
case <-ticker.C:
|
|
case <-qch:
|
|
return
|
|
case <-cqch:
|
|
return
|
|
case <-oqch:
|
|
// The consumer has stopped already, likely by an earlier delete proposal being applied.
|
|
return
|
|
}
|
|
js.mu.RLock()
|
|
if js.shuttingDown {
|
|
js.mu.RUnlock()
|
|
return
|
|
}
|
|
nca := js.consumerAssignment(acc, stream, name)
|
|
js.mu.RUnlock()
|
|
// Make sure this is the same consumer assignment, and not a new consumer with the same name.
|
|
if nca != nil && reflect.DeepEqual(nca, ca) {
|
|
s.Warnf("Consumer assignment for '%s > %s > %s' not cleaned up, retrying", acc, stream, name)
|
|
meta.ForwardProposal(removeEntry)
|
|
if interval < cnaMax {
|
|
interval *= 2
|
|
ticker.Reset(interval)
|
|
}
|
|
continue
|
|
}
|
|
// We saw that consumer has been removed, all done.
|
|
return
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
func (o *consumer) watchGWinterest() {
|
|
pa := o.isActive()
|
|
// If there is no local interest...
|
|
if o.hasNoLocalInterest() {
|
|
o.updateDeliveryInterest(false)
|
|
if !pa && o.isActive() {
|
|
o.signalNewMessages()
|
|
}
|
|
}
|
|
|
|
// We want this to always be running so we can also pick up on interest returning.
|
|
o.mu.Lock()
|
|
if o.gwdtmr != nil {
|
|
o.gwdtmr.Reset(time.Second)
|
|
} else {
|
|
stopAndClearTimer(&o.gwdtmr)
|
|
o.gwdtmr = time.AfterFunc(time.Second, func() { o.watchGWinterest() })
|
|
}
|
|
o.mu.Unlock()
|
|
}
|
|
|
|
// Config returns the consumer's configuration.
|
|
func (o *consumer) config() ConsumerConfig {
|
|
o.mu.Lock()
|
|
defer o.mu.Unlock()
|
|
return o.cfg
|
|
}
|
|
|
|
// Check if we have hit max deliveries. If so do notification and cleanup.
|
|
// Return whether or not the max was hit.
|
|
// Lock should be held.
|
|
func (o *consumer) hasMaxDeliveries(seq uint64) bool {
|
|
if o.maxdc == 0 {
|
|
return false
|
|
}
|
|
if dc := o.deliveryCount(seq); dc >= o.maxdc {
|
|
// We have hit our max deliveries for this sequence.
|
|
// Only send the advisory once.
|
|
if dc == o.maxdc {
|
|
o.notifyDeliveryExceeded(seq, dc)
|
|
}
|
|
// Determine if we signal to start flow of messages again.
|
|
if o.maxp > 0 && len(o.pending) >= o.maxp {
|
|
o.signalNewMessages()
|
|
}
|
|
// Make sure to remove from pending.
|
|
if p, ok := o.pending[seq]; ok && p != nil {
|
|
delete(o.pending, seq)
|
|
o.updateDelivered(p.Sequence, seq, dc, p.Timestamp)
|
|
}
|
|
// Ensure redelivered state is set, if not already.
|
|
if o.rdc == nil {
|
|
o.rdc = make(map[uint64]uint64)
|
|
}
|
|
o.rdc[seq] = dc
|
|
return true
|
|
}
|
|
return false
|
|
}
|
|
|
|
// Force expiration of all pending.
|
|
// Lock should be held.
|
|
func (o *consumer) forceExpirePending() {
|
|
var expired []uint64
|
|
for seq := range o.pending {
|
|
if !o.onRedeliverQueue(seq) && !o.hasMaxDeliveries(seq) {
|
|
expired = append(expired, seq)
|
|
}
|
|
}
|
|
if len(expired) > 0 {
|
|
slices.Sort(expired)
|
|
o.addToRedeliverQueue(expired...)
|
|
// Now we should update the timestamp here since we are redelivering.
|
|
// We will use an incrementing time to preserve order for any other redelivery.
|
|
off := time.Now().UnixNano() - o.pending[expired[0]].Timestamp
|
|
for _, seq := range expired {
|
|
if p, ok := o.pending[seq]; ok && p != nil {
|
|
p.Timestamp += off
|
|
}
|
|
}
|
|
o.resetPtmr(o.ackWait(0))
|
|
}
|
|
o.signalNewMessages()
|
|
}
|
|
|
|
// Acquire proper locks and update rate limit.
|
|
// Will use what is in config.
|
|
func (o *consumer) setRateLimitNeedsLocks() {
|
|
o.mu.RLock()
|
|
mset := o.mset
|
|
o.mu.RUnlock()
|
|
|
|
if mset == nil {
|
|
return
|
|
}
|
|
|
|
mset.mu.RLock()
|
|
o.mu.Lock()
|
|
o.setRateLimit(o.cfg.RateLimit)
|
|
o.mu.Unlock()
|
|
mset.mu.RUnlock()
|
|
}
|
|
|
|
// Set the rate limiter
|
|
// Both mset and consumer lock should be held.
|
|
func (o *consumer) setRateLimit(bps uint64) {
|
|
if bps == 0 {
|
|
o.rlimit = nil
|
|
return
|
|
}
|
|
|
|
// TODO(dlc) - Make sane values or error if not sane?
|
|
// We are configured in bits per sec so adjust to bytes.
|
|
rl := rate.Limit(bps / 8)
|
|
mset := o.mset
|
|
|
|
// Burst should be set to maximum msg size for this account, etc.
|
|
var burst int
|
|
// We don't need to get cfgMu's rlock here since this function
|
|
// is already invoked under mset.mu.RLock(), which superseeds cfgMu.
|
|
if mset.cfg.MaxMsgSize > 0 {
|
|
burst = int(mset.cfg.MaxMsgSize)
|
|
} else if mset.jsa.account.limits.mpay > 0 {
|
|
burst = int(mset.jsa.account.limits.mpay)
|
|
} else {
|
|
s := mset.jsa.account.srv
|
|
burst = int(s.getOpts().MaxPayload)
|
|
}
|
|
|
|
o.rlimit = rate.NewLimiter(rl, burst)
|
|
}
|
|
|
|
// Check if new consumer config allowed vs old.
|
|
func (acc *Account) checkNewConsumerConfig(cfg, ncfg *ConsumerConfig) error {
|
|
if reflect.DeepEqual(cfg, ncfg) {
|
|
return nil
|
|
}
|
|
// Something different, so check since we only allow certain things to be updated.
|
|
if cfg.DeliverPolicy != ncfg.DeliverPolicy {
|
|
return errors.New("deliver policy can not be updated")
|
|
}
|
|
if cfg.OptStartSeq != ncfg.OptStartSeq {
|
|
return errors.New("start sequence can not be updated")
|
|
}
|
|
if cfg.OptStartTime != nil && ncfg.OptStartTime != nil {
|
|
// Both have start times set, compare them directly:
|
|
if !cfg.OptStartTime.Equal(*ncfg.OptStartTime) {
|
|
return errors.New("start time can not be updated")
|
|
}
|
|
} else if cfg.OptStartTime != nil || ncfg.OptStartTime != nil {
|
|
// At least one start time is set and the other is not
|
|
return errors.New("start time can not be updated")
|
|
}
|
|
if cfg.AckPolicy != ncfg.AckPolicy {
|
|
return errors.New("ack policy can not be updated")
|
|
}
|
|
if cfg.ReplayPolicy != ncfg.ReplayPolicy {
|
|
return errors.New("replay policy can not be updated")
|
|
}
|
|
if cfg.Heartbeat != ncfg.Heartbeat {
|
|
return errors.New("heart beats can not be updated")
|
|
}
|
|
if cfg.FlowControl != ncfg.FlowControl {
|
|
return errors.New("flow control can not be updated")
|
|
}
|
|
|
|
// Deliver Subject is conditional on if its bound.
|
|
if cfg.DeliverSubject != ncfg.DeliverSubject {
|
|
if cfg.DeliverSubject == _EMPTY_ {
|
|
return errors.New("can not update pull consumer to push based")
|
|
}
|
|
if ncfg.DeliverSubject == _EMPTY_ {
|
|
return errors.New("can not update push consumer to pull based")
|
|
}
|
|
if acc.sl.HasInterest(cfg.DeliverSubject) {
|
|
return NewJSConsumerNameExistError()
|
|
}
|
|
}
|
|
|
|
if cfg.MaxWaiting != ncfg.MaxWaiting {
|
|
return errors.New("max waiting can not be updated")
|
|
}
|
|
|
|
// Check if BackOff is defined, MaxDeliver is within range.
|
|
if lbo := len(ncfg.BackOff); lbo > 0 && ncfg.MaxDeliver != -1 && lbo > ncfg.MaxDeliver {
|
|
return NewJSConsumerMaxDeliverBackoffError()
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// Update the config based on the new config, or error if update not allowed.
|
|
func (o *consumer) updateConfig(cfg *ConsumerConfig) error {
|
|
o.mu.Lock()
|
|
defer o.mu.Unlock()
|
|
|
|
if o.closed || o.mset == nil {
|
|
return NewJSConsumerDoesNotExistError()
|
|
}
|
|
|
|
if err := o.acc.checkNewConsumerConfig(&o.cfg, cfg); err != nil {
|
|
return err
|
|
}
|
|
|
|
// Make sure we always store PauseUntil in UTC.
|
|
if cfg.PauseUntil != nil {
|
|
utc := (*cfg.PauseUntil).UTC()
|
|
cfg.PauseUntil = &utc
|
|
}
|
|
|
|
if o.store != nil {
|
|
// Update local state always.
|
|
if err := o.store.UpdateConfig(cfg); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
|
|
// DeliverSubject
|
|
if cfg.DeliverSubject != o.cfg.DeliverSubject {
|
|
o.updateDeliverSubjectLocked(cfg.DeliverSubject)
|
|
}
|
|
|
|
// MaxAckPending
|
|
if cfg.MaxAckPending != o.cfg.MaxAckPending {
|
|
o.maxp = cfg.MaxAckPending
|
|
o.signalNewMessages()
|
|
// If MaxAckPending is lowered, we could have allocated a pending deliveries map of larger size.
|
|
// Reset it here, so we can shrink the map.
|
|
if cfg.MaxAckPending < o.cfg.MaxAckPending {
|
|
o.resetPendingDeliveries()
|
|
}
|
|
}
|
|
// AckWait
|
|
if cfg.AckWait != o.cfg.AckWait {
|
|
if o.ptmr != nil {
|
|
o.resetPtmr(100 * time.Millisecond)
|
|
}
|
|
}
|
|
// Rate Limit
|
|
if cfg.RateLimit != o.cfg.RateLimit {
|
|
// We need both locks here so do in Go routine.
|
|
go o.setRateLimitNeedsLocks()
|
|
}
|
|
if cfg.SampleFrequency != o.cfg.SampleFrequency {
|
|
s := strings.TrimSuffix(cfg.SampleFrequency, "%")
|
|
if sampleFreq, err := strconv.ParseInt(s, 10, 32); err == nil {
|
|
o.sfreq = int32(sampleFreq)
|
|
}
|
|
}
|
|
// Set MaxDeliver if changed
|
|
if cfg.MaxDeliver != o.cfg.MaxDeliver {
|
|
// MaxDeliver is negative (-1) when infinite.
|
|
o.maxdc = uint64(max(cfg.MaxDeliver, 0))
|
|
}
|
|
// Set InactiveThreshold if changed.
|
|
if val := cfg.InactiveThreshold; val != o.cfg.InactiveThreshold {
|
|
o.updateInactiveThreshold(cfg)
|
|
stopAndClearTimer(&o.dtmr)
|
|
// Restart timer only if we are the leader.
|
|
if o.isLeader() && o.dthresh > 0 {
|
|
o.dtmr = time.AfterFunc(o.dthresh, o.deleteNotActive)
|
|
}
|
|
}
|
|
// Check whether the pause has changed
|
|
{
|
|
var old, new time.Time
|
|
if o.cfg.PauseUntil != nil {
|
|
old = *o.cfg.PauseUntil
|
|
}
|
|
if cfg.PauseUntil != nil {
|
|
new = *cfg.PauseUntil
|
|
}
|
|
if !old.Equal(new) {
|
|
o.updatePauseState(cfg)
|
|
if o.isLeader() {
|
|
o.sendPauseAdvisoryLocked(cfg)
|
|
}
|
|
}
|
|
}
|
|
|
|
// Check for Subject Filters update.
|
|
newSubjects := gatherSubjectFilters(cfg.FilterSubject, cfg.FilterSubjects)
|
|
updatedFilters := !subjectSliceEqual(newSubjects, o.subjf.subjects())
|
|
if updatedFilters {
|
|
newSubjf := make(subjectFilters, 0, len(newSubjects))
|
|
for _, newFilter := range newSubjects {
|
|
fs := &subjectFilter{
|
|
subject: newFilter,
|
|
hasWildcard: subjectHasWildcard(newFilter),
|
|
tokenizedSubject: tokenizeSubjectIntoSlice(nil, newFilter),
|
|
}
|
|
newSubjf = append(newSubjf, fs)
|
|
}
|
|
// Make sure we have correct signaling setup.
|
|
// Consumer lock can not be held.
|
|
mset := o.mset
|
|
o.mu.Unlock()
|
|
mset.swapSigSubs(o, newSubjf.subjects())
|
|
o.mu.Lock()
|
|
|
|
// When we're done with signaling, we can replace the subjects.
|
|
// If filters were removed, set `o.subjf` to nil.
|
|
if len(newSubjf) == 0 {
|
|
o.subjf = nil
|
|
o.filters = nil
|
|
} else {
|
|
o.subjf = newSubjf
|
|
if len(o.subjf) == 1 {
|
|
o.filters = nil
|
|
} else {
|
|
o.filters = gsl.NewSublist[struct{}]()
|
|
for _, filter := range o.subjf {
|
|
o.filters.Insert(filter.subject, struct{}{})
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// Record new config for others that do not need special handling.
|
|
// Allowed but considered no-op, [Description, SampleFrequency, MaxWaiting, HeadersOnly]
|
|
o.cfg = *cfg
|
|
|
|
if cfg.Sourcing && (!o.srv.JetStreamIsClustered() && o.srv.standAloneMode()) {
|
|
o.resetStartingSeqLocked(0, _EMPTY_, false)
|
|
}
|
|
if updatedFilters {
|
|
// Cleanup messages that lost interest.
|
|
if o.retention == InterestPolicy {
|
|
o.mu.Unlock()
|
|
o.cleanupNoInterestMessages(o.mset, false)
|
|
o.mu.Lock()
|
|
}
|
|
|
|
// Re-calculate num pending on update.
|
|
o.streamNumPending()
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// This is a config change for the delivery subject for a
|
|
// push based consumer.
|
|
func (o *consumer) updateDeliverSubject(newDeliver string) {
|
|
// Update the config and the dsubj
|
|
o.mu.Lock()
|
|
defer o.mu.Unlock()
|
|
o.updateDeliverSubjectLocked(newDeliver)
|
|
}
|
|
|
|
// This is a config change for the delivery subject for a
|
|
// push based consumer.
|
|
func (o *consumer) updateDeliverSubjectLocked(newDeliver string) {
|
|
if o.closed || o.isPullMode() || o.cfg.DeliverSubject == newDeliver {
|
|
return
|
|
}
|
|
|
|
// Force redeliver of all pending on change of delivery subject.
|
|
if len(o.pending) > 0 {
|
|
o.forceExpirePending()
|
|
}
|
|
|
|
o.acc.sl.clearNotification(o.dsubj, o.cfg.DeliverGroup, o.inch)
|
|
o.dsubj, o.cfg.DeliverSubject = newDeliver, newDeliver
|
|
// When we register new one it will deliver to update state loop.
|
|
o.acc.sl.registerNotification(newDeliver, o.cfg.DeliverGroup, o.inch)
|
|
}
|
|
|
|
// Check that configs are equal but allow delivery subjects to be different.
|
|
func configsEqualSansDelivery(a, b ConsumerConfig) bool {
|
|
// These were copied in so can set Delivery here.
|
|
a.DeliverSubject, b.DeliverSubject = _EMPTY_, _EMPTY_
|
|
return reflect.DeepEqual(a, b)
|
|
}
|
|
|
|
// Helper to send a reply to an ack.
|
|
func (o *consumer) sendAckReply(subj string) {
|
|
o.mu.RLock()
|
|
defer o.mu.RUnlock()
|
|
o.outq.sendMsg(subj, nil)
|
|
}
|
|
|
|
type jsAckMsg struct {
|
|
subject string
|
|
reply string
|
|
hdr int
|
|
msg []byte
|
|
}
|
|
|
|
var jsAckMsgPool sync.Pool
|
|
|
|
func newJSAckMsg(subj, reply string, hdr int, msg []byte) *jsAckMsg {
|
|
var m *jsAckMsg
|
|
am := jsAckMsgPool.Get()
|
|
if am != nil {
|
|
m = am.(*jsAckMsg)
|
|
} else {
|
|
m = &jsAckMsg{}
|
|
}
|
|
// When getting something from a pool it is critical that all fields are
|
|
// initialized. Doing this way guarantees that if someone adds a field to
|
|
// the structure, the compiler will fail the build if this line is not updated.
|
|
(*m) = jsAckMsg{subj, reply, hdr, msg}
|
|
return m
|
|
}
|
|
|
|
func (am *jsAckMsg) returnToPool() {
|
|
if am == nil {
|
|
return
|
|
}
|
|
am.subject, am.reply, am.hdr, am.msg = _EMPTY_, _EMPTY_, -1, nil
|
|
jsAckMsgPool.Put(am)
|
|
}
|
|
|
|
// Push the ack message to the consumer's ackMsgs queue
|
|
func (o *consumer) pushAck(_ *subscription, c *client, _ *Account, subject, reply string, rmsg []byte) {
|
|
atomic.AddInt64(&o.awl, 1)
|
|
o.ackMsgs.push(newJSAckMsg(subject, reply, c.pa.hdr, copyBytes(rmsg)))
|
|
}
|
|
|
|
// Processes a message for the ack reply subject delivered with a message.
|
|
func (o *consumer) processAck(subject, reply string, hdr int, rmsg []byte) {
|
|
defer atomic.AddInt64(&o.awl, -1)
|
|
|
|
var msg []byte
|
|
if hdr > 0 {
|
|
msg = rmsg[hdr:]
|
|
} else {
|
|
msg = rmsg
|
|
}
|
|
|
|
sseq, dseq, dc, _, _ := ackReplyInfo(subject)
|
|
|
|
skipAckReply := sseq == 0
|
|
|
|
switch {
|
|
case len(msg) == 0, bytes.Equal(msg, AckAck), bytes.Equal(msg, AckOK):
|
|
if !o.processAckMsg(sseq, dseq, dc, reply, true) {
|
|
// We handle replies for acks in updateAcks
|
|
skipAckReply = true
|
|
}
|
|
case bytes.HasPrefix(msg, AckNext):
|
|
o.processAckMsg(sseq, dseq, dc, _EMPTY_, true)
|
|
o.processNextMsgRequest(reply, msg[len(AckNext):])
|
|
skipAckReply = true
|
|
case bytes.HasPrefix(msg, AckNak):
|
|
o.processNak(sseq, dseq, dc, msg)
|
|
case bytes.Equal(msg, AckProgress):
|
|
o.progressUpdate(sseq)
|
|
case bytes.HasPrefix(msg, AckTerm):
|
|
var reason string
|
|
if buf := msg[len(AckTerm):]; len(buf) > 0 {
|
|
reason = string(bytes.TrimSpace(buf))
|
|
}
|
|
if !o.processTerm(sseq, dseq, dc, reason, reply) {
|
|
// We handle replies for acks in updateAcks
|
|
skipAckReply = true
|
|
}
|
|
}
|
|
|
|
// Ack the ack if requested.
|
|
if len(reply) > 0 && !skipAckReply {
|
|
o.sendAckReply(reply)
|
|
}
|
|
}
|
|
|
|
// Used to process a working update to delay redelivery.
|
|
func (o *consumer) progressUpdate(seq uint64) {
|
|
o.mu.Lock()
|
|
defer o.mu.Unlock()
|
|
|
|
if p, ok := o.pending[seq]; ok {
|
|
p.Timestamp = time.Now().UnixNano()
|
|
// Update store system.
|
|
o.updateDelivered(p.Sequence, seq, 1, p.Timestamp)
|
|
}
|
|
}
|
|
|
|
// Lock should be held.
|
|
func (o *consumer) updateSkipped(seq uint64) {
|
|
// Clustered mode and R>1 only.
|
|
if o.node == nil || !o.isLeader() {
|
|
return
|
|
}
|
|
var b [1 + 8]byte
|
|
b[0] = byte(updateSkipOp)
|
|
var le = binary.LittleEndian
|
|
le.PutUint64(b[1:], seq)
|
|
o.propose(b[:])
|
|
}
|
|
|
|
func (o *consumer) resetStartingSeq(seq uint64, reply string, internal bool) (uint64, bool, error) {
|
|
o.mu.Lock()
|
|
defer o.mu.Unlock()
|
|
return o.resetStartingSeqLocked(seq, reply, internal)
|
|
}
|
|
|
|
// Lock should be held.
|
|
func (o *consumer) resetStartingSeqLocked(seq uint64, reply string, internal bool) (uint64, bool, error) {
|
|
// Reset to a specific sequence, or back to the ack floor.
|
|
if seq == 0 {
|
|
seq = o.asflr + 1
|
|
} else if o.cfg.DeliverPolicy == DeliverAll {
|
|
// Always allowed.
|
|
goto VALID
|
|
} else if o.cfg.DeliverPolicy == DeliverByStartSequence {
|
|
// Only allowed if not going below what's configured.
|
|
if seq < o.cfg.OptStartSeq {
|
|
return 0, false, errors.New("below start seq")
|
|
}
|
|
goto VALID
|
|
} else if o.cfg.DeliverPolicy == DeliverByStartTime && o.mset != nil {
|
|
// Only allowed if not going below what's configured.
|
|
nseq := o.mset.store.GetSeqFromTime(*o.cfg.OptStartTime)
|
|
if seq < nseq {
|
|
return 0, false, errors.New("below start time")
|
|
}
|
|
goto VALID
|
|
} else {
|
|
return 0, false, errors.New("not allowed")
|
|
}
|
|
|
|
VALID:
|
|
// Must be a minimum of 1.
|
|
if seq <= 0 {
|
|
seq = 1
|
|
}
|
|
// The replicated path requires quorum first before the reset actually takes effect.
|
|
if o.node != nil {
|
|
if !o.isLeader() {
|
|
return 0, false, nil
|
|
}
|
|
b := make([]byte, 1+8+len(reply))
|
|
b[0] = byte(resetSeqOp)
|
|
var le = binary.LittleEndian
|
|
le.PutUint64(b[1:], seq)
|
|
copy(b[1+8:], reply)
|
|
o.propose(b[:])
|
|
if reply != _EMPTY_ {
|
|
if o.rsm == nil {
|
|
o.rsm = make(map[string]bool, 1)
|
|
}
|
|
o.rsm[reply] = internal
|
|
}
|
|
return seq, false, nil
|
|
}
|
|
o.resetLocalStartingSeq(seq)
|
|
if o.store != nil {
|
|
o.store.Reset(seq - 1)
|
|
// Cleanup messages that lost interest.
|
|
if o.retention == InterestPolicy {
|
|
if mset := o.mset; mset != nil {
|
|
o.mu.Unlock()
|
|
ss := mset.state()
|
|
o.checkStateForInterestStream(&ss)
|
|
o.mu.Lock()
|
|
}
|
|
}
|
|
|
|
// Recalculate pending, and re-trigger message delivery.
|
|
o.streamNumPending()
|
|
o.signalNewMessages()
|
|
return seq, true, nil
|
|
}
|
|
return seq, false, nil
|
|
}
|
|
|
|
// Lock should be held.
|
|
func (o *consumer) resetLocalStartingSeq(seq uint64) {
|
|
o.pending, o.rdc = nil, nil
|
|
o.rdq = nil
|
|
o.rdqi.Empty()
|
|
o.sseq, o.dseq = seq, 1
|
|
o.adflr, o.asflr = o.dseq-1, o.sseq-1
|
|
o.ldt, o.lat = time.Time{}, time.Time{}
|
|
}
|
|
|
|
func (o *consumer) loopAndForwardProposals(qch chan struct{}) {
|
|
// On exit make sure we nil out pch.
|
|
defer func() {
|
|
o.mu.Lock()
|
|
o.pch = nil
|
|
o.mu.Unlock()
|
|
}()
|
|
|
|
o.mu.RLock()
|
|
node, pch := o.node, o.pch
|
|
o.mu.RUnlock()
|
|
|
|
if node == nil || pch == nil {
|
|
return
|
|
}
|
|
|
|
forwardProposals := func() error {
|
|
o.mu.Lock()
|
|
if o.node == nil || !o.node.Leader() {
|
|
o.mu.Unlock()
|
|
return errors.New("no longer leader")
|
|
}
|
|
proposal := o.phead
|
|
o.phead, o.ptail = nil, nil
|
|
o.mu.Unlock()
|
|
// 256k max for now per batch.
|
|
const maxBatch = 256 * 1024
|
|
var entries []*Entry
|
|
for sz := 0; proposal != nil; proposal = proposal.next {
|
|
entries = append(entries, newEntry(EntryNormal, proposal.data))
|
|
sz += len(proposal.data)
|
|
if sz > maxBatch {
|
|
node.ProposeMulti(entries)
|
|
// We need to re-create `entries` because there is a reference
|
|
// to it in the node's pae map.
|
|
sz, entries = 0, nil
|
|
}
|
|
}
|
|
if len(entries) > 0 {
|
|
node.ProposeMulti(entries)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// In case we have anything pending on entry.
|
|
forwardProposals()
|
|
|
|
for {
|
|
select {
|
|
case <-qch:
|
|
forwardProposals()
|
|
return
|
|
case <-pch:
|
|
if err := forwardProposals(); err != nil {
|
|
return
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// Lock should be held.
|
|
func (o *consumer) propose(entry []byte) {
|
|
p := &proposal{data: entry}
|
|
if o.phead == nil {
|
|
o.phead = p
|
|
} else {
|
|
o.ptail.next = p
|
|
}
|
|
o.ptail = p
|
|
|
|
// Kick our looper routine.
|
|
select {
|
|
case o.pch <- struct{}{}:
|
|
default:
|
|
}
|
|
}
|
|
|
|
// Lock should be held.
|
|
func (o *consumer) updateDelivered(dseq, sseq, dc uint64, ts int64) {
|
|
// Clustered mode and R>1.
|
|
if o.node != nil {
|
|
// Inline for now, use variable compression.
|
|
var b [4*binary.MaxVarintLen64 + 1]byte
|
|
b[0] = byte(updateDeliveredOp)
|
|
n := 1
|
|
n += binary.PutUvarint(b[n:], dseq)
|
|
n += binary.PutUvarint(b[n:], sseq)
|
|
n += binary.PutUvarint(b[n:], dc)
|
|
n += binary.PutVarint(b[n:], ts)
|
|
o.propose(b[:n])
|
|
} else if o.store != nil {
|
|
o.store.UpdateDelivered(dseq, sseq, dc, ts)
|
|
}
|
|
// Update activity.
|
|
o.ldt = time.Now()
|
|
}
|
|
|
|
// Used to remember a pending ack reply in a replicated consumer.
|
|
// Lock should be held.
|
|
func (o *consumer) addAckReply(sseq uint64, reply string) {
|
|
if o.replies == nil {
|
|
o.replies = make(map[uint64]string)
|
|
}
|
|
o.replies[sseq] = reply
|
|
}
|
|
|
|
// Used to remember messages that need to be sent for a replicated consumer, after delivered quorum.
|
|
// Lock should be held.
|
|
func (o *consumer) addReplicatedQueuedMsg(pmsg *jsPubMsg) {
|
|
// Is not explicitly limited in size, but will at most hold maximum ack pending.
|
|
if o.pendingDeliveries == nil {
|
|
o.pendingDeliveries = make(map[uint64]*jsPubMsg)
|
|
}
|
|
o.pendingDeliveries[pmsg.seq] = pmsg
|
|
|
|
// Is not explicitly limited in size, but will at most hold maximum waiting requests.
|
|
if o.waitingDeliveries == nil {
|
|
o.waitingDeliveries = make(map[string]*waitingDelivery)
|
|
}
|
|
if wd, ok := o.waitingDeliveries[pmsg.dsubj]; ok {
|
|
wd.seq = pmsg.seq
|
|
} else {
|
|
wd := wdPool.Get().(*waitingDelivery)
|
|
wd.seq = pmsg.seq
|
|
o.waitingDeliveries[pmsg.dsubj] = wd
|
|
}
|
|
}
|
|
|
|
// Lock should be held.
|
|
func (o *consumer) updateAcks(dseq, sseq uint64, reply string) {
|
|
if o.node != nil {
|
|
// Inline for now, use variable compression.
|
|
var b [2*binary.MaxVarintLen64 + 1]byte
|
|
b[0] = byte(updateAcksOp)
|
|
n := 1
|
|
n += binary.PutUvarint(b[n:], dseq)
|
|
n += binary.PutUvarint(b[n:], sseq)
|
|
o.propose(b[:n])
|
|
if reply != _EMPTY_ {
|
|
o.addAckReply(sseq, reply)
|
|
}
|
|
} else if o.store != nil {
|
|
o.store.UpdateAcks(dseq, sseq)
|
|
if reply != _EMPTY_ {
|
|
// Already locked so send direct.
|
|
o.outq.sendMsg(reply, nil)
|
|
}
|
|
}
|
|
// Update activity.
|
|
o.lat = time.Now()
|
|
}
|
|
|
|
// Communicate to the cluster an addition of a pending request.
|
|
// Lock should be held.
|
|
func (o *consumer) addClusterPendingRequest(reply string) {
|
|
if o.node == nil || !o.pendingRequestsOk() {
|
|
return
|
|
}
|
|
b := make([]byte, len(reply)+1)
|
|
b[0] = byte(addPendingRequest)
|
|
copy(b[1:], reply)
|
|
o.propose(b)
|
|
}
|
|
|
|
// Communicate to the cluster a removal of a pending request.
|
|
// Lock should be held.
|
|
func (o *consumer) removeClusterPendingRequest(reply string) {
|
|
if o.node == nil || !o.pendingRequestsOk() {
|
|
return
|
|
}
|
|
b := make([]byte, len(reply)+1)
|
|
b[0] = byte(removePendingRequest)
|
|
copy(b[1:], reply)
|
|
o.propose(b)
|
|
}
|
|
|
|
// Set whether or not we can send pending requests to followers.
|
|
func (o *consumer) setPendingRequestsOk(ok bool) {
|
|
o.mu.Lock()
|
|
o.prOk = ok
|
|
o.mu.Unlock()
|
|
}
|
|
|
|
// Lock should be held.
|
|
func (o *consumer) pendingRequestsOk() bool {
|
|
return o.prOk
|
|
}
|
|
|
|
// Set whether or not we can send info about pending pull requests to our group.
|
|
// Will require all peers have a minimum version.
|
|
func (o *consumer) checkAndSetPendingRequestsOk() {
|
|
o.mu.RLock()
|
|
s, isValid := o.srv, o.mset != nil
|
|
o.mu.RUnlock()
|
|
if !isValid {
|
|
return
|
|
}
|
|
|
|
if ca := o.consumerAssignment(); ca != nil && len(ca.Group.Peers) > 1 {
|
|
for _, pn := range ca.Group.Peers {
|
|
if si, ok := s.nodeToInfo.Load(pn); ok {
|
|
if !versionAtLeast(si.(nodeInfo).version, 2, 7, 1) {
|
|
// We expect all of our peers to eventually be up to date.
|
|
// So check again in awhile.
|
|
time.AfterFunc(eventsHBInterval, func() { o.checkAndSetPendingRequestsOk() })
|
|
o.setPendingRequestsOk(false)
|
|
return
|
|
}
|
|
}
|
|
}
|
|
}
|
|
o.setPendingRequestsOk(true)
|
|
}
|
|
|
|
// On leadership change make sure we alert the pending requests that they are no longer valid.
|
|
func (o *consumer) checkPendingRequests() {
|
|
o.mu.Lock()
|
|
defer o.mu.Unlock()
|
|
if o.mset == nil || o.outq == nil {
|
|
return
|
|
}
|
|
hdr := []byte("NATS/1.0 409 Leadership Change\r\n\r\n")
|
|
for reply := range o.prm {
|
|
o.outq.send(newJSPubMsg(reply, _EMPTY_, _EMPTY_, hdr, nil, nil, 0))
|
|
}
|
|
o.prm = nil
|
|
}
|
|
|
|
// This will release any pending pull requests if applicable.
|
|
// Should be called only by the leader being deleted or stopped.
|
|
// Lock should be held.
|
|
func (o *consumer) releaseAnyPendingRequests(isAssigned bool) {
|
|
if o.mset == nil || o.outq == nil || o.waiting.len() == 0 {
|
|
return
|
|
}
|
|
wq := o.waiting
|
|
for wr := wq.head; wr != nil; {
|
|
if !isAssigned {
|
|
hdr := []byte("NATS/1.0 409 Consumer Deleted\r\n\r\n")
|
|
o.outq.send(newJSPubMsg(wr.reply, _EMPTY_, _EMPTY_, hdr, nil, nil, 0))
|
|
}
|
|
next := wr.next
|
|
wr.recycle()
|
|
wr = next
|
|
}
|
|
// Nil out old queue.
|
|
o.waiting = nil
|
|
}
|
|
|
|
// Process a NAK.
|
|
func (o *consumer) processNak(sseq, dseq, dc uint64, nak []byte) {
|
|
o.mu.Lock()
|
|
defer o.mu.Unlock()
|
|
|
|
// Check for out of range.
|
|
if dseq <= o.adflr || dseq > o.dseq {
|
|
return
|
|
}
|
|
// If we are explicit ack make sure this is still on our pending list.
|
|
if _, ok := o.pending[sseq]; !ok {
|
|
return
|
|
}
|
|
|
|
// Deliver an advisory
|
|
e := JSConsumerDeliveryNakAdvisory{
|
|
TypedEvent: TypedEvent{
|
|
Type: JSConsumerDeliveryNakAdvisoryType,
|
|
ID: nuid.Next(),
|
|
Time: time.Now().UTC(),
|
|
},
|
|
Stream: o.stream,
|
|
Consumer: o.name,
|
|
ConsumerSeq: dseq,
|
|
StreamSeq: sseq,
|
|
Deliveries: dc,
|
|
Domain: o.srv.getOpts().JetStreamDomain,
|
|
}
|
|
|
|
o.sendAdvisory(o.nakEventT, e)
|
|
|
|
// Check to see if we have delays attached.
|
|
if len(nak) > len(AckNak) {
|
|
arg := bytes.TrimSpace(nak[len(AckNak):])
|
|
if len(arg) > 0 {
|
|
var d time.Duration
|
|
var err error
|
|
if arg[0] == '{' {
|
|
var nd ConsumerNakOptions
|
|
if err = json.Unmarshal(arg, &nd); err == nil {
|
|
d = nd.Delay
|
|
}
|
|
} else {
|
|
d, err = time.ParseDuration(string(arg))
|
|
}
|
|
if err != nil {
|
|
// Treat this as normal NAK.
|
|
o.srv.Warnf("JetStream consumer '%s > %s > %s' bad NAK delay value: %q", o.acc.Name, o.stream, o.name, arg)
|
|
} else {
|
|
// We have a parsed duration that the user wants us to wait before retrying.
|
|
// Make sure we are not on the rdq.
|
|
o.removeFromRedeliverQueue(sseq)
|
|
if p, ok := o.pending[sseq]; ok {
|
|
// now - ackWait is expired now, so offset from there.
|
|
p.Timestamp = time.Now().Add(-o.cfg.AckWait).Add(d).UnixNano()
|
|
// Update store system which will update followers as well.
|
|
o.updateDelivered(p.Sequence, sseq, dc, p.Timestamp)
|
|
if o.ptmr != nil {
|
|
// Want checkPending to run and figure out the next timer ttl.
|
|
// TODO(dlc) - We could optimize this maybe a bit more and track when we expect the timer to fire.
|
|
o.resetPtmr(10 * time.Millisecond)
|
|
}
|
|
}
|
|
// Nothing else for use to do now so return.
|
|
return
|
|
}
|
|
}
|
|
}
|
|
|
|
// If already queued up also ignore.
|
|
if !o.onRedeliverQueue(sseq) {
|
|
o.addToRedeliverQueue(sseq)
|
|
}
|
|
|
|
o.signalNewMessages()
|
|
}
|
|
|
|
// Process a TERM
|
|
// Returns `true` if the ack was processed in place and the sender can now respond
|
|
// to the client, or `false` if there was an error or the ack is replicated (in which
|
|
// case the reply will be sent later).
|
|
func (o *consumer) processTerm(sseq, dseq, dc uint64, reason, reply string) bool {
|
|
return o.processTermLocked(sseq, dseq, dc, reason, reply, true)
|
|
}
|
|
|
|
func (o *consumer) processTermLocked(sseq, dseq, dc uint64, reason, reply string, needLock bool) bool {
|
|
// Treat like an ack to suppress redelivery.
|
|
ackedInPlace := o.processAckMsgLocked(sseq, dseq, dc, reply, false, needLock)
|
|
|
|
if needLock {
|
|
o.mu.Lock()
|
|
defer o.mu.Unlock()
|
|
}
|
|
|
|
// Deliver an advisory
|
|
e := JSConsumerDeliveryTerminatedAdvisory{
|
|
TypedEvent: TypedEvent{
|
|
Type: JSConsumerDeliveryTerminatedAdvisoryType,
|
|
ID: nuid.Next(),
|
|
Time: time.Now().UTC(),
|
|
},
|
|
Stream: o.stream,
|
|
Consumer: o.name,
|
|
ConsumerSeq: dseq,
|
|
StreamSeq: sseq,
|
|
Deliveries: dc,
|
|
Reason: reason,
|
|
Domain: o.srv.getOpts().JetStreamDomain,
|
|
}
|
|
|
|
subj := JSAdvisoryConsumerMsgTerminatedPre + "." + o.stream + "." + o.name
|
|
o.sendAdvisory(subj, e)
|
|
return ackedInPlace
|
|
}
|
|
|
|
// Introduce a small delay in when timer fires to check pending.
|
|
// Allows bursts to be treated in same time frame.
|
|
const ackWaitDelay = time.Millisecond
|
|
|
|
// ackWait returns how long to wait to fire the pending timer.
|
|
func (o *consumer) ackWait(next time.Duration) time.Duration {
|
|
if next > 0 {
|
|
return next + ackWaitDelay
|
|
}
|
|
return o.cfg.AckWait + ackWaitDelay
|
|
}
|
|
|
|
// Due to bug in calculation of sequences on restoring redelivered let's do quick sanity check.
|
|
// Lock should be held.
|
|
func (o *consumer) checkRedelivered() {
|
|
var shouldUpdateState bool
|
|
for sseq := range o.rdc {
|
|
if sseq <= o.asflr {
|
|
delete(o.rdc, sseq)
|
|
o.removeFromRedeliverQueue(sseq)
|
|
shouldUpdateState = true
|
|
}
|
|
}
|
|
if shouldUpdateState {
|
|
if err := o.writeStoreStateUnlocked(); err != nil && o.srv != nil && o.mset != nil && !o.closed {
|
|
s, acc, mset, name := o.srv, o.acc, o.mset, o.name
|
|
s.Warnf("Consumer '%s > %s > %s' error on write store state from check redelivered: %v", acc, mset.getCfgName(), name, err)
|
|
}
|
|
}
|
|
}
|
|
|
|
// This will restore the state from disk.
|
|
// Lock should be held.
|
|
func (o *consumer) readStoredState() error {
|
|
if o.store == nil {
|
|
return nil
|
|
}
|
|
state, err := o.store.State()
|
|
if err == nil {
|
|
o.applyState(state)
|
|
if len(o.rdc) > 0 {
|
|
o.checkRedelivered()
|
|
}
|
|
}
|
|
return err
|
|
}
|
|
|
|
// Apply the consumer stored state.
|
|
// Lock should be held.
|
|
func (o *consumer) applyState(state *ConsumerState) {
|
|
if state == nil {
|
|
return
|
|
}
|
|
|
|
o.sseq = state.Delivered.Stream + 1
|
|
o.dseq = state.Delivered.Consumer + 1
|
|
o.adflr = state.AckFloor.Consumer
|
|
o.asflr = state.AckFloor.Stream
|
|
o.pending = state.Pending
|
|
o.rdc = state.Redelivered
|
|
|
|
// Setup tracking timer if we have restored pending.
|
|
if o.isLeader() && len(o.pending) > 0 {
|
|
// This is on startup or leader change. We want to check pending
|
|
// sooner in case there are inconsistencies etc. Pick between 500ms - 1.5s
|
|
delay := 500*time.Millisecond + time.Duration(rand.Int63n(1000))*time.Millisecond
|
|
|
|
// If normal is lower than this just use that.
|
|
if o.cfg.AckWait < delay {
|
|
delay = o.ackWait(0)
|
|
}
|
|
o.resetPtmr(delay)
|
|
}
|
|
}
|
|
|
|
// Sets our store state from another source. Used in clustered mode on snapshot restore.
|
|
// Lock should be held.
|
|
func (o *consumer) setStoreState(state *ConsumerState) error {
|
|
if state == nil || o.store == nil {
|
|
return nil
|
|
}
|
|
err := o.store.Update(state)
|
|
if err == nil {
|
|
o.applyState(state)
|
|
} else if err == ErrStoreOldUpdate {
|
|
// Our store already has a newer state, which is normal during recovery
|
|
// when the consumer was loaded from disk before the meta snapshot state
|
|
// was applied.
|
|
return nil
|
|
}
|
|
return err
|
|
}
|
|
|
|
// Update our state to the store.
|
|
func (o *consumer) writeStoreState() error {
|
|
o.mu.Lock()
|
|
defer o.mu.Unlock()
|
|
return o.writeStoreStateUnlocked()
|
|
}
|
|
|
|
// Update our state to the store.
|
|
// Lock should be held.
|
|
func (o *consumer) writeStoreStateUnlocked() error {
|
|
if o.store == nil {
|
|
return nil
|
|
}
|
|
state := ConsumerState{
|
|
Delivered: SequencePair{
|
|
Consumer: o.dseq - 1,
|
|
Stream: o.sseq - 1,
|
|
},
|
|
AckFloor: SequencePair{
|
|
Consumer: o.adflr,
|
|
Stream: o.asflr,
|
|
},
|
|
Pending: o.pending,
|
|
Redelivered: o.rdc,
|
|
}
|
|
return o.store.Update(&state)
|
|
}
|
|
|
|
// Returns an initial info. Only applicable for non-clustered consumers.
|
|
// We will clear after we return it, so one shot.
|
|
func (o *consumer) initialInfo() *ConsumerInfo {
|
|
o.mu.Lock()
|
|
ici := o.ici
|
|
o.ici = nil // gc friendly
|
|
o.mu.Unlock()
|
|
if ici == nil {
|
|
ici = o.info()
|
|
}
|
|
return ici
|
|
}
|
|
|
|
// Clears our initial info.
|
|
// Used when we have a leader change in cluster mode but do not send a response.
|
|
func (o *consumer) clearInitialInfo() {
|
|
o.mu.Lock()
|
|
o.ici = nil // gc friendly
|
|
o.mu.Unlock()
|
|
}
|
|
|
|
// Info returns our current consumer state.
|
|
func (o *consumer) info() *ConsumerInfo {
|
|
return o.infoWithSnap(false)
|
|
}
|
|
|
|
func (o *consumer) infoWithSnap(snap bool) *ConsumerInfo {
|
|
return o.infoWithSnapAndReply(snap, _EMPTY_)
|
|
}
|
|
|
|
func (o *consumer) infoWithSnapAndReply(snap bool, reply string) *ConsumerInfo {
|
|
o.mu.Lock()
|
|
mset := o.mset
|
|
if o.closed || mset == nil || mset.srv == nil {
|
|
o.mu.Unlock()
|
|
return nil
|
|
}
|
|
js := o.js
|
|
if js == nil {
|
|
o.mu.Unlock()
|
|
return nil
|
|
}
|
|
|
|
// Capture raftGroup.
|
|
var rg *raftGroup
|
|
if o.ca != nil {
|
|
rg = o.ca.Group
|
|
}
|
|
|
|
priorityGroups := []PriorityGroupState{}
|
|
// TODO(jrm): when we introduce supporting many priority groups, we need to update assigning `o.currentNuid` for each group.
|
|
if len(o.cfg.PriorityGroups) > 0 {
|
|
priorityGroups = append(priorityGroups, PriorityGroupState{
|
|
Group: o.cfg.PriorityGroups[0],
|
|
PinnedClientID: o.currentPinId,
|
|
PinnedTS: o.pinnedTS,
|
|
})
|
|
}
|
|
|
|
np, err := o.checkNumPending()
|
|
if err != nil {
|
|
o.mu.Unlock()
|
|
return nil
|
|
}
|
|
|
|
dseq, sseq := o.dseq, o.sseq
|
|
if dseq <= 0 {
|
|
dseq = 1
|
|
}
|
|
if sseq <= 0 {
|
|
sseq = 1
|
|
}
|
|
|
|
cfg := o.cfg
|
|
info := &ConsumerInfo{
|
|
Stream: o.stream,
|
|
Name: o.name,
|
|
Created: o.created,
|
|
Config: &cfg,
|
|
Delivered: SequenceInfo{
|
|
Consumer: dseq - 1,
|
|
Stream: sseq - 1,
|
|
},
|
|
AckFloor: SequenceInfo{
|
|
Consumer: o.adflr,
|
|
Stream: o.asflr,
|
|
},
|
|
NumAckPending: len(o.pending),
|
|
NumRedelivered: len(o.rdc),
|
|
NumPending: np,
|
|
PushBound: o.isPushMode() && o.active,
|
|
TimeStamp: time.Now().UTC(),
|
|
PriorityGroups: priorityGroups,
|
|
}
|
|
// Reset redelivered for MaxDeliver 1. Redeliveries are disabled so must not report it (is confusing otherwise).
|
|
// The state does still keep track of these messages.
|
|
if o.cfg.MaxDeliver == 1 {
|
|
info.NumRedelivered = 0
|
|
}
|
|
if o.cfg.PauseUntil != nil {
|
|
p := *o.cfg.PauseUntil
|
|
if info.Paused = time.Now().Before(p); info.Paused {
|
|
info.PauseRemaining = time.Until(p)
|
|
}
|
|
}
|
|
|
|
// We always need to pull certain data from our store.
|
|
if o.store != nil {
|
|
state, err := o.store.BorrowState()
|
|
if err != nil {
|
|
o.mu.Unlock()
|
|
return nil
|
|
}
|
|
|
|
// If we are the leader we could have o.sseq that is skipped ahead.
|
|
// To maintain consistency in reporting (e.g. jsz) we always take the state for our delivered/ackfloor stream sequence.
|
|
// Only use skipped ahead o.sseq if we're a new consumer and have not yet replicated this state yet.
|
|
leader := o.isLeader()
|
|
if !leader || o.store.HasState() {
|
|
info.Delivered.Consumer, info.Delivered.Stream = state.Delivered.Consumer, state.Delivered.Stream
|
|
}
|
|
info.AckFloor.Consumer, info.AckFloor.Stream = state.AckFloor.Consumer, state.AckFloor.Stream
|
|
if !leader {
|
|
info.NumAckPending = len(state.Pending)
|
|
info.NumRedelivered = len(state.Redelivered)
|
|
}
|
|
}
|
|
|
|
// Adjust active based on non-zero etc. Also make UTC here.
|
|
if !o.ldt.IsZero() {
|
|
ldt := o.ldt.UTC() // This copies as well.
|
|
info.Delivered.Last = &ldt
|
|
}
|
|
if !o.lat.IsZero() {
|
|
lat := o.lat.UTC() // This copies as well.
|
|
info.AckFloor.Last = &lat
|
|
}
|
|
|
|
// If we are a pull mode consumer, report on number of waiting requests.
|
|
if o.isPullMode() {
|
|
o.processWaiting(false)
|
|
info.NumWaiting = o.waiting.len()
|
|
}
|
|
// If we were asked to snapshot do so here.
|
|
if snap {
|
|
o.ici = info
|
|
}
|
|
sysc := o.sysc
|
|
o.mu.Unlock()
|
|
|
|
// Do cluster.
|
|
if rg != nil {
|
|
info.Cluster = js.clusterInfo(rg)
|
|
}
|
|
|
|
// If we have a reply subject send the response here.
|
|
if reply != _EMPTY_ && sysc != nil {
|
|
sysc.sendInternalMsg(reply, _EMPTY_, nil, info)
|
|
}
|
|
|
|
return info
|
|
}
|
|
|
|
// Will signal us that new messages are available. Will break out of waiting.
|
|
func (o *consumer) signalNewMessages() {
|
|
// Kick our new message channel
|
|
select {
|
|
case o.mch <- struct{}{}:
|
|
default:
|
|
}
|
|
}
|
|
|
|
// shouldSample lets us know if we are sampling metrics on acks.
|
|
func (o *consumer) shouldSample() bool {
|
|
switch {
|
|
case o.sfreq <= 0:
|
|
return false
|
|
case o.sfreq >= 100:
|
|
return true
|
|
}
|
|
|
|
// TODO(ripienaar) this is a tad slow so we need to rethink here, however this will only
|
|
// hit for those with sampling enabled and its not the default
|
|
return rand.Int31n(100) <= o.sfreq
|
|
}
|
|
|
|
func (o *consumer) sampleAck(sseq, dseq, dc uint64) {
|
|
if !o.shouldSample() {
|
|
return
|
|
}
|
|
|
|
now := time.Now().UTC()
|
|
unow := now.UnixNano()
|
|
|
|
e := JSConsumerAckMetric{
|
|
TypedEvent: TypedEvent{
|
|
Type: JSConsumerAckMetricType,
|
|
ID: nuid.Next(),
|
|
Time: now,
|
|
},
|
|
Stream: o.stream,
|
|
Consumer: o.name,
|
|
ConsumerSeq: dseq,
|
|
StreamSeq: sseq,
|
|
Delay: unow - o.pending[sseq].Timestamp,
|
|
Deliveries: dc,
|
|
Domain: o.srv.getOpts().JetStreamDomain,
|
|
}
|
|
|
|
o.sendAdvisory(o.ackEventT, e)
|
|
}
|
|
|
|
// Process an ACK.
|
|
// Returns `true` if the ack was processed in place and the sender can now respond
|
|
// to the client, or `false` if there was an error or the ack is replicated (in which
|
|
// case the reply will be sent later).
|
|
func (o *consumer) processAckMsg(sseq, dseq, dc uint64, reply string, doSample bool) bool {
|
|
return o.processAckMsgLocked(sseq, dseq, dc, reply, doSample, true)
|
|
}
|
|
|
|
func (o *consumer) processAckMsgLocked(sseq, dseq, dc uint64, reply string, doSample bool, needLock bool) bool {
|
|
lock := func() {
|
|
if needLock {
|
|
o.mu.Lock()
|
|
}
|
|
}
|
|
unlock := func() {
|
|
if needLock {
|
|
o.mu.Unlock()
|
|
}
|
|
}
|
|
lock()
|
|
if o.closed {
|
|
unlock()
|
|
return false
|
|
}
|
|
|
|
mset := o.mset
|
|
if mset == nil || mset.closed.Load() {
|
|
unlock()
|
|
return false
|
|
}
|
|
|
|
// Check if this ack is above the current pointer to our next to deliver.
|
|
// Ignore if it's a flow-controlled consumer, its state could end up further ahead
|
|
// since its state is not replicated before delivery.
|
|
if sseq >= o.sseq && !o.cfg.FlowControl {
|
|
// Let's make sure this is valid.
|
|
// This is only received on the consumer leader, so should never be higher
|
|
// than the last stream sequence. But could happen if we've just become
|
|
// consumer leader, and we are not up-to-date on the stream yet.
|
|
var ss StreamState
|
|
mset.store.FastState(&ss)
|
|
if sseq > ss.LastSeq {
|
|
o.srv.Warnf("JetStream consumer '%s > %s > %s' ACK sequence %d past last stream sequence of %d",
|
|
o.acc.Name, o.stream, o.name, sseq, ss.LastSeq)
|
|
// FIXME(dlc) - For 2.11 onwards should we return an error here to the caller?
|
|
}
|
|
// Even though another leader must have delivered a message with this sequence, we must not adjust
|
|
// the current pointer. This could otherwise result in a stuck consumer, where messages below this
|
|
// sequence can't be redelivered, and we'll have incorrect pending state and ack floors.
|
|
unlock()
|
|
return false
|
|
}
|
|
|
|
// Let the owning stream know if we are interest or workqueue retention based.
|
|
// If this consumer is clustered (o.node != nil) this will be handled by
|
|
// processReplicatedAck after the ack has propagated.
|
|
// If we're already holding the lock we can't ack in place, since that will
|
|
// violate lock ordering with respect to the stream.
|
|
ackInPlace := o.node == nil && o.retention != LimitsPolicy && needLock
|
|
|
|
var sgap, floor uint64
|
|
var needSignal bool
|
|
|
|
switch o.cfg.AckPolicy {
|
|
case AckExplicit:
|
|
if p, ok := o.pending[sseq]; ok {
|
|
if doSample {
|
|
o.sampleAck(sseq, dseq, dc)
|
|
}
|
|
if o.maxp > 0 && len(o.pending) >= o.maxp {
|
|
needSignal = true
|
|
}
|
|
delete(o.pending, sseq)
|
|
// Use the original deliver sequence from our pending record.
|
|
dseq = p.Sequence
|
|
|
|
// Only move floors if we matched an existing pending.
|
|
if len(o.pending) == 0 {
|
|
o.adflr = o.dseq - 1
|
|
o.asflr = o.sseq - 1
|
|
} else if dseq == o.adflr+1 {
|
|
o.adflr, o.asflr = dseq, sseq
|
|
for ss := sseq + 1; ss < o.sseq; ss++ {
|
|
if p, ok := o.pending[ss]; ok {
|
|
if p.Sequence > 0 {
|
|
o.adflr, o.asflr = p.Sequence-1, ss-1
|
|
}
|
|
break
|
|
}
|
|
}
|
|
}
|
|
}
|
|
delete(o.rdc, sseq)
|
|
o.removeFromRedeliverQueue(sseq)
|
|
case AckAll, AckFlowControl:
|
|
// no-op
|
|
if dseq <= o.adflr || sseq <= o.asflr {
|
|
unlock()
|
|
// Return true to let caller respond back to the client.
|
|
return true
|
|
}
|
|
if o.maxp > 0 && len(o.pending) >= o.maxp {
|
|
needSignal = true
|
|
}
|
|
sgap = sseq - o.asflr
|
|
floor = sseq // start at same and set lower as we go.
|
|
o.adflr, o.asflr = dseq, sseq
|
|
|
|
remove := func(seq uint64) {
|
|
delete(o.pending, seq)
|
|
delete(o.rdc, seq)
|
|
o.removeFromRedeliverQueue(seq)
|
|
if seq < floor {
|
|
floor = seq
|
|
}
|
|
}
|
|
// Determine if smarter to walk all of pending vs the sequence range.
|
|
if sgap > uint64(len(o.pending)) {
|
|
for seq := range o.pending {
|
|
if seq <= sseq {
|
|
remove(seq)
|
|
}
|
|
}
|
|
} else {
|
|
for seq := sseq; seq > sseq-sgap && len(o.pending) > 0; seq-- {
|
|
remove(seq)
|
|
}
|
|
}
|
|
case AckNone:
|
|
// FIXME(dlc) - This is error but do we care?
|
|
unlock()
|
|
return ackInPlace
|
|
}
|
|
|
|
// No ack replication, so we set reply to "" so that updateAcks does not
|
|
// send the reply. The caller will.
|
|
if ackInPlace {
|
|
reply = _EMPTY_
|
|
}
|
|
// Update underlying store.
|
|
o.updateAcks(dseq, sseq, reply)
|
|
unlock()
|
|
|
|
if ackInPlace {
|
|
if sgap > 1 {
|
|
// FIXME(dlc) - This can very inefficient, will need to fix.
|
|
for seq := sseq; seq >= floor; seq-- {
|
|
mset.ackMsg(o, seq)
|
|
}
|
|
} else {
|
|
mset.ackMsg(o, sseq)
|
|
}
|
|
}
|
|
|
|
// If we had max ack pending set and were at limit we need to unblock ourselves.
|
|
if needSignal {
|
|
o.signalNewMessages()
|
|
}
|
|
return ackInPlace
|
|
}
|
|
|
|
// Determine if this is a truly filtered consumer. Modern clients will place filtered subjects
|
|
// even if the stream only has a single non-wildcard subject designation.
|
|
// Read lock should be held.
|
|
func (o *consumer) isFiltered() bool {
|
|
if o.subjf == nil {
|
|
return false
|
|
}
|
|
// If we are here we want to check if the filtered subject is
|
|
// a direct match for our only listed subject.
|
|
mset := o.mset
|
|
if mset == nil {
|
|
return true
|
|
}
|
|
|
|
// Protect access to mset.cfg with the cfgMu mutex.
|
|
mset.cfgMu.RLock()
|
|
msetSubjects := mset.cfg.Subjects
|
|
mset.cfgMu.RUnlock()
|
|
|
|
// `isFiltered` need to be performant, so we do
|
|
// as any checks as possible to avoid unnecessary work.
|
|
// Here we avoid iteration over slices if there is only one subject in stream
|
|
// and one filter for the consumer.
|
|
if len(msetSubjects) == 1 && len(o.subjf) == 1 {
|
|
return msetSubjects[0] != o.subjf[0].subject
|
|
}
|
|
|
|
// if the list is not equal length, we can return early, as this is filtered.
|
|
if len(msetSubjects) != len(o.subjf) {
|
|
return true
|
|
}
|
|
|
|
// if in rare case scenario that user passed all stream subjects as consumer filters,
|
|
// we need to do a more expensive operation.
|
|
// reflect.DeepEqual would return false if the filters are the same, but in different order
|
|
// so it can't be used here.
|
|
cfilters := make(map[string]struct{}, len(o.subjf))
|
|
for _, val := range o.subjf {
|
|
cfilters[val.subject] = struct{}{}
|
|
}
|
|
for _, val := range msetSubjects {
|
|
if _, ok := cfilters[val]; !ok {
|
|
return true
|
|
}
|
|
}
|
|
return false
|
|
}
|
|
|
|
// Check if we need an ack for this store seq.
|
|
// This is called for interest based retention streams to remove messages.
|
|
func (o *consumer) needAck(sseq uint64, subj string) bool {
|
|
var needAck bool
|
|
var asflr, osseq uint64
|
|
var pending map[uint64]*Pending
|
|
var rdc map[uint64]uint64
|
|
|
|
o.mu.RLock()
|
|
defer o.mu.RUnlock()
|
|
|
|
isFiltered := o.isFiltered()
|
|
if isFiltered && o.mset == nil {
|
|
return false
|
|
}
|
|
|
|
// Check if we are filtered, and if so check if this is even applicable to us.
|
|
if isFiltered {
|
|
if subj == _EMPTY_ {
|
|
var err error
|
|
if subj, err = o.mset.store.SubjectForSeq(sseq); err != nil {
|
|
return false
|
|
}
|
|
}
|
|
if !o.isFilteredMatch(subj) {
|
|
return false
|
|
}
|
|
}
|
|
if o.isLeader() {
|
|
asflr, osseq = o.asflr, o.sseq
|
|
pending, rdc = o.pending, o.rdc
|
|
} else {
|
|
if o.store == nil {
|
|
return false
|
|
}
|
|
state, err := o.store.BorrowState()
|
|
if err != nil || state == nil {
|
|
// Fall back to what we track internally for now.
|
|
return sseq > o.asflr && !o.isFiltered()
|
|
}
|
|
// If loading state as here, the osseq is +1.
|
|
asflr, osseq, pending, rdc = state.AckFloor.Stream, state.Delivered.Stream+1, state.Pending, state.Redelivered
|
|
}
|
|
|
|
switch o.cfg.AckPolicy {
|
|
case AckNone, AckAll, AckFlowControl:
|
|
needAck = sseq > asflr
|
|
case AckExplicit:
|
|
if sseq > asflr {
|
|
if sseq >= osseq {
|
|
needAck = true
|
|
} else {
|
|
_, needAck = pending[sseq]
|
|
}
|
|
}
|
|
}
|
|
|
|
// Finally check if redelivery of this message is tracked.
|
|
// If the message is not pending, it should be preserved if it reached max delivery.
|
|
if !needAck {
|
|
_, needAck = rdc[sseq]
|
|
}
|
|
|
|
return needAck
|
|
}
|
|
|
|
type PriorityGroup struct {
|
|
Group string `json:"group,omitempty"`
|
|
MinPending int64 `json:"min_pending,omitempty"`
|
|
MinAckPending int64 `json:"min_ack_pending,omitempty"`
|
|
Id string `json:"id,omitempty"`
|
|
Priority int `json:"priority,omitempty"`
|
|
}
|
|
|
|
// Used in nextReqFromMsg, since the json.Unmarshal causes the request
|
|
// struct to escape to the heap always. This should reduce GC pressure.
|
|
var jsGetNextPool = sync.Pool{
|
|
New: func() any {
|
|
return &JSApiConsumerGetNextRequest{}
|
|
},
|
|
}
|
|
|
|
// Helper for the next message requests.
|
|
func nextReqFromMsg(msg []byte) (time.Time, int, int, bool, time.Duration, time.Time, *PriorityGroup, error) {
|
|
req := bytes.TrimSpace(msg)
|
|
|
|
switch {
|
|
case len(req) == 0:
|
|
return time.Time{}, 1, 0, false, 0, time.Time{}, nil, nil
|
|
|
|
case req[0] == '{':
|
|
cr := jsGetNextPool.Get().(*JSApiConsumerGetNextRequest)
|
|
defer func() {
|
|
*cr = JSApiConsumerGetNextRequest{}
|
|
jsGetNextPool.Put(cr)
|
|
}()
|
|
if err := json.Unmarshal(req, &cr); err != nil {
|
|
return time.Time{}, -1, 0, false, 0, time.Time{}, nil, err
|
|
}
|
|
var hbt time.Time
|
|
if cr.Heartbeat > 0 {
|
|
if cr.Heartbeat*2 > cr.Expires {
|
|
return time.Time{}, 1, 0, false, 0, time.Time{}, nil, errors.New("heartbeat value too large")
|
|
}
|
|
hbt = time.Now().Add(cr.Heartbeat)
|
|
}
|
|
priorityGroup := cr.PriorityGroup
|
|
if cr.Expires == time.Duration(0) {
|
|
return time.Time{}, cr.Batch, cr.MaxBytes, cr.NoWait, cr.Heartbeat, hbt, &priorityGroup, nil
|
|
}
|
|
return time.Now().Add(cr.Expires), cr.Batch, cr.MaxBytes, cr.NoWait, cr.Heartbeat, hbt, &priorityGroup, nil
|
|
default:
|
|
if n, err := strconv.Atoi(string(req)); err == nil {
|
|
return time.Time{}, n, 0, false, 0, time.Time{}, nil, nil
|
|
}
|
|
}
|
|
|
|
return time.Time{}, 1, 0, false, 0, time.Time{}, nil, nil
|
|
}
|
|
|
|
// Represents a request that is on the internal waiting queue
|
|
type waitingRequest struct {
|
|
next *waitingRequest
|
|
acc *Account
|
|
interest string
|
|
reply string
|
|
n int // For batching
|
|
d int // num delivered
|
|
b int // For max bytes tracking
|
|
expires time.Time
|
|
received time.Time
|
|
hb time.Duration
|
|
hbt time.Time
|
|
noWait bool
|
|
priorityGroup *PriorityGroup
|
|
}
|
|
|
|
// sync.Pool for waiting requests.
|
|
var wrPool = sync.Pool{
|
|
New: func() any {
|
|
return new(waitingRequest)
|
|
},
|
|
}
|
|
|
|
// Recycle this request. This request can not be accessed after this call.
|
|
func (wr *waitingRequest) recycleIfDone() bool {
|
|
if wr != nil && wr.n <= 0 {
|
|
wr.recycle()
|
|
return true
|
|
}
|
|
return false
|
|
}
|
|
|
|
// Force a recycle.
|
|
func (wr *waitingRequest) recycle() {
|
|
if wr != nil {
|
|
wr.next, wr.acc, wr.interest, wr.reply = nil, nil, _EMPTY_, _EMPTY_
|
|
wrPool.Put(wr)
|
|
}
|
|
}
|
|
|
|
// Represents an (optional) request timeout that's sent after waiting for replicated deliveries.
|
|
type waitingDelivery struct {
|
|
seq uint64
|
|
pn int // Pending messages.
|
|
pb int // Pending bytes.
|
|
}
|
|
|
|
// sync.Pool for waiting deliveries.
|
|
var wdPool = sync.Pool{
|
|
New: func() any {
|
|
return new(waitingDelivery)
|
|
},
|
|
}
|
|
|
|
// Force a recycle.
|
|
func (wd *waitingDelivery) recycle() {
|
|
if wd != nil {
|
|
wd.seq, wd.pn, wd.pb = 0, 0, 0
|
|
wdPool.Put(wd)
|
|
}
|
|
}
|
|
|
|
// waiting queue for requests that are waiting for new messages to arrive.
|
|
type waitQueue struct {
|
|
n, max int
|
|
last time.Time
|
|
head *waitingRequest
|
|
tail *waitingRequest
|
|
}
|
|
|
|
// Create a new ring buffer with at most max items.
|
|
func newWaitQueue(max int) *waitQueue {
|
|
return &waitQueue{max: max}
|
|
}
|
|
|
|
var (
|
|
errWaitQueueFull = errors.New("wait queue is full")
|
|
errWaitQueueNil = errors.New("wait queue is nil")
|
|
)
|
|
|
|
// insertSorted inserts wr at the correct position based on priority
|
|
func (wq *waitQueue) insertSorted(wr *waitingRequest) {
|
|
|
|
// Handle empty queue
|
|
if wq.head == nil {
|
|
wq.head = wr
|
|
wq.tail = wr
|
|
wr.next = nil
|
|
return
|
|
}
|
|
insertAtPosition(wr, wq)
|
|
}
|
|
|
|
func (wq *waitQueue) addPrioritized(wr *waitingRequest) error {
|
|
if wq == nil {
|
|
return errWaitQueueNil
|
|
}
|
|
if wq.isFull() {
|
|
return errWaitQueueFull
|
|
}
|
|
|
|
wq.insertSorted(wr)
|
|
wq.n++
|
|
wq.last = wr.received
|
|
return nil
|
|
}
|
|
|
|
// Adds in a new request.
|
|
func (wq *waitQueue) add(wr *waitingRequest) error {
|
|
if wq == nil {
|
|
return errWaitQueueNil
|
|
}
|
|
if wq.isFull() {
|
|
return errWaitQueueFull
|
|
}
|
|
if wq.head == nil {
|
|
wq.head = wr
|
|
} else {
|
|
wq.tail.next = wr
|
|
}
|
|
// Always set tail.
|
|
wq.tail = wr
|
|
// Make sure nil
|
|
wr.next = nil
|
|
|
|
// Track last active via when we receive a request.
|
|
wq.last = wr.received
|
|
wq.n++
|
|
return nil
|
|
}
|
|
|
|
func (wq *waitQueue) isFull() bool {
|
|
if wq == nil {
|
|
return false
|
|
}
|
|
return wq.n == wq.max
|
|
}
|
|
|
|
func (wq *waitQueue) isEmpty() bool {
|
|
if wq == nil {
|
|
return true
|
|
}
|
|
return wq.n == 0
|
|
}
|
|
|
|
func (wq *waitQueue) len() int {
|
|
if wq == nil {
|
|
return 0
|
|
}
|
|
return wq.n
|
|
}
|
|
|
|
// Peek will return the next request waiting or nil if empty.
|
|
func (wq *waitQueue) peek() *waitingRequest {
|
|
if wq == nil {
|
|
return nil
|
|
}
|
|
return wq.head
|
|
}
|
|
|
|
func (wq *waitQueue) cycle() {
|
|
wr := wq.peek()
|
|
if wr != nil {
|
|
// Always remove current now on a pop, and move to end if still valid.
|
|
// If we were the only one don't need to remove since this can be a no-op.
|
|
wq.removeCurrent()
|
|
wq.add(wr)
|
|
}
|
|
}
|
|
|
|
func (wq *waitQueue) popOrPopAndRequeue(priority PriorityPolicy) *waitingRequest {
|
|
if wq == nil || wq.head == nil {
|
|
return nil
|
|
}
|
|
|
|
if priority == PriorityPrioritized {
|
|
return wq.popAndRequeue()
|
|
}
|
|
return wq.pop()
|
|
}
|
|
|
|
// pop will return the next request and move the read cursor.
|
|
// This will now place a request that still has pending items at the ends of the list.
|
|
func (wq *waitQueue) pop() *waitingRequest {
|
|
wr := wq.peek()
|
|
if wr != nil {
|
|
wr.d++
|
|
wr.n--
|
|
// Always remove current now on a pop, and move to end if still valid.
|
|
// If we were the only one don't need to remove since this can be a no-op.
|
|
if wr.n > 0 && wq.n > 1 {
|
|
wq.removeCurrent()
|
|
wq.add(wr)
|
|
} else if wr.n <= 0 {
|
|
wq.removeCurrent()
|
|
}
|
|
}
|
|
return wr
|
|
}
|
|
|
|
// popAndRequeue pops the head element and requeues it at the end of its priority group.
|
|
// This maintains FIFO order within the same priority level.
|
|
func (wq *waitQueue) popAndRequeue() *waitingRequest {
|
|
if wq == nil || wq.head == nil {
|
|
return nil
|
|
}
|
|
|
|
// Save the head
|
|
wr := wq.head
|
|
|
|
if wr == nil {
|
|
return wr
|
|
}
|
|
|
|
wr.d++
|
|
wr.n--
|
|
|
|
if wr.n > 0 && wq.n > 1 {
|
|
if wr.next == nil {
|
|
return wr
|
|
}
|
|
wq.head = wq.head.next
|
|
wr.next = nil
|
|
|
|
insertAtPosition(wr, wq)
|
|
|
|
} else if wr.n <= 0 {
|
|
wq.removeCurrent()
|
|
return wr
|
|
|
|
}
|
|
|
|
return wr
|
|
}
|
|
|
|
func insertAtPosition(wr *waitingRequest, wq *waitQueue) {
|
|
priority := math.MaxInt32
|
|
if wr.priorityGroup != nil {
|
|
priority = wr.priorityGroup.Priority
|
|
}
|
|
|
|
var prev *waitingRequest
|
|
current := wq.head
|
|
for current != nil {
|
|
currentPriority := math.MaxInt32
|
|
if current.priorityGroup != nil {
|
|
currentPriority = current.priorityGroup.Priority
|
|
}
|
|
if currentPriority > priority {
|
|
break
|
|
}
|
|
prev = current
|
|
current = current.next
|
|
}
|
|
|
|
if prev == nil {
|
|
// All remaining elements have higher priority
|
|
wr.next = wq.head
|
|
wq.head = wr
|
|
} else {
|
|
wr.next = prev.next
|
|
prev.next = wr
|
|
if wr.next == nil {
|
|
wq.tail = wr
|
|
}
|
|
}
|
|
}
|
|
|
|
// Removes the current read pointer (head FIFO) entry.
|
|
func (wq *waitQueue) removeCurrent() {
|
|
wq.remove(nil, wq.head)
|
|
}
|
|
|
|
// Remove the wr element from the wait queue.
|
|
func (wq *waitQueue) remove(pre, wr *waitingRequest) {
|
|
if wr == nil {
|
|
return
|
|
}
|
|
if pre != nil {
|
|
pre.next = wr.next
|
|
} else if wr == wq.head {
|
|
// We are removing head here.
|
|
wq.head = wr.next
|
|
}
|
|
// Check if wr was our tail.
|
|
if wr == wq.tail {
|
|
// Check if we need to assign to pre.
|
|
if wr.next == nil {
|
|
wq.tail = pre
|
|
} else {
|
|
wq.tail = wr.next
|
|
}
|
|
}
|
|
wq.n--
|
|
}
|
|
|
|
// Return the map of pending requests keyed by the reply subject.
|
|
// No-op if push consumer or invalid etc.
|
|
func (o *consumer) pendingRequests() map[string]*waitingRequest {
|
|
if o.waiting == nil {
|
|
return nil
|
|
}
|
|
wq, m := o.waiting, make(map[string]*waitingRequest)
|
|
for wr := wq.head; wr != nil; wr = wr.next {
|
|
m[wr.reply] = wr
|
|
}
|
|
|
|
return m
|
|
}
|
|
|
|
func (o *consumer) setPinnedTimer(priorityGroup string) {
|
|
if o.pinnedTtl != nil {
|
|
o.pinnedTtl.Reset(o.cfg.PinnedTTL)
|
|
} else {
|
|
o.pinnedTtl = time.AfterFunc(o.cfg.PinnedTTL, func() {
|
|
o.mu.Lock()
|
|
// Skip if already unset.
|
|
if o.currentPinId == _EMPTY_ {
|
|
o.mu.Unlock()
|
|
return
|
|
}
|
|
o.unassignPinId()
|
|
o.sendUnpinnedAdvisoryLocked(priorityGroup, "timeout")
|
|
o.mu.Unlock()
|
|
o.signalNewMessages()
|
|
})
|
|
}
|
|
}
|
|
|
|
// Lock should be held.
|
|
func (o *consumer) assignNewPinId(wr *waitingRequest) {
|
|
if wr.priorityGroup == nil || wr.priorityGroup.Group == _EMPTY_ {
|
|
return
|
|
}
|
|
o.currentPinId = nuid.Next()
|
|
o.pinnedTS = time.Now().UTC()
|
|
wr.priorityGroup.Id = o.currentPinId
|
|
o.setPinnedTimer(wr.priorityGroup.Group)
|
|
o.sendPinnedAdvisoryLocked(wr.priorityGroup.Group)
|
|
}
|
|
|
|
// Lock should be held.
|
|
func (o *consumer) unassignPinId() {
|
|
o.currentPinId = _EMPTY_
|
|
o.pinnedTS = time.Time{}
|
|
if o.pinnedTtl != nil {
|
|
o.pinnedTtl.Stop()
|
|
o.pinnedTtl = nil
|
|
}
|
|
}
|
|
|
|
// Return next waiting request. This will check for expirations but not noWait or interest.
|
|
// That will be handled by processWaiting.
|
|
// Lock should be held.
|
|
func (o *consumer) nextWaiting(sz int) *waitingRequest {
|
|
if o.waiting == nil || o.waiting.isEmpty() {
|
|
return nil
|
|
}
|
|
|
|
// Check if server needs to assign a new pin id.
|
|
needNewPin := o.currentPinId == _EMPTY_ && o.cfg.PriorityPolicy == PriorityPinnedClient
|
|
|
|
numCycled := 0
|
|
for wr := o.waiting.peek(); !o.waiting.isEmpty(); wr = o.waiting.peek() {
|
|
if wr == nil {
|
|
break
|
|
}
|
|
// Check if we have max bytes set.
|
|
if wr.b > 0 {
|
|
if sz <= wr.b {
|
|
wr.b -= sz
|
|
// If we are right now at zero, set batch to 1 to deliver this one but stop after.
|
|
if wr.b == 0 {
|
|
wr.n = 1
|
|
}
|
|
} else {
|
|
// Since we can't send that message to the requestor, we need to
|
|
// notify that we are closing the request.
|
|
const maxBytesT = "NATS/1.0 409 Message Size Exceeds MaxBytes\r\n%s: %d\r\n%s: %d\r\n\r\n"
|
|
hdr := fmt.Appendf(nil, maxBytesT, JSPullRequestPendingMsgs, wr.n, JSPullRequestPendingBytes, wr.b)
|
|
o.outq.send(newJSPubMsg(wr.reply, _EMPTY_, _EMPTY_, hdr, nil, nil, 0))
|
|
// Remove the current one, no longer valid due to max bytes limit.
|
|
o.waiting.removeCurrent()
|
|
if o.node != nil {
|
|
o.removeClusterPendingRequest(wr.reply)
|
|
}
|
|
wr.recycle()
|
|
continue
|
|
}
|
|
}
|
|
|
|
if wr.expires.IsZero() || time.Now().Before(wr.expires) {
|
|
if needNewPin {
|
|
if wr.priorityGroup.Id == _EMPTY_ {
|
|
o.assignNewPinId(wr)
|
|
} else {
|
|
// There is pin id set, but not a matching one. Send a notification to the client and remove the request.
|
|
// Probably this is the old pin id.
|
|
hdr := fmt.Appendf(nil, "NATS/1.0 423 Nats-Wrong-Pin-Id\r\n%s: %d\r\n%s: %d\r\n\r\n", JSPullRequestPendingMsgs, wr.n, JSPullRequestPendingBytes, wr.b)
|
|
o.outq.send(newJSPubMsg(wr.reply, _EMPTY_, _EMPTY_, hdr, nil, nil, 0))
|
|
o.waiting.removeCurrent()
|
|
if o.node != nil {
|
|
o.removeClusterPendingRequest(wr.reply)
|
|
}
|
|
wr.recycle()
|
|
continue
|
|
}
|
|
} else if o.currentPinId != _EMPTY_ {
|
|
// Check if we have a match on the currentNuid
|
|
if wr.priorityGroup != nil && wr.priorityGroup.Id == o.currentPinId {
|
|
// If we have a match, we do nothing here and will deliver the message later down the code path.
|
|
} else if wr.priorityGroup.Id == _EMPTY_ {
|
|
o.waiting.cycle()
|
|
numCycled++
|
|
if numCycled >= o.waiting.len() {
|
|
return nil
|
|
}
|
|
continue
|
|
} else {
|
|
// There is pin id set, but not a matching one. Send a notification to the client and remove the request.
|
|
hdr := fmt.Appendf(nil, "NATS/1.0 423 Nats-Wrong-Pin-Id\r\n%s: %d\r\n%s: %d\r\n\r\n", JSPullRequestPendingMsgs, wr.n, JSPullRequestPendingBytes, wr.b)
|
|
o.outq.send(newJSPubMsg(wr.reply, _EMPTY_, _EMPTY_, hdr, nil, nil, 0))
|
|
o.waiting.removeCurrent()
|
|
if o.node != nil {
|
|
o.removeClusterPendingRequest(wr.reply)
|
|
}
|
|
wr.recycle()
|
|
continue
|
|
}
|
|
}
|
|
|
|
if o.cfg.PriorityPolicy == PriorityOverflow {
|
|
if wr.priorityGroup != nil &&
|
|
// If both limits are zero we don't cycle and the request will be fulfilled.
|
|
(wr.priorityGroup.MinPending > 0 || wr.priorityGroup.MinAckPending > 0) &&
|
|
// We need to check o.npc+1, because before calling nextWaiting, we do o.npc--
|
|
// If one OR the other limit is exceeded, we want to fulfill the request.
|
|
// This is an inverted check. For clarity, we check the positive condition and negate.
|
|
!((wr.priorityGroup.MinPending > 0 && wr.priorityGroup.MinPending <= o.npc+1) ||
|
|
(wr.priorityGroup.MinAckPending > 0 && wr.priorityGroup.MinAckPending <= int64(len(o.pending)))) {
|
|
o.waiting.cycle()
|
|
numCycled++
|
|
// We're done cycling through the requests.
|
|
if numCycled >= o.waiting.len() {
|
|
return nil
|
|
}
|
|
continue
|
|
}
|
|
}
|
|
if wr.acc.sl.HasInterest(wr.interest) {
|
|
return o.waiting.popOrPopAndRequeue(o.cfg.PriorityPolicy)
|
|
} else if time.Since(wr.received) < defaultGatewayRecentSubExpiration && (o.srv.leafNodeEnabled || o.srv.gateway.enabled) {
|
|
return o.waiting.popOrPopAndRequeue(o.cfg.PriorityPolicy)
|
|
} else if o.srv.gateway.enabled && o.srv.hasGatewayInterest(wr.acc.Name, wr.interest) {
|
|
return o.waiting.popOrPopAndRequeue(o.cfg.PriorityPolicy)
|
|
}
|
|
} else {
|
|
// We do check for expiration in `processWaiting`, but it is possible to hit the expiry here, and not there.
|
|
rdWait := o.replicateDeliveries()
|
|
if rdWait {
|
|
// Check if we need to send the timeout after pending replicated deliveries, or can do so immediately.
|
|
if wd, ok := o.waitingDeliveries[wr.reply]; !ok {
|
|
rdWait = false
|
|
} else {
|
|
wd.pn, wd.pb = wr.n, wr.b
|
|
}
|
|
}
|
|
if !rdWait {
|
|
hdr := fmt.Appendf(nil, "NATS/1.0 408 Request Timeout\r\n%s: %d\r\n%s: %d\r\n\r\n", JSPullRequestPendingMsgs, wr.n, JSPullRequestPendingBytes, wr.b)
|
|
o.outq.send(newJSPubMsg(wr.reply, _EMPTY_, _EMPTY_, hdr, nil, nil, 0))
|
|
}
|
|
o.waiting.removeCurrent()
|
|
if o.node != nil {
|
|
o.removeClusterPendingRequest(wr.reply)
|
|
}
|
|
wr.recycle()
|
|
continue
|
|
|
|
}
|
|
if wr.interest != wr.reply {
|
|
const intExpT = "NATS/1.0 408 Interest Expired\r\n%s: %d\r\n%s: %d\r\n\r\n"
|
|
hdr := fmt.Appendf(nil, intExpT, JSPullRequestPendingMsgs, wr.n, JSPullRequestPendingBytes, wr.b)
|
|
o.outq.send(newJSPubMsg(wr.reply, _EMPTY_, _EMPTY_, hdr, nil, nil, 0))
|
|
}
|
|
// Remove the current one, no longer valid.
|
|
o.waiting.removeCurrent()
|
|
if o.node != nil {
|
|
o.removeClusterPendingRequest(wr.reply)
|
|
}
|
|
wr.recycle()
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// Next message request.
|
|
type nextMsgReq struct {
|
|
reply string
|
|
msg []byte
|
|
}
|
|
|
|
var nextMsgReqPool sync.Pool
|
|
|
|
func newNextMsgReq(reply string, msg []byte) *nextMsgReq {
|
|
var nmr *nextMsgReq
|
|
m := nextMsgReqPool.Get()
|
|
if m != nil {
|
|
nmr = m.(*nextMsgReq)
|
|
} else {
|
|
nmr = &nextMsgReq{}
|
|
}
|
|
// When getting something from a pool it is critical that all fields are
|
|
// initialized. Doing this way guarantees that if someone adds a field to
|
|
// the structure, the compiler will fail the build if this line is not updated.
|
|
(*nmr) = nextMsgReq{reply, msg}
|
|
return nmr
|
|
}
|
|
|
|
func (nmr *nextMsgReq) returnToPool() {
|
|
if nmr == nil {
|
|
return
|
|
}
|
|
nmr.reply, nmr.msg = _EMPTY_, nil
|
|
nextMsgReqPool.Put(nmr)
|
|
}
|
|
|
|
// processNextMsgReq will process a request for the next message available. A nil message payload means deliver
|
|
// a single message. If the payload is a formal request or a number parseable with Atoi(), then we will send a
|
|
// batch of messages without requiring another request to this endpoint, or an ACK.
|
|
func (o *consumer) processNextMsgReq(_ *subscription, c *client, _ *Account, _, reply string, rmsg []byte) {
|
|
if reply == _EMPTY_ {
|
|
return
|
|
}
|
|
|
|
// Short circuit error here.
|
|
if o.nextMsgReqs == nil {
|
|
hdr := []byte("NATS/1.0 409 Consumer is push based\r\n\r\n")
|
|
o.outq.send(newJSPubMsg(reply, _EMPTY_, _EMPTY_, hdr, nil, nil, 0))
|
|
return
|
|
}
|
|
|
|
hdr, msg := c.msgParts(rmsg)
|
|
if errorOnRequiredApiLevel(hdr) {
|
|
hdr = []byte("NATS/1.0 412 Required Api Level\r\n\r\n")
|
|
o.outq.send(newJSPubMsg(reply, _EMPTY_, _EMPTY_, hdr, nil, nil, 0))
|
|
return
|
|
}
|
|
o.nextMsgReqs.push(newNextMsgReq(reply, copyBytes(msg)))
|
|
}
|
|
|
|
// processResetReq will reset a consumer to a new starting sequence.
|
|
func (o *consumer) processResetReq(_ *subscription, c *client, a *Account, _, reply string, rmsg []byte) {
|
|
if reply == _EMPTY_ {
|
|
return
|
|
}
|
|
|
|
s := o.srv
|
|
var resp = JSApiConsumerResetResponse{ApiResponse: ApiResponse{Type: JSApiConsumerResetResponseType}}
|
|
|
|
hdr, msg := c.msgParts(rmsg)
|
|
if errorOnRequiredApiLevel(hdr) {
|
|
resp.Error = NewJSRequiredApiLevelError()
|
|
s.sendInternalAccountMsg(a, reply, s.jsonResponse(&resp))
|
|
return
|
|
}
|
|
|
|
// An empty message resets back to the ack floor, otherwise a custom sequence can be used.
|
|
var req JSApiConsumerResetRequest
|
|
if len(msg) > 0 {
|
|
if err := json.Unmarshal(msg, &req); err != nil {
|
|
resp.Error = NewJSInvalidJSONError(err)
|
|
s.sendInternalAccountMsg(a, reply, s.jsonResponse(&resp))
|
|
return
|
|
}
|
|
}
|
|
resetSeq, canRespond, err := o.resetStartingSeq(req.Seq, reply, false)
|
|
if err != nil {
|
|
resp.Error = NewJSConsumerInvalidResetError(err)
|
|
s.sendInternalAccountMsg(a, reply, s.jsonResponse(&resp))
|
|
} else if canRespond {
|
|
resp.ConsumerInfo = setDynamicConsumerInfoMetadata(o.info())
|
|
resp.ResetSeq = resetSeq
|
|
s.sendInternalAccountMsg(a, reply, s.jsonResponse(&resp))
|
|
}
|
|
}
|
|
|
|
func (o *consumer) processNextMsgRequest(reply string, msg []byte) {
|
|
o.mu.Lock()
|
|
defer o.mu.Unlock()
|
|
|
|
mset := o.mset
|
|
if mset == nil {
|
|
return
|
|
}
|
|
|
|
sendErr := func(status int, description string) {
|
|
hdr := fmt.Appendf(nil, "NATS/1.0 %d %s\r\n\r\n", status, description)
|
|
o.outq.send(newJSPubMsg(reply, _EMPTY_, _EMPTY_, hdr, nil, nil, 0))
|
|
}
|
|
|
|
if o.isPushMode() || o.waiting == nil {
|
|
sendErr(409, "Consumer is push based")
|
|
return
|
|
}
|
|
|
|
// Check payload here to see if they sent in batch size or a formal request.
|
|
expires, batchSize, maxBytes, noWait, hb, hbt, priorityGroup, err := nextReqFromMsg(msg)
|
|
if err != nil {
|
|
sendErr(400, fmt.Sprintf("Bad Request - %v", err))
|
|
return
|
|
}
|
|
|
|
// Check for request limits
|
|
if o.cfg.MaxRequestBatch > 0 && batchSize > o.cfg.MaxRequestBatch {
|
|
sendErr(409, fmt.Sprintf("Exceeded MaxRequestBatch of %d", o.cfg.MaxRequestBatch))
|
|
return
|
|
}
|
|
|
|
if !expires.IsZero() && o.cfg.MaxRequestExpires > 0 && expires.After(time.Now().Add(o.cfg.MaxRequestExpires)) {
|
|
sendErr(409, fmt.Sprintf("Exceeded MaxRequestExpires of %v", o.cfg.MaxRequestExpires))
|
|
return
|
|
}
|
|
|
|
if maxBytes > 0 && o.cfg.MaxRequestMaxBytes > 0 && maxBytes > o.cfg.MaxRequestMaxBytes {
|
|
sendErr(409, fmt.Sprintf("Exceeded MaxRequestMaxBytes of %v", o.cfg.MaxRequestMaxBytes))
|
|
return
|
|
}
|
|
|
|
if priorityGroup != nil {
|
|
if (priorityGroup.MinPending != 0 || priorityGroup.MinAckPending != 0) && o.cfg.PriorityPolicy != PriorityOverflow {
|
|
sendErr(400, "Bad Request - Not a Overflow Priority consumer")
|
|
}
|
|
|
|
if priorityGroup.Id != _EMPTY_ && o.cfg.PriorityPolicy != PriorityPinnedClient {
|
|
sendErr(400, "Bad Request - Not a Pinned Client Priority consumer")
|
|
}
|
|
if priorityGroup.Priority < 0 || priorityGroup.Priority > 9 {
|
|
sendErr(400, "Bad Request - Priority must be between 0 and 9")
|
|
return
|
|
}
|
|
}
|
|
|
|
if priorityGroup != nil && o.cfg.PriorityPolicy != PriorityNone {
|
|
if priorityGroup.Group == _EMPTY_ {
|
|
sendErr(400, "Bad Request - Priority Group missing")
|
|
return
|
|
}
|
|
if !slices.Contains(o.cfg.PriorityGroups, priorityGroup.Group) {
|
|
sendErr(400, "Bad Request - Invalid Priority Group")
|
|
return
|
|
}
|
|
|
|
if o.currentPinId != _EMPTY_ {
|
|
if priorityGroup.Id == o.currentPinId {
|
|
o.setPinnedTimer(priorityGroup.Group)
|
|
} else if priorityGroup.Id != _EMPTY_ {
|
|
sendErr(423, "Nats-Pin-Id mismatch")
|
|
return
|
|
}
|
|
}
|
|
}
|
|
|
|
// If we have the max number of requests already pending try to expire.
|
|
if o.waiting.isFull() {
|
|
// Try to expire some of the requests.
|
|
// We do not want to push too hard here so at maximum process once per sec.
|
|
if time.Since(o.lwqic) > time.Second {
|
|
o.processWaiting(false)
|
|
}
|
|
}
|
|
|
|
// If the request is for noWait and we have pending requests already, check if we have room.
|
|
if noWait {
|
|
msgsPending := o.numPending() + uint64(len(o.rdq))
|
|
// If no pending at all, decide what to do with request.
|
|
// If no expires was set then fail.
|
|
if msgsPending == 0 && expires.IsZero() {
|
|
o.waiting.last = time.Now()
|
|
sendErr(404, "No Messages")
|
|
return
|
|
}
|
|
if msgsPending > 0 {
|
|
_, _, batchPending, _ := o.processWaiting(false)
|
|
if msgsPending < uint64(batchPending) {
|
|
o.waiting.last = time.Now()
|
|
sendErr(408, "Requests Pending")
|
|
return
|
|
}
|
|
}
|
|
// If we are here this should be considered a one-shot situation.
|
|
// We will wait for expires but will return as soon as we have any messages.
|
|
}
|
|
|
|
// If we receive this request though an account export, we need to track that interest subject and account.
|
|
acc, interest := trackDownAccountAndInterest(o.acc, reply)
|
|
|
|
// Create a waiting request.
|
|
wr := wrPool.Get().(*waitingRequest)
|
|
wr.acc, wr.interest, wr.reply, wr.n, wr.d, wr.noWait, wr.expires, wr.hb, wr.hbt, wr.priorityGroup = acc, interest, reply, batchSize, 0, noWait, expires, hb, hbt, priorityGroup
|
|
wr.b = maxBytes
|
|
wr.received = time.Now()
|
|
|
|
if o.cfg.PriorityPolicy == PriorityPrioritized {
|
|
if err := o.waiting.addPrioritized(wr); err != nil {
|
|
if hb == 0 {
|
|
sendErr(409, "Exceeded MaxWaiting")
|
|
}
|
|
wr.recycle()
|
|
return
|
|
}
|
|
} else {
|
|
if err := o.waiting.add(wr); err != nil {
|
|
// If the client has a heartbeat interval set, don't bother responding with a 409,
|
|
// otherwise we can end up in a hot loop with the client re-requesting instead of
|
|
// waiting for the missing heartbeats instead and retrying.
|
|
if hb == 0 {
|
|
sendErr(409, "Exceeded MaxWaiting")
|
|
}
|
|
wr.recycle()
|
|
return
|
|
}
|
|
}
|
|
o.signalNewMessages()
|
|
// If we are clustered update our followers about this request.
|
|
if o.node != nil {
|
|
o.addClusterPendingRequest(wr.reply)
|
|
}
|
|
}
|
|
|
|
func trackDownAccountAndInterest(acc *Account, interest string) (*Account, string) {
|
|
for strings.HasPrefix(interest, replyPrefix) {
|
|
oa := acc
|
|
oa.mu.RLock()
|
|
if oa.exports.responses == nil {
|
|
oa.mu.RUnlock()
|
|
break
|
|
}
|
|
si := oa.exports.responses[interest]
|
|
if si == nil {
|
|
oa.mu.RUnlock()
|
|
break
|
|
}
|
|
acc, interest = si.acc, si.to
|
|
oa.mu.RUnlock()
|
|
}
|
|
return acc, interest
|
|
}
|
|
|
|
// Return current delivery count for a given sequence.
|
|
func (o *consumer) deliveryCount(seq uint64) uint64 {
|
|
if o.rdc == nil {
|
|
return 1
|
|
}
|
|
if dc := o.rdc[seq]; dc >= 1 {
|
|
return dc
|
|
}
|
|
return 1
|
|
}
|
|
|
|
// Increase the delivery count for this message.
|
|
// ONLY used on redelivery semantics.
|
|
// Lock should be held.
|
|
func (o *consumer) incDeliveryCount(sseq uint64) uint64 {
|
|
if o.rdc == nil {
|
|
o.rdc = make(map[uint64]uint64)
|
|
}
|
|
o.rdc[sseq] += 1
|
|
return o.rdc[sseq] + 1
|
|
}
|
|
|
|
// Used if we have to adjust on failed delivery or bad lookups.
|
|
// Those failed attempts should not increase deliver count.
|
|
// Lock should be held.
|
|
func (o *consumer) decDeliveryCount(sseq uint64) {
|
|
if o.rdc == nil {
|
|
o.rdc = make(map[uint64]uint64)
|
|
}
|
|
o.rdc[sseq] -= 1
|
|
}
|
|
|
|
// send a delivery exceeded advisory.
|
|
func (o *consumer) notifyDeliveryExceeded(sseq, dc uint64) {
|
|
e := JSConsumerDeliveryExceededAdvisory{
|
|
TypedEvent: TypedEvent{
|
|
Type: JSConsumerDeliveryExceededAdvisoryType,
|
|
ID: nuid.Next(),
|
|
Time: time.Now().UTC(),
|
|
},
|
|
Stream: o.stream,
|
|
Consumer: o.name,
|
|
StreamSeq: sseq,
|
|
Deliveries: dc,
|
|
Domain: o.srv.getOpts().JetStreamDomain,
|
|
}
|
|
|
|
o.sendAdvisory(o.deliveryExcEventT, e)
|
|
}
|
|
|
|
// Check if the candidate subject matches a filter if its present.
|
|
// Lock should be held.
|
|
func (o *consumer) isFilteredMatch(subj string) bool {
|
|
// No filter is automatic match.
|
|
if o.subjf == nil {
|
|
return true
|
|
}
|
|
for _, filter := range o.subjf {
|
|
if !filter.hasWildcard && subj == filter.subject {
|
|
return true
|
|
}
|
|
}
|
|
// It's quicker to first check for non-wildcard filters, then
|
|
// iterate again to check for subset match.
|
|
tsa := [32]string{}
|
|
tts := tokenizeSubjectIntoSlice(tsa[:0], subj)
|
|
for _, filter := range o.subjf {
|
|
if isSubsetMatchTokenized(tts, filter.tokenizedSubject) {
|
|
return true
|
|
}
|
|
}
|
|
return false
|
|
}
|
|
|
|
// Check if the candidate filter subject is equal to or a subset match
|
|
// of one of the filter subjects.
|
|
// Lock should be held.
|
|
func (o *consumer) isEqualOrSubsetMatch(subj string) bool {
|
|
for _, filter := range o.subjf {
|
|
if !filter.hasWildcard && subj == filter.subject {
|
|
return true
|
|
}
|
|
}
|
|
tsa := [32]string{}
|
|
tts := tokenizeSubjectIntoSlice(tsa[:0], subj)
|
|
for _, filter := range o.subjf {
|
|
if isSubsetMatchTokenized(filter.tokenizedSubject, tts) {
|
|
return true
|
|
}
|
|
}
|
|
return false
|
|
}
|
|
|
|
var (
|
|
errMaxAckPending = errors.New("max ack pending reached")
|
|
errBadConsumer = errors.New("consumer not valid")
|
|
errNoInterest = errors.New("consumer requires interest for delivery subject when ephemeral")
|
|
)
|
|
|
|
// Get next available message from underlying store.
|
|
// Is partition aware and redeliver aware.
|
|
// Lock should be held.
|
|
func (o *consumer) getNextMsg() (*jsPubMsg, uint64, error) {
|
|
if o.mset == nil || o.mset.store == nil {
|
|
return nil, 0, errBadConsumer
|
|
}
|
|
// Process redelivered messages before looking at possibly "skip list" (deliver last per subject)
|
|
if o.hasRedeliveries() {
|
|
var seq, dc uint64
|
|
for seq = o.getNextToRedeliver(); seq > 0; seq = o.getNextToRedeliver() {
|
|
dc = o.incDeliveryCount(seq)
|
|
if o.maxdc > 0 && dc > o.maxdc {
|
|
// Only send once
|
|
if dc == o.maxdc+1 {
|
|
o.notifyDeliveryExceeded(seq, dc-1)
|
|
}
|
|
// Make sure to remove from pending.
|
|
if p, ok := o.pending[seq]; ok && p != nil {
|
|
delete(o.pending, seq)
|
|
o.updateDelivered(p.Sequence, seq, dc, p.Timestamp)
|
|
}
|
|
continue
|
|
}
|
|
pmsg := getJSPubMsgFromPool()
|
|
sm, err := o.mset.store.LoadMsg(seq, &pmsg.StoreMsg)
|
|
if sm == nil || err != nil {
|
|
pmsg.returnToPool()
|
|
pmsg = nil
|
|
// Adjust back deliver count.
|
|
o.decDeliveryCount(seq)
|
|
}
|
|
// 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 would normally run. However,
|
|
// if we get here this likely didn't fire, or we are replicated and changed leaders.
|
|
// That will correct the pending state and delivery/ack floors, so just skip here.
|
|
pmsg.returnToPool()
|
|
pmsg = nil
|
|
if p, ok := o.pending[seq]; ok {
|
|
o.processTermLocked(seq, p.Sequence, dc-1, ackTermUnackedLimitsReason, _EMPTY_, false)
|
|
}
|
|
continue
|
|
}
|
|
return pmsg, dc, err
|
|
}
|
|
}
|
|
|
|
// Check if we have max pending.
|
|
if o.maxp > 0 && len(o.pending) >= o.maxp {
|
|
// maxp only set when ack policy != AckNone and user set MaxAckPending
|
|
// Stall if we have hit max pending.
|
|
return nil, 0, errMaxAckPending
|
|
}
|
|
|
|
if o.hasSkipListPending() {
|
|
seq := o.lss.seqs[0]
|
|
if len(o.lss.seqs) == 1 {
|
|
o.sseq = o.lss.resume
|
|
o.lss = nil
|
|
o.updateSkipped(o.sseq)
|
|
} else {
|
|
o.lss.seqs = o.lss.seqs[1:]
|
|
o.sseq = seq
|
|
}
|
|
pmsg := getJSPubMsgFromPool()
|
|
sm, err := o.mset.store.LoadMsg(seq, &pmsg.StoreMsg)
|
|
if sm == nil || err != nil {
|
|
pmsg.returnToPool()
|
|
pmsg = nil
|
|
}
|
|
o.sseq++
|
|
return pmsg, 1, err
|
|
}
|
|
|
|
var sseq uint64
|
|
var err error
|
|
var sm *StoreMsg
|
|
var pmsg = getJSPubMsgFromPool()
|
|
|
|
// Grab next message applicable to us.
|
|
filters, subjf, fseq := o.filters, o.subjf, o.sseq
|
|
// Check if we are multi-filtered or not.
|
|
if filters != nil {
|
|
sm, sseq, err = o.mset.store.LoadNextMsgMulti(filters, fseq, &pmsg.StoreMsg)
|
|
} else if len(subjf) > 0 { // Means single filtered subject since o.filters means > 1.
|
|
filter, wc := subjf[0].subject, subjf[0].hasWildcard
|
|
sm, sseq, err = o.mset.store.LoadNextMsg(filter, wc, fseq, &pmsg.StoreMsg)
|
|
} else {
|
|
// No filter here.
|
|
sm, sseq, err = o.mset.store.LoadNextMsg(_EMPTY_, false, fseq, &pmsg.StoreMsg)
|
|
}
|
|
if sm == nil {
|
|
pmsg.returnToPool()
|
|
pmsg = nil
|
|
}
|
|
// Check if we should move our o.sseq.
|
|
if sseq >= o.sseq {
|
|
// If we are moving step by step then sseq == o.sseq.
|
|
// If we have jumped we should update skipped for other replicas.
|
|
if sseq != o.sseq && err == ErrStoreEOF {
|
|
o.updateSkipped(sseq + 1)
|
|
}
|
|
o.sseq = sseq + 1
|
|
}
|
|
return pmsg, 1, err
|
|
}
|
|
|
|
// Will check for expiration and lack of interest on waiting requests.
|
|
// Will also do any heartbeats and return the next expiration or HB interval.
|
|
func (o *consumer) processWaiting(eos bool) (int, int, int, time.Time) {
|
|
var fexp time.Time
|
|
if o.srv == nil || o.waiting.isEmpty() {
|
|
return 0, 0, 0, fexp
|
|
}
|
|
// Mark our last check time.
|
|
o.lwqic = time.Now()
|
|
|
|
var expired, brp int
|
|
s, now := o.srv, time.Now()
|
|
|
|
wq := o.waiting
|
|
remove := func(pre, wr *waitingRequest) *waitingRequest {
|
|
expired++
|
|
if o.node != nil {
|
|
o.removeClusterPendingRequest(wr.reply)
|
|
}
|
|
next := wr.next
|
|
wq.remove(pre, wr)
|
|
wr.recycle()
|
|
return next
|
|
}
|
|
|
|
var pre *waitingRequest
|
|
for wr := wq.head; wr != nil; {
|
|
// Check expiration.
|
|
expires := !wr.expires.IsZero() && now.After(wr.expires)
|
|
if (eos && wr.noWait) || expires {
|
|
rdWait := o.replicateDeliveries()
|
|
if rdWait {
|
|
// Check if we need to send the timeout after pending replicated deliveries, or can do so immediately.
|
|
if wd, ok := o.waitingDeliveries[wr.reply]; !ok {
|
|
rdWait = false
|
|
} else {
|
|
wd.pn, wd.pb = wr.n, wr.b
|
|
}
|
|
// If we still need to wait for replicated deliveries, remove from waiting list.
|
|
if rdWait {
|
|
wr = remove(pre, wr)
|
|
continue
|
|
}
|
|
}
|
|
// Normally it's a timeout.
|
|
if expires {
|
|
hdr := fmt.Appendf(nil, "NATS/1.0 408 Request Timeout\r\n%s: %d\r\n%s: %d\r\n\r\n", JSPullRequestPendingMsgs, wr.n, JSPullRequestPendingBytes, wr.b)
|
|
o.outq.send(newJSPubMsg(wr.reply, _EMPTY_, _EMPTY_, hdr, nil, nil, 0))
|
|
wr = remove(pre, wr)
|
|
continue
|
|
} else if wr.expires.IsZero() || wr.d > 0 {
|
|
// But if we're NoWait without expiry, we've reached the end of the stream, and we've not delivered any messages.
|
|
// Return no messages instead, which is the same as if we'd rejected the pull request initially.
|
|
hdr := fmt.Appendf(nil, "NATS/1.0 404 No Messages\r\n\r\n")
|
|
o.outq.send(newJSPubMsg(wr.reply, _EMPTY_, _EMPTY_, hdr, nil, nil, 0))
|
|
wr = remove(pre, wr)
|
|
continue
|
|
}
|
|
}
|
|
// Now check interest.
|
|
interest := wr.acc.sl.HasInterest(wr.interest)
|
|
if !interest && (s.leafNodeEnabled || s.gateway.enabled) {
|
|
// If we are here check on gateways and leaf nodes (as they can mask gateways on the other end).
|
|
// If we have interest or the request is too young break and do not expire.
|
|
if time.Since(wr.received) < defaultGatewayRecentSubExpiration {
|
|
interest = true
|
|
} else if s.gateway.enabled && s.hasGatewayInterest(wr.acc.Name, wr.interest) {
|
|
interest = true
|
|
}
|
|
}
|
|
// Check if we have interest.
|
|
if !interest {
|
|
// No more interest here so go ahead and remove this one from our list.
|
|
wr = remove(pre, wr)
|
|
continue
|
|
}
|
|
|
|
// If interest, update batch pending requests counter and update fexp timer.
|
|
brp += wr.n
|
|
if !wr.hbt.IsZero() {
|
|
if now.After(wr.hbt) {
|
|
// Fire off a heartbeat here.
|
|
o.sendIdleHeartbeat(wr.reply)
|
|
// Update next HB.
|
|
wr.hbt = now.Add(wr.hb)
|
|
}
|
|
if fexp.IsZero() || wr.hbt.Before(fexp) {
|
|
fexp = wr.hbt
|
|
}
|
|
}
|
|
if !wr.expires.IsZero() && (fexp.IsZero() || wr.expires.Before(fexp)) {
|
|
fexp = wr.expires
|
|
}
|
|
// Update pre and wr here.
|
|
pre = wr
|
|
wr = wr.next
|
|
}
|
|
|
|
return expired, wq.len(), brp, fexp
|
|
}
|
|
|
|
// Will check to make sure those waiting still have registered interest.
|
|
func (o *consumer) checkWaitingForInterest() bool {
|
|
o.processWaiting(true)
|
|
return o.waiting.len() > 0
|
|
}
|
|
|
|
// Lock should be held.
|
|
func (o *consumer) hbTimer() (time.Duration, *time.Timer) {
|
|
if o.cfg.Heartbeat == 0 {
|
|
return 0, nil
|
|
}
|
|
return o.cfg.Heartbeat, time.NewTimer(o.cfg.Heartbeat)
|
|
}
|
|
|
|
// Check here for conditions when our ack floor may have drifted below the streams first sequence.
|
|
// In general this is accounted for in normal operations, but if the consumer misses the signal from
|
|
// the stream it will not clear the message and move the ack state.
|
|
// Should only be called from consumer leader.
|
|
func (o *consumer) checkAckFloor() {
|
|
o.mu.RLock()
|
|
mset, closed, asflr, numPending := o.mset, o.closed, o.asflr, len(o.pending)
|
|
o.mu.RUnlock()
|
|
|
|
if asflr == 0 || closed || mset == nil {
|
|
return
|
|
}
|
|
|
|
var ss StreamState
|
|
mset.store.FastState(&ss)
|
|
|
|
// If our floor is equal or greater that is normal and nothing for us to do.
|
|
if ss.FirstSeq == 0 || asflr >= ss.FirstSeq-1 {
|
|
return
|
|
}
|
|
|
|
// Check which linear space is less to walk.
|
|
if ss.FirstSeq-asflr-1 < uint64(numPending) {
|
|
// Process all messages that no longer exist.
|
|
for seq := asflr + 1; seq < ss.FirstSeq; seq++ {
|
|
// Check if this message was pending.
|
|
o.mu.RLock()
|
|
p, isPending := o.pending[seq]
|
|
rdc := o.deliveryCount(seq)
|
|
o.mu.RUnlock()
|
|
// If it was pending for us, get rid of it.
|
|
if isPending {
|
|
o.processTerm(seq, p.Sequence, rdc, ackTermLimitsReason, _EMPTY_)
|
|
}
|
|
}
|
|
} else if numPending > 0 {
|
|
// here it is shorter to walk pending.
|
|
// toTerm is seq, dseq, rcd for each entry.
|
|
toTerm := make([]uint64, 0, numPending*3)
|
|
o.mu.RLock()
|
|
for seq, p := range o.pending {
|
|
if seq < ss.FirstSeq {
|
|
var dseq uint64 = 1
|
|
if p != nil {
|
|
dseq = p.Sequence
|
|
}
|
|
rdc := o.deliveryCount(seq)
|
|
toTerm = append(toTerm, seq, dseq, rdc)
|
|
}
|
|
}
|
|
o.mu.RUnlock()
|
|
|
|
for i := 0; i < len(toTerm); i += 3 {
|
|
seq, dseq, rdc := toTerm[i], toTerm[i+1], toTerm[i+2]
|
|
o.processTerm(seq, dseq, rdc, ackTermLimitsReason, _EMPTY_)
|
|
}
|
|
}
|
|
|
|
// Do one final check here.
|
|
o.mu.Lock()
|
|
defer o.mu.Unlock()
|
|
|
|
// If we are closed do not change anything and simply return.
|
|
if o.closed {
|
|
return
|
|
}
|
|
|
|
// If we are here, and this should be rare, we still are off with our ack floor.
|
|
// We will make sure we are not doing un-necessary work here if only off by a bit
|
|
// since this could be normal for a high activity wq or stream.
|
|
// We will set it explicitly to 1 behind our current lowest in pending, or if
|
|
// pending is empty, to our current delivered -1.
|
|
const minOffThreshold = 50
|
|
if ss.FirstSeq >= minOffThreshold && o.asflr < ss.FirstSeq-minOffThreshold {
|
|
var psseq, pdseq uint64
|
|
for seq, p := range o.pending {
|
|
if psseq == 0 || seq < psseq {
|
|
psseq, pdseq = seq, p.Sequence
|
|
}
|
|
}
|
|
// If we still have none, set to current delivered -1.
|
|
if psseq == 0 {
|
|
psseq, pdseq = o.sseq-1, o.dseq-1
|
|
// If still not adjusted.
|
|
if psseq < ss.FirstSeq-1 {
|
|
psseq = ss.FirstSeq - 1
|
|
}
|
|
} else {
|
|
// Since this was set via the pending, we should not include
|
|
// it directly but set floors to -1.
|
|
psseq, pdseq = psseq-1, pdseq-1
|
|
}
|
|
o.asflr, o.adflr = psseq, pdseq
|
|
}
|
|
}
|
|
|
|
func (o *consumer) processInboundAcks(qch chan struct{}) {
|
|
// Grab the server lock to watch for server quit.
|
|
o.mu.RLock()
|
|
s, mset := o.srv, o.mset
|
|
hasInactiveThresh := o.cfg.InactiveThreshold > 0
|
|
|
|
o.mu.RUnlock()
|
|
|
|
if s == nil || mset == nil {
|
|
return
|
|
}
|
|
|
|
// We will check this on entry and periodically.
|
|
o.checkAckFloor()
|
|
|
|
// How often we will check for ack floor drift.
|
|
// Spread these out for large numbers on a server restart.
|
|
delta := time.Duration(rand.Int63n(int64(time.Minute)))
|
|
ticker := time.NewTicker(time.Minute + delta)
|
|
defer ticker.Stop()
|
|
|
|
for {
|
|
select {
|
|
case <-o.ackMsgs.ch:
|
|
// If we have an inactiveThreshold set, mark our activity.
|
|
// Do this before processing acks, otherwise we might race if there are no pending messages
|
|
// anymore and the inactivity threshold kicks in before we're able to mark activity.
|
|
if hasInactiveThresh {
|
|
o.suppressDeletion()
|
|
}
|
|
|
|
acks := o.ackMsgs.pop()
|
|
for _, ack := range acks {
|
|
o.processAck(ack.subject, ack.reply, ack.hdr, ack.msg)
|
|
ack.returnToPool()
|
|
}
|
|
o.ackMsgs.recycle(&acks)
|
|
case <-ticker.C:
|
|
o.checkAckFloor()
|
|
case <-qch:
|
|
return
|
|
case <-s.quitCh:
|
|
return
|
|
}
|
|
}
|
|
}
|
|
|
|
// Process inbound next message requests.
|
|
func (o *consumer) processInboundNextMsgReqs(qch chan struct{}) {
|
|
// Grab the server lock to watch for server quit.
|
|
o.mu.RLock()
|
|
s := o.srv
|
|
o.mu.RUnlock()
|
|
|
|
for {
|
|
select {
|
|
case <-o.nextMsgReqs.ch:
|
|
reqs := o.nextMsgReqs.pop()
|
|
for _, req := range reqs {
|
|
o.processNextMsgRequest(req.reply, req.msg)
|
|
req.returnToPool()
|
|
}
|
|
o.nextMsgReqs.recycle(&reqs)
|
|
case <-qch:
|
|
return
|
|
case <-s.quitCh:
|
|
return
|
|
}
|
|
}
|
|
}
|
|
|
|
// Suppress auto cleanup on ack activity of any kind.
|
|
func (o *consumer) suppressDeletion() {
|
|
o.mu.Lock()
|
|
defer o.mu.Unlock()
|
|
|
|
if o.closed {
|
|
return
|
|
}
|
|
|
|
if o.isPushMode() && o.dtmr != nil {
|
|
// if dtmr is not nil we have started the countdown, simply reset to threshold.
|
|
o.dtmr.Reset(o.dthresh)
|
|
} else if o.isPullMode() && o.waiting != nil {
|
|
// Pull mode always has timer running, update last on waiting queue.
|
|
o.waiting.last = time.Now()
|
|
if o.dtmr != nil {
|
|
o.dtmr.Reset(o.dthresh)
|
|
}
|
|
}
|
|
}
|
|
|
|
// loopAndGatherMsgs waits for messages for the consumer. qch is the quit channel,
|
|
// upch is the unpause channel which fires when the PauseUntil deadline is reached.
|
|
func (o *consumer) loopAndGatherMsgs(qch chan struct{}) {
|
|
// On startup check to see if we are in a reply situation where replay policy is not instant.
|
|
var (
|
|
lts int64 // last time stamp seen, used for replay.
|
|
lseq uint64
|
|
)
|
|
|
|
o.mu.RLock()
|
|
mset := o.mset
|
|
getLSeq := o.replay
|
|
o.mu.RUnlock()
|
|
// consumer is closed when mset is set to nil.
|
|
if mset == nil {
|
|
return
|
|
}
|
|
if getLSeq {
|
|
lseq = mset.state().LastSeq
|
|
}
|
|
|
|
o.mu.Lock()
|
|
s := o.srv
|
|
// need to check again if consumer is closed
|
|
if o.mset == nil {
|
|
o.mu.Unlock()
|
|
return
|
|
}
|
|
// For idle heartbeat support.
|
|
var hbc <-chan time.Time
|
|
hbd, hb := o.hbTimer()
|
|
if hb != nil {
|
|
hbc = hb.C
|
|
}
|
|
// Interest changes.
|
|
inch := o.inch
|
|
o.mu.Unlock()
|
|
|
|
// Grab the stream's retention policy and name
|
|
mset.cfgMu.RLock()
|
|
stream, rp := mset.cfg.Name, mset.cfg.Retention
|
|
mset.cfgMu.RUnlock()
|
|
|
|
var err error
|
|
|
|
// Deliver all the msgs we have now, once done or on a condition, we wait for new ones.
|
|
for {
|
|
var (
|
|
pmsg *jsPubMsg
|
|
dc uint64
|
|
dsubj string
|
|
ackReply string
|
|
delay time.Duration
|
|
sz int
|
|
wrn, wrb int
|
|
)
|
|
|
|
o.mu.Lock()
|
|
|
|
// consumer is closed when mset is set to nil.
|
|
if o.closed || o.mset == nil {
|
|
o.mu.Unlock()
|
|
return
|
|
}
|
|
|
|
// Clear last error.
|
|
err = nil
|
|
|
|
// If the consumer is paused then stop sending.
|
|
if o.cfg.PauseUntil != nil && !o.cfg.PauseUntil.IsZero() && time.Now().Before(*o.cfg.PauseUntil) {
|
|
// If the consumer is paused and we haven't reached the deadline yet then
|
|
// go back to waiting.
|
|
goto waitForMsgs
|
|
}
|
|
|
|
// If we are in push mode and not active or under flowcontrol let's stop sending.
|
|
if o.isPushMode() {
|
|
if !o.active || (o.maxpb > 0 && o.pbytes > o.maxpb) {
|
|
goto waitForMsgs
|
|
}
|
|
} else if o.waiting.isEmpty() {
|
|
// If we are in pull mode and no one is waiting already break and wait.
|
|
goto waitForMsgs
|
|
}
|
|
|
|
// Grab our next msg.
|
|
pmsg, dc, err = o.getNextMsg()
|
|
|
|
// We can release the lock now under getNextMsg so need to check this condition again here.
|
|
if o.closed || o.mset == nil {
|
|
o.mu.Unlock()
|
|
return
|
|
}
|
|
|
|
// On error either wait or return.
|
|
if err != nil || pmsg == nil {
|
|
// On EOF we can optionally fast sync num pending state.
|
|
if err == ErrStoreEOF {
|
|
o.checkNumPendingOnEOF()
|
|
}
|
|
if err == ErrStoreMsgNotFound || err == errDeletedMsg || err == ErrStoreEOF || err == errMaxAckPending {
|
|
goto waitForMsgs
|
|
} else {
|
|
if pmsg != nil {
|
|
s.Errorf("Received an error looking up message with sequence %d for consumer '%s > %s > %s': %v",
|
|
pmsg.seq, o.mset.acc, stream, o.cfg.Name, err)
|
|
} else {
|
|
s.Errorf("Received an error looking up message for consumer '%s > %s > %s': %v",
|
|
o.mset.acc, stream, o.cfg.Name, err)
|
|
}
|
|
goto waitForMsgs
|
|
}
|
|
}
|
|
|
|
// Update our cached num pending here first.
|
|
if dc == 1 {
|
|
o.npc--
|
|
}
|
|
// Pre-calculate ackReply
|
|
ackReply = o.ackReply(pmsg.seq, o.dseq, dc, pmsg.ts, o.numPending())
|
|
|
|
// If headers only do not send msg payload.
|
|
// Add in msg size itself as header.
|
|
if o.cfg.HeadersOnly {
|
|
convertToHeadersOnly(pmsg)
|
|
}
|
|
// Calculate payload size. This can be calculated on client side.
|
|
// We do not include transport subject here since not generally known on client.
|
|
sz = len(pmsg.subj) + len(ackReply) + len(pmsg.hdr) + len(pmsg.msg)
|
|
|
|
if o.isPushMode() {
|
|
dsubj = o.dsubj
|
|
} else if wr := o.nextWaiting(sz); wr != nil {
|
|
wrn, wrb = wr.n, wr.b
|
|
dsubj = wr.reply
|
|
if o.cfg.PriorityPolicy == PriorityPinnedClient {
|
|
pmsg.hdr = genHeader(pmsg.hdr, JSPullRequestNatsPinId, o.currentPinId)
|
|
pmsg.buf = append(pmsg.hdr, pmsg.msg...)
|
|
sz = len(pmsg.subj) + len(ackReply) + len(pmsg.hdr) + len(pmsg.msg)
|
|
}
|
|
if done := wr.recycleIfDone(); done && o.node != nil {
|
|
o.removeClusterPendingRequest(dsubj)
|
|
} else if !done && wr.hb > 0 {
|
|
wr.hbt = time.Now().Add(wr.hb)
|
|
}
|
|
} else {
|
|
// We will redo this one as long as this is not a redelivery.
|
|
// Need to also test that this is not going backwards since if
|
|
// we fail to deliver we can end up here from rdq but we do not
|
|
// want to decrement o.sseq if that is the case.
|
|
if dc == 1 && pmsg.seq == o.sseq-1 {
|
|
o.sseq--
|
|
o.npc++
|
|
} else if !o.onRedeliverQueue(pmsg.seq) {
|
|
// We are not on the rdq so decrement the delivery count
|
|
// and add it back.
|
|
o.decDeliveryCount(pmsg.seq)
|
|
o.addToRedeliverQueue(pmsg.seq)
|
|
}
|
|
pmsg.returnToPool()
|
|
pmsg = nil
|
|
goto waitForMsgs
|
|
}
|
|
|
|
// If we are in a replay scenario and have not caught up check if we need to delay here.
|
|
if o.replay && lts > 0 {
|
|
if delay = time.Duration(pmsg.ts - lts); delay > time.Millisecond {
|
|
o.mu.Unlock()
|
|
select {
|
|
case <-qch:
|
|
pmsg.returnToPool()
|
|
pmsg = nil
|
|
return
|
|
case <-time.After(delay):
|
|
}
|
|
o.mu.Lock()
|
|
}
|
|
}
|
|
|
|
// Track this regardless.
|
|
lts = pmsg.ts
|
|
|
|
// If we have a rate limit set make sure we check that here.
|
|
if o.rlimit != nil {
|
|
now := time.Now()
|
|
r := o.rlimit.ReserveN(now, sz)
|
|
delay := r.DelayFrom(now)
|
|
if delay > 0 {
|
|
o.mu.Unlock()
|
|
select {
|
|
case <-qch:
|
|
pmsg.returnToPool()
|
|
pmsg = nil
|
|
return
|
|
case <-time.After(delay):
|
|
}
|
|
o.mu.Lock()
|
|
}
|
|
}
|
|
|
|
// Do actual delivery.
|
|
o.deliverMsg(dsubj, ackReply, pmsg, dc, rp)
|
|
|
|
// If given request fulfilled batch size, but there are still pending bytes, send information about it.
|
|
if wrn <= 0 && wrb > 0 {
|
|
msg := fmt.Appendf(nil, JsPullRequestRemainingBytesT, JSPullRequestPendingMsgs, wrn, JSPullRequestPendingBytes, wrb)
|
|
o.outq.send(newJSPubMsg(dsubj, _EMPTY_, _EMPTY_, msg, nil, nil, 0))
|
|
}
|
|
// Reset our idle heartbeat timer if set.
|
|
if hb != nil {
|
|
hb.Reset(hbd)
|
|
}
|
|
|
|
o.mu.Unlock()
|
|
continue
|
|
|
|
waitForMsgs:
|
|
// If we were in a replay state check to see if we are caught up. If so clear.
|
|
if o.replay && o.sseq > lseq {
|
|
o.replay = false
|
|
}
|
|
|
|
// Make sure to process any expired requests that are pending.
|
|
var wrExp <-chan time.Time
|
|
if o.isPullMode() {
|
|
// Dont expire oneshots if we are here because of max ack pending limit.
|
|
_, _, _, fexp := o.processWaiting(err != errMaxAckPending)
|
|
if !fexp.IsZero() {
|
|
expires := time.Until(fexp)
|
|
if expires <= 0 {
|
|
expires = time.Millisecond
|
|
}
|
|
wrExp = time.NewTimer(expires).C
|
|
}
|
|
}
|
|
|
|
// We will wait here for new messages to arrive.
|
|
mch, odsubj := o.mch, o.cfg.DeliverSubject
|
|
o.mu.Unlock()
|
|
|
|
select {
|
|
case <-mch:
|
|
// Messages are waiting.
|
|
case interest := <-inch:
|
|
// inch can be nil on pull-based, but then this will
|
|
// just block and not fire.
|
|
o.updateDeliveryInterest(interest)
|
|
case <-qch:
|
|
return
|
|
case <-wrExp:
|
|
o.mu.Lock()
|
|
o.processWaiting(true)
|
|
o.mu.Unlock()
|
|
case <-hbc:
|
|
if o.isActive() {
|
|
o.mu.Lock()
|
|
o.sendIdleHeartbeat(odsubj)
|
|
// Send flow control on EOS if it's used for acknowledgements.
|
|
if o.cfg.AckPolicy == AckFlowControl && len(o.pending) > 0 && o.fcid == _EMPTY_ {
|
|
o.sendFlowControl()
|
|
}
|
|
o.mu.Unlock()
|
|
}
|
|
// Reset our idle heartbeat timer.
|
|
hb.Reset(hbd)
|
|
}
|
|
}
|
|
}
|
|
|
|
// Lock should be held.
|
|
func (o *consumer) sendIdleHeartbeat(subj string) {
|
|
const t = "NATS/1.0 100 Idle Heartbeat\r\n%s: %d\r\n%s: %d\r\n\r\n"
|
|
sseq, dseq := o.sseq-1, o.dseq-1
|
|
hdr := fmt.Appendf(nil, t, JSLastConsumerSeq, dseq, JSLastStreamSeq, sseq)
|
|
if fcp := o.fcid; fcp != _EMPTY_ {
|
|
// Add in that we are stalled on flow control here.
|
|
addOn := fmt.Appendf(nil, "%s: %s\r\n\r\n", JSConsumerStalled, fcp)
|
|
hdr = append(hdr[:len(hdr)-LEN_CR_LF], []byte(addOn)...)
|
|
}
|
|
o.outq.send(newJSPubMsg(subj, _EMPTY_, _EMPTY_, hdr, nil, nil, 0))
|
|
}
|
|
|
|
func (o *consumer) ackReply(sseq, dseq, dc uint64, ts int64, pending uint64) string {
|
|
if o.useV2Ack {
|
|
return fmt.Sprintf(o.ackReplyT, dc, sseq, dseq, ts, pending)
|
|
}
|
|
return fmt.Sprintf(o.ackReplyOldT, dc, sseq, dseq, ts, pending)
|
|
}
|
|
|
|
// Used mostly for testing. Sets max pending bytes for flow control setups.
|
|
func (o *consumer) setMaxPendingBytes(limit int) {
|
|
o.pblimit = limit
|
|
o.maxpb = limit / 16
|
|
if o.maxpb == 0 {
|
|
o.maxpb = 1
|
|
}
|
|
}
|
|
|
|
// Does some sanity checks to see if we should re-calculate.
|
|
// Since there is a race when decrementing when there is contention at the beginning of the stream.
|
|
// The race is a getNextMsg skips a deleted msg, and then the decStreamPending call fires.
|
|
// This does some quick sanity checks to see if we should re-calculate num pending.
|
|
// Lock should be held.
|
|
func (o *consumer) checkNumPending() (uint64, error) {
|
|
if o.mset != nil && o.mset.store != nil {
|
|
var state StreamState
|
|
o.mset.store.FastState(&state)
|
|
npc := o.numPending()
|
|
// Make sure we can't report more messages than there are.
|
|
// TODO(nat): It's not great that this means consumer info has side effects,
|
|
// since we can't know whether anyone will call it or not. The previous num
|
|
// pending calculation that this replaces had the same problem though.
|
|
if o.sseq > state.LastSeq {
|
|
o.npc = 0
|
|
} else if npc > 0 {
|
|
o.npc = int64(min(npc, state.Msgs, state.LastSeq-o.sseq+1))
|
|
}
|
|
}
|
|
return o.numPending(), nil
|
|
}
|
|
|
|
// Lock should be held.
|
|
func (o *consumer) numPending() uint64 {
|
|
if o.npc < 0 {
|
|
return 0
|
|
}
|
|
return uint64(o.npc)
|
|
}
|
|
|
|
// This will do a quick sanity check on num pending when we encounter
|
|
// and EOF in the loop and gather.
|
|
// Lock should be held.
|
|
func (o *consumer) checkNumPendingOnEOF() {
|
|
if o.mset == nil {
|
|
return
|
|
}
|
|
var state StreamState
|
|
o.mset.store.FastState(&state)
|
|
if o.sseq > state.LastSeq && o.npc != 0 {
|
|
// We know here we can reset our running state for num pending.
|
|
o.npc, o.npf = 0, state.LastSeq
|
|
}
|
|
}
|
|
|
|
// Call into streamNumPending after acquiring the consumer lock.
|
|
func (o *consumer) streamNumPendingLocked() (uint64, error) {
|
|
o.mu.Lock()
|
|
defer o.mu.Unlock()
|
|
return o.streamNumPending()
|
|
}
|
|
|
|
// Will force a set from the stream store of num pending.
|
|
// Depends on delivery policy, for last per subject we calculate differently.
|
|
// Lock should be held.
|
|
func (o *consumer) streamNumPending() (uint64, error) {
|
|
if o.mset == nil || o.mset.store == nil {
|
|
o.npc, o.npf = 0, 0
|
|
return 0, nil
|
|
}
|
|
npc, npf, err := o.calculateNumPending()
|
|
if err != nil {
|
|
return 0, err
|
|
}
|
|
o.npc, o.npf = int64(npc), npf
|
|
return o.numPending(), nil
|
|
}
|
|
|
|
// Will calculate num pending but only requires a read lock.
|
|
// Depends on delivery policy, for last per subject we calculate differently.
|
|
// At least RLock should be held.
|
|
func (o *consumer) calculateNumPending() (npc, npf uint64, err error) {
|
|
if o.mset == nil || o.mset.store == nil {
|
|
return 0, 0, nil
|
|
}
|
|
|
|
isLastPerSubject := o.cfg.DeliverPolicy == DeliverLastPerSubject
|
|
filters, subjf := o.filters, o.subjf
|
|
|
|
if filters != nil {
|
|
return o.mset.store.NumPendingMulti(o.sseq, filters, isLastPerSubject)
|
|
} else if len(subjf) > 0 {
|
|
filter := subjf[0].subject
|
|
return o.mset.store.NumPending(o.sseq, filter, isLastPerSubject)
|
|
}
|
|
return o.mset.store.NumPending(o.sseq, _EMPTY_, isLastPerSubject)
|
|
}
|
|
|
|
func convertToHeadersOnly(pmsg *jsPubMsg) {
|
|
// If headers only do not send msg payload.
|
|
// Add in msg size itself as header.
|
|
hdr, msg := pmsg.hdr, pmsg.msg
|
|
var bb bytes.Buffer
|
|
if len(hdr) == 0 {
|
|
bb.WriteString(hdrLine)
|
|
} else {
|
|
bb.Write(hdr)
|
|
bb.Truncate(len(hdr) - LEN_CR_LF)
|
|
}
|
|
bb.WriteString(JSMsgSize)
|
|
bb.WriteString(": ")
|
|
bb.WriteString(strconv.FormatInt(int64(len(msg)), 10))
|
|
bb.WriteString(CR_LF)
|
|
bb.WriteString(CR_LF)
|
|
// Replace underlying buf which we can use directly when we send.
|
|
// TODO(dlc) - Probably just use directly when forming bytes.Buffer?
|
|
pmsg.buf = pmsg.buf[:0]
|
|
pmsg.buf = append(pmsg.buf, bb.Bytes()...)
|
|
// Replace with new header.
|
|
pmsg.hdr = pmsg.buf
|
|
// Cancel msg payload
|
|
pmsg.msg = nil
|
|
}
|
|
|
|
// Deliver a msg to the consumer.
|
|
// Lock should be held and o.mset validated to be non-nil.
|
|
func (o *consumer) deliverMsg(dsubj, ackReply string, pmsg *jsPubMsg, dc uint64, rp RetentionPolicy) {
|
|
if o.mset == nil {
|
|
pmsg.returnToPool()
|
|
return
|
|
}
|
|
|
|
dseq := o.dseq
|
|
o.dseq++
|
|
|
|
pmsg.dsubj, pmsg.reply, pmsg.o = dsubj, ackReply, o
|
|
psz := pmsg.size()
|
|
|
|
if o.maxpb > 0 {
|
|
o.pbytes += psz
|
|
}
|
|
|
|
mset := o.mset
|
|
ap := o.cfg.AckPolicy
|
|
|
|
// Cant touch pmsg after this sending so capture what we need.
|
|
seq, ts := pmsg.seq, pmsg.ts
|
|
|
|
// Update delivered first.
|
|
o.updateDelivered(dseq, seq, dc, ts)
|
|
|
|
if ap == AckNone {
|
|
o.adflr = dseq
|
|
o.asflr = seq
|
|
} else {
|
|
o.trackPending(seq, dseq)
|
|
}
|
|
|
|
// Send message.
|
|
if o.replicateDeliveries() {
|
|
o.addReplicatedQueuedMsg(pmsg)
|
|
} else {
|
|
o.outq.send(pmsg)
|
|
}
|
|
|
|
// Flow control.
|
|
if o.maxpb > 0 && o.needFlowControl(psz) {
|
|
o.sendFlowControl()
|
|
}
|
|
|
|
// If pull mode and we have inactivity threshold, signaled by dthresh, update last activity.
|
|
if o.isPullMode() && o.dthresh > 0 {
|
|
o.waiting.last = time.Now()
|
|
}
|
|
|
|
// If we are ack none and mset is interest only we should make sure stream removes interest.
|
|
if ap == AckNone && rp != LimitsPolicy {
|
|
if mset != nil && mset.ackq != nil && (o.node == nil || o.cfg.Direct) {
|
|
mset.ackq.push(seq)
|
|
} else {
|
|
o.updateAcks(dseq, seq, _EMPTY_)
|
|
}
|
|
}
|
|
}
|
|
|
|
// replicateDeliveries returns whether deliveries should be replicated before sending them.
|
|
// If we're replicated we MUST only send the message AFTER we've got quorum for updating
|
|
// delivered state. Otherwise, we could be in an invalid state after a leader change.
|
|
// We can send immediately if not replicated, not using acks, or using flow control (incompatible).
|
|
// Lock should be held.
|
|
func (o *consumer) replicateDeliveries() bool {
|
|
return o.node != nil && o.cfg.AckPolicy != AckNone && !o.cfg.FlowControl
|
|
}
|
|
|
|
func (o *consumer) needFlowControl(sz int) bool {
|
|
if o.maxpb == 0 {
|
|
return false
|
|
}
|
|
// Decide whether to send a flow control message which we will need the user to respond.
|
|
// We send when we are over 50% of our current window limit.
|
|
if o.fcid == _EMPTY_ && o.pbytes > o.maxpb/2 {
|
|
return true
|
|
}
|
|
// Or, when acking based on flow control, we need to send it if we've hit the max pending limit earlier.
|
|
if o.fcid == _EMPTY_ && o.cfg.AckPolicy == AckFlowControl && o.maxp > 0 && len(o.pending) >= o.maxp {
|
|
return true
|
|
}
|
|
// If we have an existing outstanding FC, check to see if we need to expand the o.fcsz
|
|
if o.fcid != _EMPTY_ && (o.pbytes-o.fcsz) >= o.maxpb {
|
|
o.fcsz += sz
|
|
}
|
|
return false
|
|
}
|
|
|
|
func (o *consumer) processFlowControl(_ *subscription, c *client, _ *Account, subj, _ string, rmsg []byte) {
|
|
o.mu.Lock()
|
|
|
|
// Ignore if not the latest we have sent out.
|
|
if subj != o.fcid {
|
|
o.mu.Unlock()
|
|
return
|
|
}
|
|
|
|
// For slow starts and ramping up.
|
|
if o.maxpb < o.pblimit {
|
|
o.maxpb *= 2
|
|
if o.maxpb > o.pblimit {
|
|
o.maxpb = o.pblimit
|
|
}
|
|
}
|
|
|
|
// Update accounting.
|
|
o.pbytes -= o.fcsz
|
|
if o.pbytes < 0 {
|
|
o.pbytes = 0
|
|
}
|
|
o.fcid, o.fcsz = _EMPTY_, 0
|
|
|
|
o.signalNewMessages()
|
|
ackFlowControl := o.cfg.AckPolicy == AckFlowControl
|
|
o.mu.Unlock()
|
|
|
|
if !ackFlowControl {
|
|
return
|
|
}
|
|
hdr, _ := c.msgParts(rmsg)
|
|
if len(hdr) > 0 {
|
|
ldseq := parseInt64(sliceHeader(JSLastConsumerSeq, hdr))
|
|
lsseq := parseInt64(sliceHeader(JSLastStreamSeq, hdr))
|
|
if lsseq > 0 {
|
|
// Delivered sequence is allowed to be zero as a response
|
|
// to flow control without any deliveries.
|
|
if ldseq <= 0 {
|
|
ldseq = 0
|
|
}
|
|
o.processAckMsg(uint64(lsseq), uint64(ldseq), 1, _EMPTY_, false)
|
|
}
|
|
}
|
|
}
|
|
|
|
// Lock should be held.
|
|
func (o *consumer) fcReply() string {
|
|
var sb strings.Builder
|
|
if o.useV2Ack {
|
|
sb.WriteString(o.fcPre)
|
|
} else {
|
|
sb.WriteString(o.fcPreOld)
|
|
}
|
|
sb.WriteString(o.stream)
|
|
sb.WriteByte(btsep)
|
|
sb.WriteString(o.name)
|
|
sb.WriteByte(btsep)
|
|
var b [4]byte
|
|
rn := rand.Int63()
|
|
for i, l := 0, rn; i < len(b); i++ {
|
|
b[i] = digits[l%base]
|
|
l /= base
|
|
}
|
|
sb.Write(b[:])
|
|
return sb.String()
|
|
}
|
|
|
|
// sendFlowControl will send a flow control packet to the consumer.
|
|
// Lock should be held.
|
|
func (o *consumer) sendFlowControl() {
|
|
if !o.isPushMode() {
|
|
return
|
|
}
|
|
subj, rply := o.cfg.DeliverSubject, o.fcReply()
|
|
o.fcsz, o.fcid = o.pbytes, rply
|
|
hdr := []byte("NATS/1.0 100 FlowControl Request\r\n\r\n")
|
|
o.outq.send(newJSPubMsg(subj, _EMPTY_, rply, hdr, nil, nil, 0))
|
|
}
|
|
|
|
// Tracks our outstanding pending acks. Only applicable to AckExplicit mode.
|
|
// Lock should be held.
|
|
func (o *consumer) trackPending(sseq, dseq uint64) {
|
|
if o.pending == nil {
|
|
o.pending = make(map[uint64]*Pending)
|
|
}
|
|
|
|
now := time.Now()
|
|
if p, ok := o.pending[sseq]; ok {
|
|
// Update timestamp but keep original consumer delivery sequence.
|
|
// So do not update p.Sequence.
|
|
p.Timestamp = now.UnixNano()
|
|
} else {
|
|
o.pending[sseq] = &Pending{dseq, now.UnixNano()}
|
|
}
|
|
|
|
// We could have a backoff that set a timer higher than what we need for this message.
|
|
// In that case, reset to lowest backoff required for a message redelivery.
|
|
minDelay := o.ackWait(0)
|
|
if l := len(o.cfg.BackOff); l > 0 {
|
|
bi := int(o.rdc[sseq])
|
|
if bi < 0 {
|
|
bi = 0
|
|
} else if bi >= l {
|
|
bi = l - 1
|
|
}
|
|
minDelay = o.ackWait(o.cfg.BackOff[bi])
|
|
}
|
|
minDeadline := now.Add(minDelay)
|
|
if o.ptmr == nil || o.ptmrEnd.After(minDeadline) {
|
|
o.resetPtmr(minDelay)
|
|
}
|
|
}
|
|
|
|
// Credit back a failed delivery.
|
|
// lock should be held.
|
|
func (o *consumer) creditWaitingRequest(reply string) {
|
|
wq := o.waiting
|
|
for wr := wq.head; wr != nil; wr = wr.next {
|
|
if wr.reply == reply {
|
|
wr.n++
|
|
wr.d--
|
|
return
|
|
}
|
|
}
|
|
}
|
|
|
|
// didNotDeliver is called when a delivery for a consumer message failed.
|
|
// Depending on our state, we will process the failure.
|
|
func (o *consumer) didNotDeliver(seq uint64, subj string) {
|
|
o.mu.Lock()
|
|
mset := o.mset
|
|
if mset == nil {
|
|
o.mu.Unlock()
|
|
return
|
|
}
|
|
// Adjust back deliver count.
|
|
o.decDeliveryCount(seq)
|
|
|
|
var checkDeliveryInterest bool
|
|
if o.isPushMode() {
|
|
o.active = false
|
|
checkDeliveryInterest = true
|
|
} else if o.pending != nil {
|
|
// Good chance we did not deliver because no interest so force a check.
|
|
o.processWaiting(false)
|
|
// If it is still there credit it back.
|
|
o.creditWaitingRequest(subj)
|
|
// pull mode and we have pending.
|
|
if _, ok := o.pending[seq]; ok {
|
|
// We found this messsage on pending, we need
|
|
// to queue it up for immediate redelivery since
|
|
// we know it was not delivered
|
|
if !o.onRedeliverQueue(seq) {
|
|
o.addToRedeliverQueue(seq)
|
|
if !o.waiting.isEmpty() {
|
|
o.signalNewMessages()
|
|
}
|
|
}
|
|
}
|
|
}
|
|
o.mu.Unlock()
|
|
|
|
if checkDeliveryInterest {
|
|
localInterest := !o.hasNoLocalInterest()
|
|
o.updateDeliveryInterest(localInterest)
|
|
}
|
|
}
|
|
|
|
// Lock should be held.
|
|
func (o *consumer) addToRedeliverQueue(seqs ...uint64) {
|
|
o.rdq = append(o.rdq, seqs...)
|
|
for _, seq := range seqs {
|
|
o.rdqi.Insert(seq)
|
|
}
|
|
}
|
|
|
|
// Lock should be held.
|
|
func (o *consumer) hasRedeliveries() bool {
|
|
return len(o.rdq) > 0
|
|
}
|
|
|
|
func (o *consumer) getNextToRedeliver() uint64 {
|
|
if len(o.rdq) == 0 {
|
|
return 0
|
|
}
|
|
seq := o.rdq[0]
|
|
if len(o.rdq) == 1 {
|
|
o.rdq = nil
|
|
o.rdqi.Empty()
|
|
} else {
|
|
o.rdq = append(o.rdq[:0], o.rdq[1:]...)
|
|
o.rdqi.Delete(seq)
|
|
}
|
|
return seq
|
|
}
|
|
|
|
// This checks if we already have this sequence queued for redelivery.
|
|
// FIXME(dlc) - This is O(n) but should be fast with small redeliver size.
|
|
// Lock should be held.
|
|
func (o *consumer) onRedeliverQueue(seq uint64) bool {
|
|
return o.rdqi.Exists(seq)
|
|
}
|
|
|
|
// Remove a sequence from the redelivery queue.
|
|
// Lock should be held.
|
|
func (o *consumer) removeFromRedeliverQueue(seq uint64) bool {
|
|
if !o.onRedeliverQueue(seq) {
|
|
return false
|
|
}
|
|
for i, rseq := range o.rdq {
|
|
if rseq == seq {
|
|
if len(o.rdq) == 1 {
|
|
o.rdq = nil
|
|
o.rdqi.Empty()
|
|
} else {
|
|
o.rdq = append(o.rdq[:i], o.rdq[i+1:]...)
|
|
o.rdqi.Delete(seq)
|
|
}
|
|
return true
|
|
}
|
|
}
|
|
return false
|
|
}
|
|
|
|
// Checks the pending messages.
|
|
func (o *consumer) checkPending() {
|
|
o.mu.Lock()
|
|
defer o.mu.Unlock()
|
|
|
|
mset := o.mset
|
|
ttl := int64(o.cfg.AckWait)
|
|
// On stop, mset and timer will be nil.
|
|
if o.closed || mset == nil || o.ptmr == nil || ttl == 0 {
|
|
o.stopAndClearPtmr()
|
|
return
|
|
}
|
|
|
|
var shouldUpdateState bool
|
|
var state StreamState
|
|
mset.store.FastState(&state)
|
|
fseq := state.FirstSeq
|
|
|
|
now := time.Now().UnixNano()
|
|
next := int64(o.ackWait(0))
|
|
// However, if there is backoff, initializes with the largest backoff.
|
|
// It will be adjusted as needed.
|
|
if l := len(o.cfg.BackOff); l > 0 {
|
|
next = int64(o.cfg.BackOff[l-1])
|
|
}
|
|
|
|
// Since we can update timestamps, we have to review all pending.
|
|
// We will now bail if we see an ack pending inbound to us via o.awl.
|
|
var expired []uint64
|
|
check := len(o.pending) > 1024
|
|
for seq, p := range o.pending {
|
|
if check && atomic.LoadInt64(&o.awl) > 0 {
|
|
o.resetPtmr(100 * time.Millisecond)
|
|
return
|
|
}
|
|
// Check if these are no longer valid.
|
|
if seq < fseq || seq <= o.asflr {
|
|
delete(o.pending, seq)
|
|
delete(o.rdc, seq)
|
|
o.removeFromRedeliverQueue(seq)
|
|
shouldUpdateState = true
|
|
// Check if we need to move ack floors.
|
|
if seq > o.asflr {
|
|
o.asflr = seq
|
|
}
|
|
if p.Sequence > o.adflr {
|
|
o.adflr = p.Sequence
|
|
}
|
|
continue
|
|
}
|
|
elapsed, deadline := now-p.Timestamp, ttl
|
|
if len(o.cfg.BackOff) > 0 {
|
|
// This is ok even if o.rdc is nil, we would get dc == 0, which is what we want.
|
|
dc := int(o.rdc[seq])
|
|
if dc < 0 {
|
|
// Prevent consumer backoff from going backwards.
|
|
dc = 0
|
|
}
|
|
// This will be the index for the next backoff, will set to last element if needed.
|
|
nbi := dc + 1
|
|
if dc+1 >= len(o.cfg.BackOff) {
|
|
dc = len(o.cfg.BackOff) - 1
|
|
nbi = dc
|
|
}
|
|
deadline = int64(o.cfg.BackOff[dc])
|
|
// Set `next` to the next backoff (if smaller than current `next` value).
|
|
if nextBackoff := int64(o.cfg.BackOff[nbi]); nextBackoff < next {
|
|
next = nextBackoff
|
|
}
|
|
}
|
|
if elapsed >= deadline {
|
|
// We will check if we have hit our max deliveries. Previously we would do this on getNextMsg() which
|
|
// worked well for push consumers, but with pull based consumers would require a new pull request to be
|
|
// present to process and redelivered could be reported incorrectly.
|
|
if !o.onRedeliverQueue(seq) && !o.hasMaxDeliveries(seq) {
|
|
expired = append(expired, seq)
|
|
}
|
|
} else if deadline-elapsed < next {
|
|
// Update when we should fire next.
|
|
next = deadline - elapsed
|
|
}
|
|
}
|
|
|
|
if len(expired) > 0 {
|
|
// We need to sort.
|
|
slices.Sort(expired)
|
|
o.addToRedeliverQueue(expired...)
|
|
// Now we should update the timestamp here since we are redelivering.
|
|
// We will use an incrementing time to preserve order for any other redelivery.
|
|
off := now - o.pending[expired[0]].Timestamp
|
|
for _, seq := range expired {
|
|
if p, ok := o.pending[seq]; ok {
|
|
p.Timestamp += off
|
|
}
|
|
}
|
|
o.signalNewMessages()
|
|
}
|
|
|
|
if len(o.pending) > 0 {
|
|
o.resetPtmr(time.Duration(next))
|
|
} else {
|
|
// Make sure to stop timer and clear out any re delivery queues
|
|
o.stopAndClearPtmr()
|
|
o.rdq = nil
|
|
o.rdqi.Empty()
|
|
o.pending = nil
|
|
// Mimic behavior in processAckMsg when pending is empty.
|
|
o.adflr, o.asflr = o.dseq-1, o.sseq-1
|
|
}
|
|
|
|
// Update our state if needed.
|
|
if shouldUpdateState {
|
|
if err := o.writeStoreStateUnlocked(); err != nil && o.srv != nil && o.mset != nil && !o.closed {
|
|
s, acc, mset, name := o.srv, o.acc, o.mset, o.name
|
|
s.Warnf("Consumer '%s > %s > %s' error on write store state from check pending: %v", acc, mset.getCfgName(), name, err)
|
|
}
|
|
}
|
|
}
|
|
|
|
// SeqFromReply will extract a sequence number from a reply subject.
|
|
func (o *consumer) seqFromReply(reply string) uint64 {
|
|
_, dseq, _, _, _ := ackReplyInfo(reply)
|
|
return dseq
|
|
}
|
|
|
|
// StreamSeqFromReply will extract the stream sequence from the reply subject.
|
|
func (o *consumer) streamSeqFromReply(reply string) uint64 {
|
|
sseq, _, _, _, _ := ackReplyInfo(reply)
|
|
return sseq
|
|
}
|
|
|
|
// Quick parser for positive numbers in ack reply encoding.
|
|
func parseAckReplyNum(d string) (n int64) {
|
|
if len(d) == 0 {
|
|
return -1
|
|
}
|
|
for _, dec := range d {
|
|
if dec < asciiZero || dec > asciiNine {
|
|
return -1
|
|
}
|
|
n = n*10 + (int64(dec) - asciiZero)
|
|
}
|
|
return n
|
|
}
|
|
|
|
const (
|
|
expectedNumReplyTokensV1 = 9
|
|
expectedNumReplyTokensV2 = 11
|
|
)
|
|
|
|
// Grab encoded information in the reply subject for a delivered message.
|
|
func ackReplyInfo(subject string) (sseq, dseq, dc uint64, ts int64, pending uint64) {
|
|
tsa := [expectedNumReplyTokensV2]string{}
|
|
start, tokens := 0, tsa[:0]
|
|
for i := 0; i < len(subject); i++ {
|
|
if subject[i] == btsep {
|
|
tokens = append(tokens, subject[start:i])
|
|
start = i + 1
|
|
}
|
|
}
|
|
tokens = append(tokens, subject[start:])
|
|
if (len(tokens) != expectedNumReplyTokensV1 && len(tokens) < expectedNumReplyTokensV2) || tokens[0] != "$JS" || tokens[1] != "ACK" {
|
|
return 0, 0, 0, 0, 0
|
|
}
|
|
offset := 2
|
|
if len(tokens) >= expectedNumReplyTokensV2 {
|
|
offset = 4
|
|
}
|
|
// TODO(dlc) - Should we error if we do not match consumer name?
|
|
// stream is tokens[offset], consumer is offset+1.
|
|
dc = uint64(parseAckReplyNum(tokens[offset+2]))
|
|
sseq, dseq = uint64(parseAckReplyNum(tokens[offset+3])), uint64(parseAckReplyNum(tokens[offset+4]))
|
|
ts = parseAckReplyNum(tokens[offset+5])
|
|
pending = uint64(parseAckReplyNum(tokens[offset+6]))
|
|
|
|
return sseq, dseq, dc, ts, pending
|
|
}
|
|
|
|
// NextSeq returns the next delivered sequence number for this consumer.
|
|
func (o *consumer) nextSeq() uint64 {
|
|
o.mu.RLock()
|
|
dseq := o.dseq
|
|
o.mu.RUnlock()
|
|
return dseq
|
|
}
|
|
|
|
// Used to hold skip list when deliver policy is last per subject.
|
|
type lastSeqSkipList struct {
|
|
resume uint64
|
|
seqs []uint64
|
|
}
|
|
|
|
// Let's us know we have a skip list, which is for deliver last per subject and we are just starting.
|
|
// Lock should be held.
|
|
func (o *consumer) hasSkipListPending() bool {
|
|
return o.lss != nil && len(o.lss.seqs) > 0
|
|
}
|
|
|
|
// reconcileStateWithStream reconciles consumer state when the stream has reverted
|
|
// due to data loss (e.g., VM crash). This handles the case where consumer state
|
|
// is ahead of the stream's last sequence.
|
|
// Lock should be held.
|
|
func (o *consumer) reconcileStateWithStream(streamLastSeq uint64) {
|
|
// If an ack floor is higher than stream last sequence,
|
|
// reset back down but keep the highest known sequences.
|
|
if o.asflr > streamLastSeq {
|
|
o.asflr = streamLastSeq
|
|
// Delivery floor is one below the delivered sequence,
|
|
// but if it is zero somehow, ensure we don't underflow.
|
|
o.adflr = o.dseq
|
|
if o.adflr > 0 {
|
|
o.adflr--
|
|
}
|
|
o.pending = nil
|
|
o.rdc = nil
|
|
}
|
|
|
|
// Remove pending entries that are beyond the stream's last sequence
|
|
if len(o.pending) > 0 {
|
|
for seq := range o.pending {
|
|
if seq > streamLastSeq {
|
|
delete(o.pending, seq)
|
|
}
|
|
}
|
|
}
|
|
|
|
// Remove redelivered entries that are beyond the stream's last sequence
|
|
if len(o.rdc) > 0 {
|
|
for seq := range o.rdc {
|
|
if seq > streamLastSeq {
|
|
delete(o.rdc, seq)
|
|
}
|
|
}
|
|
}
|
|
|
|
// Update starting sequence and delivery sequence based on pending state
|
|
if len(o.pending) == 0 {
|
|
o.sseq = o.asflr + 1
|
|
o.dseq = o.adflr + 1
|
|
} else {
|
|
// Find highest stream sequence in pending
|
|
var maxStreamSeq uint64
|
|
var maxConsumerSeq uint64
|
|
|
|
for streamSeq, p := range o.pending {
|
|
if streamSeq > maxStreamSeq {
|
|
maxStreamSeq = streamSeq
|
|
}
|
|
if p.Sequence > maxConsumerSeq {
|
|
maxConsumerSeq = p.Sequence
|
|
}
|
|
}
|
|
|
|
// Set next sequences based on highest pending
|
|
o.sseq = maxStreamSeq + 1
|
|
o.dseq = maxConsumerSeq + 1
|
|
}
|
|
}
|
|
|
|
// Will select the starting sequence.
|
|
func (o *consumer) selectStartingSeqNo() error {
|
|
if o.mset == nil || o.mset.store == nil {
|
|
o.sseq = 1
|
|
} else {
|
|
var state StreamState
|
|
o.mset.store.FastState(&state)
|
|
if o.cfg.OptStartSeq == 0 {
|
|
if o.cfg.DeliverPolicy == DeliverAll {
|
|
o.sseq = state.FirstSeq
|
|
} else if o.cfg.DeliverPolicy == DeliverLast {
|
|
if o.subjf == nil {
|
|
o.sseq = state.LastSeq
|
|
} else {
|
|
// If we are partitioned here this will be properly set when we become leader.
|
|
for _, filter := range o.subjf {
|
|
ss, err := o.mset.store.FilteredState(1, filter.subject)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if ss.Last > o.sseq {
|
|
o.sseq = ss.Last
|
|
}
|
|
}
|
|
}
|
|
} else if o.cfg.DeliverPolicy == DeliverLastPerSubject {
|
|
// If our parent stream is set to max msgs per subject of 1 this is just
|
|
// a normal consumer at this point. We can avoid any heavy lifting.
|
|
o.mset.cfgMu.RLock()
|
|
mmp := o.mset.cfg.MaxMsgsPer
|
|
o.mset.cfgMu.RUnlock()
|
|
if mmp == 1 {
|
|
o.sseq = state.FirstSeq
|
|
} else {
|
|
filters := make([]string, 0, len(o.subjf))
|
|
if o.subjf == nil {
|
|
filters = append(filters, o.cfg.FilterSubject)
|
|
} else {
|
|
for _, filter := range o.subjf {
|
|
filters = append(filters, filter.subject)
|
|
}
|
|
}
|
|
|
|
lss := &lastSeqSkipList{resume: state.LastSeq}
|
|
lss.seqs, _ = o.mset.store.MultiLastSeqs(filters, 0, 0)
|
|
|
|
if len(lss.seqs) == 0 {
|
|
o.sseq = state.LastSeq
|
|
} else {
|
|
o.sseq = lss.seqs[0]
|
|
}
|
|
// Assign skip list.
|
|
o.lss = lss
|
|
}
|
|
} else if o.cfg.OptStartTime != nil {
|
|
// If we are here we are time based.
|
|
// TODO(dlc) - Once clustered can't rely on this.
|
|
o.sseq = o.mset.store.GetSeqFromTime(*o.cfg.OptStartTime)
|
|
// Here we want to see if we are filtered, and if so possibly close the gap
|
|
// to the nearest first given our starting sequence from time. This is so we do
|
|
// not force the system to do a linear walk between o.sseq and the real first.
|
|
if len(o.subjf) > 0 {
|
|
nseq := state.LastSeq
|
|
for _, filter := range o.subjf {
|
|
// Use first sequence since this is more optimized atm.
|
|
ss, err := o.mset.store.FilteredState(state.FirstSeq, filter.subject)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if ss.First >= o.sseq && ss.First < nseq {
|
|
nseq = ss.First
|
|
}
|
|
}
|
|
// Skip ahead if possible.
|
|
if nseq > o.sseq && nseq < state.LastSeq {
|
|
o.sseq = nseq
|
|
}
|
|
}
|
|
} else {
|
|
// DeliverNew
|
|
o.sseq = state.LastSeq + 1
|
|
}
|
|
} else {
|
|
o.sseq = o.cfg.OptStartSeq
|
|
}
|
|
|
|
if state.FirstSeq == 0 && (o.cfg.Direct || o.cfg.OptStartSeq == 0) {
|
|
// If the stream is empty, deliver only new.
|
|
// But only if mirroring/sourcing, or start seq is unset, otherwise need to respect provided value.
|
|
o.sseq = 1
|
|
} else if o.sseq > state.LastSeq && (o.cfg.Direct || o.cfg.OptStartSeq == 0) {
|
|
// If selected sequence is in the future, clamp back down.
|
|
// But only if mirroring/sourcing, or start seq is unset, otherwise need to respect provided value.
|
|
o.sseq = state.LastSeq + 1
|
|
} else if o.sseq < state.FirstSeq {
|
|
// If the first sequence is further ahead than the starting sequence,
|
|
// there are no messages there anymore, so move the sequence up.
|
|
o.sseq = state.FirstSeq
|
|
}
|
|
}
|
|
|
|
// Always set delivery sequence to 1.
|
|
o.dseq = 1
|
|
// Set ack delivery floor to delivery-1
|
|
o.adflr = o.dseq - 1
|
|
// Set ack store floor to store-1
|
|
o.asflr = o.sseq - 1
|
|
// Set our starting sequence state.
|
|
// But only if we're not clustered, if clustered we propose upon becoming leader.
|
|
if o.store != nil && o.sseq > 0 && o.cfg.replicas(&o.mset.cfg) == 1 {
|
|
if err := o.store.SetStarting(o.sseq - 1); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// Test whether a config represents a durable subscriber.
|
|
func isDurableConsumer(config *ConsumerConfig) bool {
|
|
return config != nil && config.Durable != _EMPTY_
|
|
}
|
|
|
|
func (o *consumer) isDurable() bool {
|
|
return o.cfg.Durable != _EMPTY_
|
|
}
|
|
|
|
// Are we in push mode, delivery subject, etc.
|
|
func (o *consumer) isPushMode() bool {
|
|
return o.cfg.DeliverSubject != _EMPTY_
|
|
}
|
|
|
|
func (o *consumer) isPullMode() bool {
|
|
return o.cfg.DeliverSubject == _EMPTY_
|
|
}
|
|
|
|
// Name returns the name of this consumer.
|
|
func (o *consumer) String() string {
|
|
o.mu.RLock()
|
|
n := o.name
|
|
o.mu.RUnlock()
|
|
return n
|
|
}
|
|
|
|
func createConsumerName() string {
|
|
return getHash(nuid.Next())
|
|
}
|
|
|
|
// Lock should be held.
|
|
func (mset *stream) createStableConsumerHash() string {
|
|
id := fmt.Sprintf("%s %s", mset.cfg.Name, mset.acc.Name)
|
|
if domain := mset.srv.getOpts().JetStreamDomain; domain != _EMPTY_ {
|
|
id = fmt.Sprintf("%s %s", id, domain)
|
|
}
|
|
return getHash(id)
|
|
}
|
|
|
|
// Lock should be held.
|
|
func (mset *stream) createSourcingConsumerHash(ssi *StreamSource, sources []*StreamSource) string {
|
|
id := mset.createStableConsumerHash()
|
|
|
|
// If the stream sources contain the same stream at least twice, we use a more strict hash of
|
|
// an ID that also contains filter subjects etc. If the stream name is only used once, we can
|
|
// support the stable identifier above.
|
|
var once bool
|
|
for _, src := range sources {
|
|
if src.Name == ssi.Name {
|
|
if once {
|
|
if ssi.iname == _EMPTY_ {
|
|
ssi.setIndexName()
|
|
}
|
|
// Append identifying information of the filter subjects, etc. to make it unique
|
|
id = fmt.Sprintf("%s %s", id, ssi.iname)
|
|
break
|
|
} else {
|
|
once = true
|
|
}
|
|
}
|
|
}
|
|
|
|
return getHash(id)
|
|
}
|
|
|
|
// deleteConsumer will delete the consumer from this stream.
|
|
func (mset *stream) deleteConsumer(o *consumer) error {
|
|
return o.delete()
|
|
}
|
|
|
|
func (o *consumer) getStream() *stream {
|
|
o.mu.RLock()
|
|
mset := o.mset
|
|
o.mu.RUnlock()
|
|
return mset
|
|
}
|
|
|
|
func (o *consumer) streamName() string {
|
|
o.mu.RLock()
|
|
mset := o.mset
|
|
o.mu.RUnlock()
|
|
if mset != nil {
|
|
return mset.name()
|
|
}
|
|
return _EMPTY_
|
|
}
|
|
|
|
// Active indicates if this consumer is still active.
|
|
func (o *consumer) isActive() bool {
|
|
o.mu.RLock()
|
|
active := o.active && o.mset != nil
|
|
o.mu.RUnlock()
|
|
return active
|
|
}
|
|
|
|
// hasNoLocalInterest return true if we have no local interest.
|
|
func (o *consumer) hasNoLocalInterest() bool {
|
|
o.mu.RLock()
|
|
interest := o.acc.sl.HasInterest(o.cfg.DeliverSubject)
|
|
o.mu.RUnlock()
|
|
return !interest
|
|
}
|
|
|
|
// This is when the underlying stream has been purged.
|
|
// sseq is the new first seq for the stream after purge.
|
|
// Consumer lock should NOT be held but the parent stream
|
|
// lock MUST be held.
|
|
func (o *consumer) purge(sseq uint64, slseq uint64, isWider bool) {
|
|
// Do not update our state unless we know we are the leader.
|
|
if !o.isLeader() {
|
|
return
|
|
}
|
|
// Signals all have been purged for this consumer.
|
|
if sseq == 0 && !isWider {
|
|
sseq = slseq + 1
|
|
}
|
|
|
|
var store StreamStore
|
|
if isWider {
|
|
o.mu.RLock()
|
|
if o.mset != nil {
|
|
store = o.mset.store
|
|
}
|
|
o.mu.RUnlock()
|
|
}
|
|
|
|
o.mu.Lock()
|
|
// Do not go backwards
|
|
if o.sseq < sseq {
|
|
o.sseq = sseq
|
|
}
|
|
|
|
if o.asflr < sseq {
|
|
o.asflr = sseq - 1
|
|
// We need to remove those no longer relevant from pending.
|
|
for seq, p := range o.pending {
|
|
if seq <= o.asflr {
|
|
if p.Sequence > o.adflr {
|
|
o.adflr = p.Sequence
|
|
if o.adflr > o.dseq {
|
|
o.dseq = o.adflr
|
|
}
|
|
}
|
|
delete(o.pending, seq)
|
|
delete(o.rdc, seq)
|
|
// rdq handled below.
|
|
}
|
|
if isWider && store != nil {
|
|
// Our filtered subject, which could be all, is wider than the underlying purge.
|
|
// We need to check if the pending items left are still valid.
|
|
var smv StoreMsg
|
|
if _, err := store.LoadMsg(seq, &smv); err == errDeletedMsg || err == ErrStoreMsgNotFound {
|
|
if p.Sequence > o.adflr {
|
|
o.adflr = p.Sequence
|
|
if o.adflr > o.dseq {
|
|
o.dseq = o.adflr
|
|
}
|
|
}
|
|
delete(o.pending, seq)
|
|
delete(o.rdc, seq)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// This means we can reset everything at this point.
|
|
if len(o.pending) == 0 {
|
|
o.pending, o.rdc = nil, nil
|
|
o.adflr, o.asflr = o.dseq-1, o.sseq-1
|
|
}
|
|
|
|
// We need to remove all those being queued for redelivery under o.rdq
|
|
if len(o.rdq) > 0 {
|
|
rdq := o.rdq
|
|
o.rdq = nil
|
|
o.rdqi.Empty()
|
|
for _, sseq := range rdq {
|
|
if sseq >= o.sseq {
|
|
o.addToRedeliverQueue(sseq)
|
|
}
|
|
}
|
|
}
|
|
// Grab some info in case of error below.
|
|
s, acc, mset, name := o.srv, o.acc, o.mset, o.name
|
|
o.mu.Unlock()
|
|
|
|
if err := o.writeStoreState(); err != nil && s != nil && mset != nil {
|
|
s.Warnf("Consumer '%s > %s > %s' error on write store state from purge: %v", acc, mset.nameLocked(false), name, err)
|
|
}
|
|
}
|
|
|
|
func stopAndClearTimer(tp **time.Timer) {
|
|
if *tp == nil {
|
|
return
|
|
}
|
|
// Will get drained in normal course, do not try to
|
|
// drain here.
|
|
(*tp).Stop()
|
|
*tp = nil
|
|
}
|
|
|
|
// Stop will shutdown the consumer for the associated stream.
|
|
func (o *consumer) stop() error {
|
|
return o.stopWithFlags(false, false, true, false)
|
|
}
|
|
|
|
func (o *consumer) deleteWithoutAdvisory() error {
|
|
return o.stopWithFlags(true, false, true, false)
|
|
}
|
|
|
|
// Delete will delete the consumer for the associated stream and send advisories.
|
|
func (o *consumer) delete() error {
|
|
return o.stopWithFlags(true, false, true, true)
|
|
}
|
|
|
|
// To test for closed state.
|
|
func (o *consumer) isClosed() bool {
|
|
o.mu.RLock()
|
|
defer o.mu.RUnlock()
|
|
return o.closed
|
|
}
|
|
|
|
func (o *consumer) stopWithFlags(dflag, sdflag, doSignal, advisory bool) error {
|
|
// If dflag is true determine if we are still assigned.
|
|
var isAssigned bool
|
|
if dflag {
|
|
o.mu.RLock()
|
|
acc, stream, consumer := o.acc, o.stream, o.name
|
|
isClustered := o.js != nil && o.js.isClustered()
|
|
o.mu.RUnlock()
|
|
if isClustered {
|
|
// Grab jsa to check assignment.
|
|
var jsa *jsAccount
|
|
if acc != nil {
|
|
// Need lock here to avoid data race.
|
|
acc.mu.RLock()
|
|
jsa = acc.js
|
|
acc.mu.RUnlock()
|
|
}
|
|
if jsa != nil {
|
|
isAssigned = jsa.consumerAssigned(stream, consumer)
|
|
}
|
|
}
|
|
}
|
|
|
|
o.mu.Lock()
|
|
if o.closed {
|
|
o.mu.Unlock()
|
|
return nil
|
|
}
|
|
o.closed = true
|
|
|
|
// Signal to the monitor loop.
|
|
// Can't use only qch here, since that's used when stepping down as a leader.
|
|
if o.mqch != nil {
|
|
close(o.mqch)
|
|
o.mqch = nil
|
|
}
|
|
|
|
// Check if we are the leader and are being deleted (as a node).
|
|
if dflag && o.isLeader() {
|
|
// If we are clustered and node leader (probable from above), stepdown.
|
|
if node := o.node; node != nil {
|
|
node.StepDown()
|
|
}
|
|
|
|
// dflag does not necessarily mean that the consumer is being deleted,
|
|
// just that the consumer node is being removed from this peer, so we
|
|
// send delete advisories only if we are no longer assigned at the meta layer,
|
|
// or we are not clustered.
|
|
if !isAssigned && advisory {
|
|
o.sendDeleteAdvisoryLocked()
|
|
}
|
|
if o.isPullMode() {
|
|
// Release any pending.
|
|
o.releaseAnyPendingRequests(isAssigned)
|
|
}
|
|
}
|
|
|
|
if o.qch != nil {
|
|
close(o.qch)
|
|
o.qch = nil
|
|
}
|
|
|
|
a := o.acc
|
|
store := o.store
|
|
mset := o.mset
|
|
o.mset = nil
|
|
o.active = false
|
|
o.unsubscribe(o.ackSubOld)
|
|
o.unsubscribe(o.ackSub)
|
|
o.unsubscribe(o.reqSub)
|
|
o.unsubscribe(o.resetSub)
|
|
o.unsubscribe(o.fcSubOld)
|
|
o.unsubscribe(o.fcSub)
|
|
o.ackSubOld = nil
|
|
o.ackSub = nil
|
|
o.reqSub = nil
|
|
o.resetSub = nil
|
|
o.fcSubOld = nil
|
|
o.fcSub = nil
|
|
if o.infoSub != nil {
|
|
o.srv.sysUnsubscribe(o.infoSub)
|
|
o.infoSub = nil
|
|
}
|
|
c := o.client
|
|
o.client = nil
|
|
sysc := o.sysc
|
|
o.sysc = nil
|
|
o.stopAndClearPtmr()
|
|
stopAndClearTimer(&o.dtmr)
|
|
stopAndClearTimer(&o.gwdtmr)
|
|
delivery := o.cfg.DeliverSubject
|
|
o.waiting = nil
|
|
// Break us out of the readLoop.
|
|
if doSignal {
|
|
o.signalNewMessages()
|
|
}
|
|
n := o.node
|
|
qgroup := o.cfg.DeliverGroup
|
|
o.ackMsgs.unregister()
|
|
if o.nextMsgReqs != nil {
|
|
o.nextMsgReqs.unregister()
|
|
}
|
|
|
|
// For cleaning up the node assignment.
|
|
var ca *consumerAssignment
|
|
if dflag {
|
|
ca = o.ca
|
|
}
|
|
js := o.js
|
|
o.mu.Unlock()
|
|
|
|
if c != nil {
|
|
c.closeConnection(ClientClosed)
|
|
}
|
|
if sysc != nil {
|
|
sysc.closeConnection(ClientClosed)
|
|
}
|
|
|
|
if delivery != _EMPTY_ {
|
|
a.sl.clearNotification(delivery, qgroup, o.inch)
|
|
}
|
|
|
|
var rp RetentionPolicy
|
|
if mset != nil {
|
|
mset.mu.Lock()
|
|
mset.removeConsumer(o)
|
|
// No need for cfgMu's lock since mset.mu.Lock superseeds it.
|
|
rp = mset.cfg.Retention
|
|
mset.mu.Unlock()
|
|
}
|
|
|
|
// Cleanup messages that lost interest.
|
|
if dflag && rp == InterestPolicy {
|
|
o.cleanupNoInterestMessages(mset, true)
|
|
}
|
|
|
|
// Cluster cleanup.
|
|
if n != nil {
|
|
if dflag {
|
|
n.Delete()
|
|
} else {
|
|
n.Stop()
|
|
}
|
|
}
|
|
|
|
if ca != nil {
|
|
js.mu.Lock()
|
|
if ca.Group != nil {
|
|
ca.Group.node = nil
|
|
}
|
|
js.mu.Unlock()
|
|
}
|
|
|
|
// Clean up our store.
|
|
var err error
|
|
if store != nil {
|
|
if dflag {
|
|
if sdflag {
|
|
err = store.StreamDelete()
|
|
} else {
|
|
err = store.Delete()
|
|
}
|
|
} else {
|
|
err = store.Stop()
|
|
}
|
|
} else if dflag {
|
|
// If there's no store (for example, when it's offline), manually delete the directories.
|
|
o.mu.RLock()
|
|
stream, consumer := o.stream, o.name
|
|
o.mu.RUnlock()
|
|
accDir := filepath.Join(js.config.StoreDir, a.GetName())
|
|
consumersDir := filepath.Join(accDir, streamsDir, stream, consumerDir)
|
|
os.RemoveAll(filepath.Join(consumersDir, consumer))
|
|
}
|
|
|
|
return err
|
|
}
|
|
|
|
// We need to optionally remove all messages since we are interest based retention.
|
|
// We will do this consistently on all replicas. Note that if in clustered mode the non-leader
|
|
// consumers will need to restore state first.
|
|
// ignoreInterest marks whether the consumer should be ignored when determining interest.
|
|
// No lock held on entry.
|
|
func (o *consumer) cleanupNoInterestMessages(mset *stream, ignoreInterest bool) {
|
|
o.mu.Lock()
|
|
if !o.isLeader() {
|
|
o.readStoredState()
|
|
}
|
|
start := o.asflr
|
|
o.mu.Unlock()
|
|
|
|
// Make sure we start at worst with first sequence in the stream.
|
|
state := mset.state()
|
|
if start < state.FirstSeq {
|
|
start = state.FirstSeq
|
|
}
|
|
stop := state.LastSeq
|
|
|
|
// Consumer's interests are ignored by default. If we should not ignore interest, unset.
|
|
co := o
|
|
if !ignoreInterest {
|
|
co = nil
|
|
}
|
|
|
|
var rmseqs []uint64
|
|
mset.mu.RLock()
|
|
|
|
// If over this amount of messages to check, optimistically call to checkInterestState().
|
|
// It will not always do the right thing in removing messages that lost interest, but ensures
|
|
// we don't degrade performance by doing a linear scan through the whole stream.
|
|
// Messages might need to expire based on limits to be cleaned up.
|
|
// TODO(dlc) - Better way?
|
|
const bailThresh = 100_000
|
|
|
|
// Check if we would be spending too much time here and defer to separate go routine.
|
|
if len(mset.consumers) == 0 {
|
|
mset.mu.RUnlock()
|
|
mset.mu.Lock()
|
|
defer mset.mu.Unlock()
|
|
mset.store.Purge()
|
|
var state StreamState
|
|
mset.store.FastState(&state)
|
|
mset.lseq = state.LastSeq
|
|
// Also make sure we clear any pending acks.
|
|
mset.clearAllPreAcksBelowFloor(state.FirstSeq)
|
|
return
|
|
} else if stop-start > bailThresh {
|
|
mset.mu.RUnlock()
|
|
go mset.checkInterestState()
|
|
return
|
|
}
|
|
|
|
mset.mu.RUnlock()
|
|
mset.mu.Lock()
|
|
for seq := start; seq <= stop; seq++ {
|
|
if mset.noInterest(seq, co) {
|
|
rmseqs = append(rmseqs, seq)
|
|
}
|
|
}
|
|
mset.mu.Unlock()
|
|
|
|
// These can be removed.
|
|
for _, seq := range rmseqs {
|
|
mset.store.RemoveMsg(seq)
|
|
}
|
|
}
|
|
|
|
// Check that we do not form a cycle by delivering to a delivery subject
|
|
// that is part of the interest group.
|
|
func deliveryFormsCycle(cfg *StreamConfig, deliverySubject string) bool {
|
|
for _, subject := range cfg.Subjects {
|
|
if subjectIsSubsetMatch(deliverySubject, subject) {
|
|
return true
|
|
}
|
|
}
|
|
return false
|
|
}
|
|
|
|
// switchToEphemeral is called on startup when recovering ephemerals.
|
|
func (o *consumer) switchToEphemeral() {
|
|
o.mu.Lock()
|
|
o.cfg.Durable = _EMPTY_
|
|
store, ok := o.store.(*consumerFileStore)
|
|
interest := o.acc.sl.HasInterest(o.cfg.DeliverSubject)
|
|
// Setup dthresh.
|
|
o.updateInactiveThreshold(&o.cfg)
|
|
o.updatePauseState(&o.cfg)
|
|
o.mu.Unlock()
|
|
|
|
// Update interest
|
|
o.updateDeliveryInterest(interest)
|
|
// Write out new config
|
|
if ok {
|
|
store.updateConfig(o.cfg)
|
|
}
|
|
}
|
|
|
|
// RequestNextMsgSubject returns the subject to request the next message when in pull or worker mode.
|
|
// Returns empty otherwise.
|
|
func (o *consumer) requestNextMsgSubject() string {
|
|
return o.nextMsgSubj
|
|
}
|
|
|
|
func (o *consumer) decStreamPending(sseq uint64, subj string) {
|
|
o.mu.Lock()
|
|
|
|
// Update our cached num pending only if we think deliverMsg has not done so.
|
|
if sseq >= o.sseq && o.isFilteredMatch(subj) {
|
|
o.npc--
|
|
}
|
|
|
|
// Check if this message was pending.
|
|
p, wasPending := o.pending[sseq]
|
|
var rdc uint64
|
|
if wasPending {
|
|
rdc = o.deliveryCount(sseq)
|
|
}
|
|
|
|
o.mu.Unlock()
|
|
|
|
// If it was pending process it like an ack.
|
|
if wasPending {
|
|
// We could have the lock for the stream so do this in a go routine.
|
|
// TODO(dlc) - We should do this with ipq vs naked go routines.
|
|
go o.processTerm(sseq, p.Sequence, rdc, ackTermUnackedLimitsReason, _EMPTY_)
|
|
}
|
|
}
|
|
|
|
func (o *consumer) account() *Account {
|
|
o.mu.RLock()
|
|
a := o.acc
|
|
o.mu.RUnlock()
|
|
return a
|
|
}
|
|
|
|
// Creates a sublist for consumer.
|
|
// All subjects share the same callback.
|
|
func (o *consumer) signalSubs() []string {
|
|
o.mu.Lock()
|
|
defer o.mu.Unlock()
|
|
|
|
if o.sigSubs != nil {
|
|
return o.sigSubs
|
|
}
|
|
|
|
if len(o.subjf) == 0 {
|
|
subs := []string{fwcs}
|
|
o.sigSubs = subs
|
|
return subs
|
|
}
|
|
|
|
subs := make([]string, 0, len(o.subjf))
|
|
for _, filter := range o.subjf {
|
|
subs = append(subs, filter.subject)
|
|
}
|
|
o.sigSubs = subs
|
|
return subs
|
|
}
|
|
|
|
// This is what will be called when our parent stream wants to kick us regarding a new message.
|
|
// We know that this subject matches us by how the parent handles registering us with the signaling sublist,
|
|
// but we must check if we are leader.
|
|
// We do need the sequence of the message however and we use the msg as the encoded seq.
|
|
func (o *consumer) processStreamSignal(seq uint64) {
|
|
// We can get called here now when not leader, so bail fast
|
|
// and without acquiring any locks.
|
|
if !o.leader.Load() {
|
|
return
|
|
}
|
|
o.mu.Lock()
|
|
defer o.mu.Unlock()
|
|
if o.mset == nil {
|
|
return
|
|
}
|
|
if seq > o.npf {
|
|
o.npc++
|
|
}
|
|
if seq < o.sseq {
|
|
return
|
|
}
|
|
if o.isPushMode() && o.active || o.isPullMode() && !o.waiting.isEmpty() {
|
|
o.signalNewMessages()
|
|
}
|
|
}
|
|
|
|
// Used to compare if two multiple filtered subject lists are equal.
|
|
func subjectSliceEqual(slice1 []string, slice2 []string) bool {
|
|
if len(slice1) != len(slice2) {
|
|
return false
|
|
}
|
|
set2 := make(map[string]struct{}, len(slice2))
|
|
for _, val := range slice2 {
|
|
set2[val] = struct{}{}
|
|
}
|
|
for _, val := range slice1 {
|
|
if _, ok := set2[val]; !ok {
|
|
return false
|
|
}
|
|
}
|
|
return true
|
|
}
|
|
|
|
// Utility for simpler if conditions in Consumer config checks.
|
|
// In future iteration, we can immediately create `o.subjf` and
|
|
// use it to validate things.
|
|
func gatherSubjectFilters(filter string, filters []string) []string {
|
|
if filter != _EMPTY_ {
|
|
filters = append(filters, filter)
|
|
}
|
|
// list of filters should never contain non-empty filter.
|
|
return filters
|
|
}
|
|
|
|
// shouldStartMonitor will return true if we should start a monitor
|
|
// goroutine or will return false if one is already running.
|
|
func (o *consumer) shouldStartMonitor() bool {
|
|
o.mu.Lock()
|
|
defer o.mu.Unlock()
|
|
|
|
if o.inMonitor {
|
|
return false
|
|
}
|
|
o.monitorWg.Add(1)
|
|
o.inMonitor = true
|
|
return true
|
|
}
|
|
|
|
// Clear the monitor running state. The monitor goroutine should
|
|
// call this in a defer to clean up on exit.
|
|
func (o *consumer) clearMonitorRunning() {
|
|
o.mu.Lock()
|
|
defer o.mu.Unlock()
|
|
|
|
if o.inMonitor {
|
|
o.monitorWg.Done()
|
|
o.inMonitor = false
|
|
}
|
|
}
|
|
|
|
// Test whether we are in the monitor routine.
|
|
func (o *consumer) isMonitorRunning() bool {
|
|
o.mu.RLock()
|
|
defer o.mu.RUnlock()
|
|
return o.inMonitor
|
|
}
|
|
|
|
// If we detect that our ackfloor is higher than the stream's last sequence, return this error.
|
|
var errAckFloorHigherThanLastSeq = errors.New("consumer ack floor is higher than streams last sequence")
|
|
var errAckFloorInvalid = errors.New("consumer ack floor is invalid")
|
|
|
|
// If we are a consumer of an interest or workqueue policy stream, process that state and make sure consistent.
|
|
func (o *consumer) checkStateForInterestStream(ss *StreamState) error {
|
|
o.mu.RLock()
|
|
// See if we need to process this update if our parent stream is not a limits policy stream.
|
|
mset := o.mset
|
|
shouldProcessState := mset != nil && o.retention != LimitsPolicy
|
|
if o.closed || !shouldProcessState || o.store == nil || ss == nil {
|
|
o.mu.RUnlock()
|
|
return nil
|
|
}
|
|
store := mset.store
|
|
state, err := o.store.State()
|
|
|
|
filters, subjf, filter := o.filters, o.subjf, _EMPTY_
|
|
var wc bool
|
|
if filters == nil && subjf != nil {
|
|
filter, wc = subjf[0].subject, subjf[0].hasWildcard
|
|
}
|
|
chkfloor := o.chkflr
|
|
o.mu.RUnlock()
|
|
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
asflr := state.AckFloor.Stream
|
|
// Protect ourselves against rolling backwards.
|
|
if asflr&(1<<63) != 0 {
|
|
return errAckFloorInvalid
|
|
}
|
|
dflr := asflr
|
|
if len(state.Pending) > 0 && state.Delivered.Stream > dflr {
|
|
dflr = state.Delivered.Stream
|
|
}
|
|
|
|
// Check if the underlying stream's last sequence is less than our floor.
|
|
// This can happen if the stream has been reset and has not caught up yet.
|
|
if asflr > ss.LastSeq {
|
|
return errAckFloorHigherThanLastSeq
|
|
}
|
|
|
|
var smv StoreMsg
|
|
var seq, nseq uint64
|
|
// Start at first stream seq or a previous check floor, whichever is higher.
|
|
// Note this will really help for interest retention, with WQ the loadNextMsg
|
|
// gets us a long way already since it will skip deleted msgs not for our filter.
|
|
fseq := ss.FirstSeq
|
|
if chkfloor > fseq {
|
|
fseq = chkfloor
|
|
}
|
|
|
|
var retryAsflr uint64
|
|
for seq = fseq; dflr > 0 && seq <= dflr; seq++ {
|
|
if filters != nil {
|
|
_, nseq, err = store.LoadNextMsgMulti(filters, seq, &smv)
|
|
} else {
|
|
_, nseq, err = store.LoadNextMsg(filter, wc, seq, &smv)
|
|
}
|
|
// if we advanced sequence update our seq. This can be on no error and EOF.
|
|
if nseq > seq {
|
|
seq = nseq
|
|
}
|
|
if err == nil {
|
|
// Only ack though if no error and seq <= ack floor.
|
|
if seq <= asflr {
|
|
didRemove := mset.ackMsg(o, seq)
|
|
// Removing the message could fail. For example if clustered since we need to propose it.
|
|
// Overwrite retry floor (only the first time) to allow us to check next time if the removal was successful.
|
|
if didRemove && retryAsflr == 0 {
|
|
retryAsflr = seq
|
|
}
|
|
} else if seq <= dflr {
|
|
// Store the first entry above our ack floor, so we don't need to look it up again on retryAsflr=0.
|
|
if retryAsflr == 0 {
|
|
retryAsflr = seq
|
|
}
|
|
// If we have pending, we will need to walk through to delivered in case we missed any of those acks as well.
|
|
if _, ok := state.Pending[seq]; !ok {
|
|
// The filters are already taken into account,
|
|
mset.ackMsg(o, seq)
|
|
}
|
|
}
|
|
} else if err == ErrStoreEOF {
|
|
break
|
|
}
|
|
}
|
|
// If retry floor was not overwritten, set to ack floor+1, we don't need to account for any retries below it.
|
|
// However, our ack floor may be lower than the next message we can receive, so we correct it upward if needed.
|
|
if retryAsflr == 0 {
|
|
if filters != nil {
|
|
_, nseq, err = store.LoadNextMsgMulti(filters, asflr+1, &smv)
|
|
} else {
|
|
_, nseq, err = store.LoadNextMsg(filter, wc, asflr+1, &smv)
|
|
}
|
|
if err == nil {
|
|
retryAsflr = max(asflr+1, nseq)
|
|
} else if err == ErrStoreEOF {
|
|
retryAsflr = ss.LastSeq + 1
|
|
}
|
|
}
|
|
|
|
o.mu.Lock()
|
|
// Update our check floor.
|
|
// Check floor must never be greater than ack floor+1, otherwise subsequent calls to this function would skip work.
|
|
if retryAsflr > o.chkflr {
|
|
o.chkflr = retryAsflr
|
|
}
|
|
o.mu.Unlock()
|
|
return nil
|
|
}
|
|
|
|
func (o *consumer) resetPtmr(delay time.Duration) {
|
|
// A delay of zero means it should be stopped.
|
|
if delay == 0 {
|
|
o.stopAndClearPtmr()
|
|
return
|
|
}
|
|
|
|
if o.ptmr == nil {
|
|
o.ptmr = time.AfterFunc(delay, o.checkPending)
|
|
} else {
|
|
o.ptmr.Reset(delay)
|
|
}
|
|
o.ptmrEnd = time.Now().Add(delay)
|
|
}
|
|
|
|
func (o *consumer) stopAndClearPtmr() {
|
|
// If the end time is unset, short-circuit since the timer will already be stopped.
|
|
if o.ptmrEnd.IsZero() {
|
|
return
|
|
}
|
|
stopAndClearTimer(&o.ptmr)
|
|
o.ptmrEnd = time.Time{}
|
|
}
|
|
|
|
func (o *consumer) resetPendingDeliveries() {
|
|
for _, pmsg := range o.pendingDeliveries {
|
|
pmsg.returnToPool()
|
|
}
|
|
o.pendingDeliveries = nil
|
|
for _, wd := range o.waitingDeliveries {
|
|
wd.recycle()
|
|
}
|
|
o.waitingDeliveries = nil
|
|
}
|