Files
nats-server/server/jetstream_consumer_test.go
2026-02-20 11:37:28 +01:00

11311 lines
316 KiB
Go

// Copyright 2022-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.
//go:build !skip_js_tests && !skip_js_consumer_tests
package server
import (
"bytes"
"context"
crand "crypto/rand"
"encoding/base64"
"encoding/json"
"errors"
"fmt"
"math/rand"
"net/url"
"os"
"reflect"
"runtime"
"slices"
"strconv"
"strings"
"sync"
"sync/atomic"
"testing"
"time"
"github.com/nats-io/nats.go/jetstream"
"github.com/nats-io/nats.go"
"github.com/nats-io/nuid"
)
func TestJetStreamConsumerMultipleFiltersRemoveFilters(t *testing.T) {
s := RunBasicJetStreamServer(t)
defer s.Shutdown()
nc, js := jsClientConnect(t, s)
defer nc.Close()
acc := s.GlobalAccount()
mset, err := acc.addStream(&StreamConfig{
Name: "TEST",
Retention: LimitsPolicy,
Subjects: []string{"one", "two", "three"},
MaxAge: time.Second * 90,
})
require_NoError(t, err)
_, err = mset.addConsumer(&ConsumerConfig{
Durable: "consumer",
FilterSubjects: []string{"one", "two"},
})
require_NoError(t, err)
sendStreamMsg(t, nc, "one", "data")
sendStreamMsg(t, nc, "two", "data")
sendStreamMsg(t, nc, "three", "data")
consumer, err := js.PullSubscribe("", "consumer", nats.Bind("TEST", "consumer"))
require_NoError(t, err)
msgs, err := consumer.Fetch(1)
require_NoError(t, err)
require_True(t, len(msgs) == 1)
_, err = mset.addConsumer(&ConsumerConfig{
Durable: "consumer",
FilterSubjects: []string{},
})
require_NoError(t, err)
msgs, err = consumer.Fetch(1)
require_NoError(t, err)
require_True(t, len(msgs) == 1)
msgs, err = consumer.Fetch(1)
require_NoError(t, err)
require_True(t, len(msgs) == 1)
}
func TestJetStreamConsumerMultipleFiltersRace(t *testing.T) {
s := RunBasicJetStreamServer(t)
defer s.Shutdown()
nc, js := jsClientConnect(t, s)
defer nc.Close()
acc := s.GlobalAccount()
mset, err := acc.addStream(&StreamConfig{
Name: "TEST",
Retention: LimitsPolicy,
Subjects: []string{"one", "two", "three", "four"},
MaxAge: time.Second * 90,
})
require_NoError(t, err)
var seqs []uint64
var mu sync.Mutex
total := 10_000
var wg sync.WaitGroup
send := func(subj string) {
defer wg.Done()
for i := 0; i < total; i++ {
sendStreamMsg(t, nc, subj, "data")
}
}
wg.Add(4)
go send("one")
go send("two")
go send("three")
go send("four")
wg.Wait()
mset.addConsumer(&ConsumerConfig{
Durable: "consumer",
FilterSubjects: []string{"one", "two", "three"},
AckPolicy: AckExplicit,
})
done := make(chan struct{})
wg.Add(10)
for i := 0; i < 10; i++ {
go func(t *testing.T) {
defer wg.Done()
c, err := js.PullSubscribe(_EMPTY_, "consumer", nats.Bind("TEST", "consumer"))
require_NoError(t, err)
for {
select {
case <-done:
return
default:
}
msgs, err := c.Fetch(10, nats.MaxWait(2*time.Second))
// We don't want to stop before at expected number of messages, as we want
// to also test against getting to many messages.
// Because of that, we ignore timeout and connection closed errors.
if err != nil && err != nats.ErrTimeout && err != nats.ErrConnectionClosed {
t.Errorf("error while fetching messages: %v", err)
}
for _, msg := range msgs {
info, err := msg.Metadata()
require_NoError(t, err)
mu.Lock()
seqs = append(seqs, info.Sequence.Consumer)
mu.Unlock()
msg.Ack()
}
}
}(t)
}
checkFor(t, 30*time.Second, 100*time.Millisecond, func() error {
mu.Lock()
defer mu.Unlock()
if len(seqs) != 3*total {
return fmt.Errorf("found %d messages instead of %d", len(seqs), 3*total)
}
slices.Sort(seqs)
for i := 1; i < len(seqs); i++ {
if seqs[i] != seqs[i-1]+1 {
fmt.Printf("seqs: %+v\n", seqs)
return fmt.Errorf("sequence mismatch at %v", i)
}
}
return nil
})
close(done)
wg.Wait()
}
func TestJetStreamConsumerMultipleConsumersSingleFilter(t *testing.T) {
s := RunBasicJetStreamServer(t)
defer s.Shutdown()
nc, js := jsClientConnect(t, s)
defer nc.Close()
acc := s.GlobalAccount()
// Setup few subjects with varying messages count.
subjects := []struct {
subject string
messages int
wc bool
}{
{subject: "one", messages: 5000},
{subject: "two", messages: 7500},
{subject: "three", messages: 2500},
{subject: "four", messages: 1000},
{subject: "five.>", messages: 3000, wc: true},
}
totalMsgs := 0
for _, subject := range subjects {
totalMsgs += subject.messages
}
// Setup consumers, filtering some of the messages from the stream.
consumers := []*struct {
name string
subjects []string
expectedMsgs int
delivered atomic.Int32
}{
{name: "C1", subjects: []string{"one"}, expectedMsgs: 5000},
{name: "C2", subjects: []string{"two"}, expectedMsgs: 7500},
{name: "C3", subjects: []string{"one"}, expectedMsgs: 5000},
{name: "C4", subjects: []string{"one"}, expectedMsgs: 5000},
}
mset, err := acc.addStream(&StreamConfig{
Name: "TEST",
Retention: LimitsPolicy,
Subjects: []string{"one", "two", "three", "four", "five.>"},
MaxAge: time.Second * 90,
})
require_NoError(t, err)
for c, consumer := range consumers {
_, err := mset.addConsumer(&ConsumerConfig{
Durable: consumer.name,
FilterSubjects: consumer.subjects,
AckPolicy: AckExplicit,
DeliverPolicy: DeliverAll,
AckWait: time.Second * 30,
DeliverSubject: nc.NewInbox(),
})
require_NoError(t, err)
go func(c int, name string) {
_, err = js.Subscribe("", func(m *nats.Msg) {
require_NoError(t, m.Ack())
require_NoError(t, err)
consumers[c].delivered.Add(1)
}, nats.Bind("TEST", name))
require_NoError(t, err)
}(c, consumer.name)
}
// Publish with random intervals, while consumers are active.
var wg sync.WaitGroup
for _, subject := range subjects {
wg.Add(subject.messages)
go func(subject string, messages int, wc bool) {
nc, js := jsClientConnect(t, s)
defer nc.Close()
time.Sleep(time.Duration(rand.Int63n(1000)+1) * time.Millisecond)
for i := 0; i < messages; i++ {
time.Sleep(time.Duration(rand.Int63n(1000)+1) * time.Microsecond)
// If subject has wildcard, add random last subject token.
pubSubject := subject
if wc {
pubSubject = fmt.Sprintf("%v.%v", subject, rand.Int63n(10))
}
_, err := js.PublishAsync(pubSubject, []byte("data"))
require_NoError(t, err)
wg.Done()
}
}(subject.subject, subject.messages, subject.wc)
}
wg.Wait()
checkFor(t, time.Second*10, time.Millisecond*500, func() error {
for _, consumer := range consumers {
info, err := js.ConsumerInfo("TEST", consumer.name)
require_NoError(t, err)
if info.Delivered.Consumer != uint64(consumer.expectedMsgs) {
return fmt.Errorf("%v:expected consumer delivered seq %v, got %v. actually delivered: %v",
consumer.name, consumer.expectedMsgs, info.Delivered.Consumer, consumer.delivered.Load())
}
if info.AckFloor.Consumer != uint64(consumer.expectedMsgs) {
return fmt.Errorf("%v: expected consumer ack floor %v, got %v", consumer.name, totalMsgs, info.AckFloor.Consumer)
}
if consumer.delivered.Load() != int32(consumer.expectedMsgs) {
return fmt.Errorf("%v: expected %v, got %v", consumer.name, consumer.expectedMsgs, consumer.delivered.Load())
}
}
return nil
})
}
func TestJetStreamConsumerMultipleConsumersMultipleFilters(t *testing.T) {
s := RunBasicJetStreamServer(t)
defer s.Shutdown()
nc, js := jsClientConnect(t, s)
defer nc.Close()
acc := s.GlobalAccount()
// Setup few subjects with varying messages count.
subjects := []struct {
subject string
messages int
wc bool
}{
{subject: "one", messages: 50},
{subject: "two", messages: 75},
{subject: "three", messages: 250},
{subject: "four", messages: 10},
{subject: "five.>", messages: 300, wc: true},
}
totalMsgs := 0
for _, subject := range subjects {
totalMsgs += subject.messages
}
// Setup consumers, filtering some of the messages from the stream.
consumers := []*struct {
name string
subjects []string
expectedMsgs int
delivered atomic.Int32
}{
{name: "C1", subjects: []string{"one", "two"}, expectedMsgs: 125},
{name: "C2", subjects: []string{"two", "three"}, expectedMsgs: 325},
{name: "C3", subjects: []string{"one", "three"}, expectedMsgs: 300},
{name: "C4", subjects: []string{"one", "five.>"}, expectedMsgs: 350},
}
mset, err := acc.addStream(&StreamConfig{
Name: "TEST",
Retention: LimitsPolicy,
Subjects: []string{"one", "two", "three", "four", "five.>"},
MaxAge: time.Second * 90,
})
require_NoError(t, err)
for c, consumer := range consumers {
_, err := mset.addConsumer(&ConsumerConfig{
Durable: consumer.name,
FilterSubjects: consumer.subjects,
AckPolicy: AckExplicit,
DeliverPolicy: DeliverAll,
AckWait: time.Second * 30,
DeliverSubject: nc.NewInbox(),
})
require_NoError(t, err)
go func(c int, name string) {
_, err = js.Subscribe("", func(m *nats.Msg) {
require_NoError(t, m.Ack())
require_NoError(t, err)
consumers[c].delivered.Add(1)
}, nats.Bind("TEST", name))
require_NoError(t, err)
}(c, consumer.name)
}
// Publish with random intervals, while consumers are active.
var wg sync.WaitGroup
for _, subject := range subjects {
wg.Add(subject.messages)
go func(subject string, messages int, wc bool) {
nc, js := jsClientConnect(t, s)
defer nc.Close()
time.Sleep(time.Duration(rand.Int63n(1000)+1) * time.Millisecond)
for i := 0; i < messages; i++ {
time.Sleep(time.Duration(rand.Int63n(1000)+1) * time.Microsecond)
// If subject has wildcard, add random last subject token.
pubSubject := subject
if wc {
pubSubject = fmt.Sprintf("%v.%v", subject, rand.Int63n(10))
}
ack, err := js.PublishAsync(pubSubject, []byte("data"))
require_NoError(t, err)
go func() {
ack.Ok()
wg.Done()
}()
}
}(subject.subject, subject.messages, subject.wc)
}
done := make(chan struct{})
go func() {
wg.Wait()
close(done)
}()
select {
case <-time.After(time.Second * 15):
t.Fatalf("Timed out waiting for acks")
case <-done:
}
wg.Wait()
checkFor(t, time.Second*15, time.Second*1, func() error {
for _, consumer := range consumers {
info, err := js.ConsumerInfo("TEST", consumer.name)
require_NoError(t, err)
if info.Delivered.Consumer != uint64(consumer.expectedMsgs) {
return fmt.Errorf("%v:expected consumer delivered seq %v, got %v. actually delivered: %v",
consumer.name, consumer.expectedMsgs, info.Delivered.Consumer, consumer.delivered.Load())
}
if info.AckFloor.Consumer != uint64(consumer.expectedMsgs) {
return fmt.Errorf("%v: expected consumer ack floor %v, got %v", consumer.name, totalMsgs, info.AckFloor.Consumer)
}
if consumer.delivered.Load() != int32(consumer.expectedMsgs) {
return fmt.Errorf("%v: expected %v, got %v", consumer.name, consumer.expectedMsgs, consumer.delivered.Load())
}
}
return nil
})
}
func TestJetStreamConsumerMultipleFiltersSequence(t *testing.T) {
s := RunBasicJetStreamServer(t)
defer s.Shutdown()
nc, js := jsClientConnect(t, s)
defer nc.Close()
acc := s.GlobalAccount()
mset, err := acc.addStream(&StreamConfig{
Name: "TEST",
Retention: LimitsPolicy,
Subjects: []string{"one", "two", "three", "four", "five.>"},
MaxAge: time.Second * 90,
})
require_NoError(t, err)
_, err = mset.addConsumer(&ConsumerConfig{
Durable: "DUR",
FilterSubjects: []string{"one", "two"},
AckPolicy: AckExplicit,
DeliverPolicy: DeliverAll,
AckWait: time.Second * 30,
DeliverSubject: nc.NewInbox(),
})
require_NoError(t, err)
for i := 0; i < 20; i++ {
sendStreamMsg(t, nc, "one", fmt.Sprintf("%d", i))
}
for i := 20; i < 40; i++ {
sendStreamMsg(t, nc, "two", fmt.Sprintf("%d", i))
}
for i := 40; i < 60; i++ {
sendStreamMsg(t, nc, "one", fmt.Sprintf("%d", i))
}
sub, err := js.SubscribeSync("", nats.Bind("TEST", "DUR"))
require_NoError(t, err)
for i := 0; i < 60; i++ {
msg, err := sub.NextMsg(time.Second * 1)
require_NoError(t, err)
require_True(t, string(msg.Data) == fmt.Sprintf("%d", i))
}
}
func TestJetStreamConsumerActions(t *testing.T) {
s := RunBasicJetStreamServer(t)
defer s.Shutdown()
nc, _ := jsClientConnect(t, s)
defer nc.Close()
acc := s.GlobalAccount()
mset, err := acc.addStream(&StreamConfig{
Name: "TEST",
Retention: LimitsPolicy,
Subjects: []string{"one", "two", "three", "four", "five.>"},
MaxAge: time.Second * 90,
})
require_NoError(t, err)
// Create Consumer. No consumers existed before, so should be fine.
_, err = mset.addConsumerWithAction(&ConsumerConfig{
Durable: "DUR",
FilterSubjects: []string{"one", "two"},
AckPolicy: AckExplicit,
DeliverPolicy: DeliverAll,
AckWait: time.Second * 30,
}, ActionCreate, false)
require_NoError(t, err)
// Create consumer again. Should be ok if action is CREATE but config is exactly the same.
_, err = mset.addConsumerWithAction(&ConsumerConfig{
Durable: "DUR",
FilterSubjects: []string{"one", "two"},
AckPolicy: AckExplicit,
DeliverPolicy: DeliverAll,
AckWait: time.Second * 30,
}, ActionCreate, false)
require_NoError(t, err)
// Create consumer again. Should error if action is CREATE.
_, err = mset.addConsumerWithAction(&ConsumerConfig{
Durable: "DUR",
FilterSubjects: []string{"one"},
AckPolicy: AckExplicit,
DeliverPolicy: DeliverAll,
AckWait: time.Second * 30,
}, ActionCreate, false)
require_Error(t, err)
// Update existing consumer. Should be fine, as consumer exists.
_, err = mset.addConsumerWithAction(&ConsumerConfig{
Durable: "DUR",
FilterSubjects: []string{"one"},
AckPolicy: AckExplicit,
DeliverPolicy: DeliverAll,
AckWait: time.Second * 30,
}, ActionUpdate, false)
require_NoError(t, err)
// Update consumer. Should error, as this consumer does not exist.
_, err = mset.addConsumerWithAction(&ConsumerConfig{
Durable: "NEW",
FilterSubjects: []string{"one"},
AckPolicy: AckExplicit,
DeliverPolicy: DeliverAll,
AckWait: time.Second * 30,
}, ActionUpdate, false)
require_Error(t, err)
// Create new ephemeral. Should be fine as the consumer doesn't exist already
_, err = mset.addConsumerWithAction(&ConsumerConfig{
Name: "EPH",
FilterSubjects: []string{"one"},
AckPolicy: AckExplicit,
DeliverPolicy: DeliverAll,
AckWait: time.Second * 30,
}, ActionCreate, false)
require_NoError(t, err)
// Trying to create it again right away. Should error as it already exists (and hasn't been cleaned up yet)
_, err = mset.addConsumerWithAction(&ConsumerConfig{
Name: "EPH",
FilterSubjects: []string{"one"},
AckPolicy: AckExplicit,
DeliverPolicy: DeliverAll,
AckWait: time.Second * 30,
}, ActionCreate, false)
require_Error(t, err)
}
func TestJetStreamConsumerActionsOnWorkQueuePolicyStream(t *testing.T) {
s := RunBasicJetStreamServer(t)
defer s.Shutdown()
nc, _ := jsClientConnect(t, s)
defer nc.Close()
acc := s.GlobalAccount()
mset, err := acc.addStream(&StreamConfig{
Name: "TEST",
Retention: WorkQueuePolicy,
Subjects: []string{"one", "two", "three", "four", "five.>"},
})
require_NoError(t, err)
_, err = mset.addConsumerWithAction(&ConsumerConfig{
Durable: "C1",
FilterSubjects: []string{"one", "two"},
AckPolicy: AckExplicit,
}, ActionCreate, false)
require_NoError(t, err)
_, err = mset.addConsumerWithAction(&ConsumerConfig{
Durable: "C2",
FilterSubjects: []string{"three", "four"},
AckPolicy: AckExplicit,
}, ActionCreate, false)
require_NoError(t, err)
_, err = mset.addConsumerWithAction(&ConsumerConfig{
Durable: "C3",
FilterSubjects: []string{"five.*"},
AckPolicy: AckExplicit,
}, ActionCreate, false)
require_NoError(t, err)
// Updating a consumer by removing a previous subject filter.
_, err = mset.addConsumerWithAction(&ConsumerConfig{
Durable: "C1",
FilterSubjects: []string{"one"}, // Remove a subject.
AckPolicy: AckExplicit,
}, ActionUpdate, false)
require_NoError(t, err)
// Updating a consumer without overlapping subjects.
_, err = mset.addConsumerWithAction(&ConsumerConfig{
Durable: "C2",
FilterSubjects: []string{"three", "four", "two"}, // Add previously removed subject.
AckPolicy: AckExplicit,
}, ActionUpdate, false)
require_NoError(t, err)
// Creating a consumer with overlapping subjects should return an error.
_, err = mset.addConsumerWithAction(&ConsumerConfig{
Durable: "C4",
FilterSubjects: []string{"one", "two", "three", "four"},
AckPolicy: AckExplicit,
}, ActionCreate, false)
require_Error(t, err)
if !IsNatsErr(err, JSConsumerWQConsumerNotUniqueErr) {
t.Errorf("want error %q, got %q", ApiErrors[JSConsumerWQConsumerNotUniqueErr], err)
}
// Updating a consumer with overlapping subjects should return an error.
_, err = mset.addConsumerWithAction(&ConsumerConfig{
Durable: "C3",
FilterSubjects: []string{"one", "two", "three", "four"},
AckPolicy: AckExplicit,
}, ActionUpdate, false)
require_Error(t, err)
if !IsNatsErr(err, JSConsumerWQConsumerNotUniqueErr) {
t.Errorf("want error %q, got %q", ApiErrors[JSConsumerWQConsumerNotUniqueErr], err)
}
}
func TestJetStreamConsumerActionsViaAPI(t *testing.T) {
s := RunBasicJetStreamServer(t)
defer s.Shutdown()
nc, _ := jsClientConnect(t, s)
defer nc.Close()
acc := s.GlobalAccount()
_, err := acc.addStream(&StreamConfig{
Name: "TEST",
Retention: LimitsPolicy,
Subjects: []string{"one"},
MaxAge: time.Second * 90,
})
require_NoError(t, err)
// Update non-existing consumer, which should fail.
request, err := json.Marshal(&CreateConsumerRequest{
Action: ActionUpdate,
Config: ConsumerConfig{
Durable: "hello",
},
Stream: "TEST",
})
require_NoError(t, err)
resp, err := nc.Request("$JS.API.CONSUMER.DURABLE.CREATE.TEST.hello", []byte(request), time.Second*6)
require_NoError(t, err)
var ccResp JSApiConsumerCreateResponse
err = json.Unmarshal(resp.Data, &ccResp)
require_NoError(t, err)
require_Error(t, ccResp.Error)
// create non existing consumer - which should be fine.
ccResp.Error = nil
request, err = json.Marshal(&CreateConsumerRequest{
Action: ActionCreate,
Config: ConsumerConfig{
Durable: "hello",
},
Stream: "TEST",
})
require_NoError(t, err)
resp, err = nc.Request("$JS.API.CONSUMER.DURABLE.CREATE.TEST.hello", []byte(request), time.Second*6)
require_NoError(t, err)
err = json.Unmarshal(resp.Data, &ccResp)
require_NoError(t, err)
if ccResp.Error != nil {
t.Fatalf("expected nil, got %v", ccResp.Error)
}
// re-create existing consumer - which should be an error.
ccResp.Error = nil
request, err = json.Marshal(&CreateConsumerRequest{
Action: ActionCreate,
Config: ConsumerConfig{
Durable: "hello",
FilterSubject: "one",
},
Stream: "TEST",
})
require_NoError(t, err)
resp, err = nc.Request("$JS.API.CONSUMER.DURABLE.CREATE.TEST.hello", []byte(request), time.Second*6)
require_NoError(t, err)
err = json.Unmarshal(resp.Data, &ccResp)
require_NoError(t, err)
if ccResp.Error == nil {
t.Fatalf("expected err, got nil")
}
// create a named ephemeral consumer
ccResp.Error = nil
request, err = json.Marshal(&CreateConsumerRequest{
Action: ActionCreate,
Config: ConsumerConfig{
Name: "ephemeral",
FilterSubject: "one",
},
Stream: "TEST",
})
require_NoError(t, err)
resp, err = nc.Request("$JS.API.CONSUMER.CREATE.TEST.ephemeral", []byte(request), time.Second*6)
require_NoError(t, err)
err = json.Unmarshal(resp.Data, &ccResp)
require_NoError(t, err)
// re-create existing consumer - which should be an error.
ccResp.Error = nil
request, err = json.Marshal(&CreateConsumerRequest{
Action: ActionCreate,
Config: ConsumerConfig{
Name: "ephemeral",
FilterSubject: "one",
},
Stream: "TEST",
})
require_NoError(t, err)
resp, err = nc.Request("$JS.API.CONSUMER.CREATE.TEST.ephemeral", []byte(request), time.Second*6)
require_NoError(t, err)
err = json.Unmarshal(resp.Data, &ccResp)
require_NoError(t, err)
if ccResp.Error == nil {
t.Fatalf("expected err, got nil")
}
}
func TestJetStreamConsumerActionsUnmarshal(t *testing.T) {
tests := []struct {
name string
given []byte
expected ConsumerAction
expectErr bool
}{
{name: "action create", given: []byte(`{"action": "create"}`), expected: ActionCreate},
{name: "action update", given: []byte(`{"action": "update"}`), expected: ActionUpdate},
{name: "no action", given: []byte("{}"), expected: ActionCreateOrUpdate},
{name: "unknown", given: []byte(`{"action": "unknown"}`), expected: ActionCreateOrUpdate, expectErr: true},
}
for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
var request CreateConsumerRequest
err := json.Unmarshal(test.given, &request)
fmt.Printf("given: %v, expected: %v\n", test.expectErr, err)
if !test.expectErr {
require_NoError(t, err)
} else {
require_Error(t, err)
}
require_True(t, test.expected == request.Action)
})
}
}
func TestJetStreamConsumerMultipleFiltersLastPerSubject(t *testing.T) {
c := createJetStreamClusterExplicit(t, "R3S", 3)
defer c.shutdown()
nc, js := jsClientConnect(t, c.randomServer())
defer nc.Close()
_, error := js.AddStream(&nats.StreamConfig{
Name: "TEST",
Subjects: []string{"one", "two"},
Replicas: 3,
})
require_NoError(t, error)
sendStreamMsg(t, nc, "one", "1")
sendStreamMsg(t, nc, "one", "2")
sendStreamMsg(t, nc, "one", "3")
sendStreamMsg(t, nc, "two", "1")
sendStreamMsg(t, nc, "two", "2")
sendStreamMsg(t, nc, "two", "3")
_, err := js.AddConsumer("TEST", &nats.ConsumerConfig{
Name: "C",
FilterSubjects: []string{"one", "two"},
DeliverPolicy: nats.DeliverLastPerSubjectPolicy,
Replicas: 3,
DeliverSubject: "deliver",
})
require_NoError(t, err)
consumer, err := js.SubscribeSync("", nats.Bind("TEST", "C"))
require_NoError(t, err)
// expect last message for subject "one"
msg, err := consumer.NextMsg(time.Second)
require_NoError(t, err)
require_Equal(t, "3", string(msg.Data))
require_Equal(t, "one", msg.Subject)
// expect last message for subject "two"
msg, err = consumer.NextMsg(time.Second)
require_NoError(t, err)
require_Equal(t, "3", string(msg.Data))
require_Equal(t, "two", msg.Subject)
}
func consumerWithFilterSubjects(filterSubjects []string) *consumer {
c := consumer{}
for _, filter := range filterSubjects {
sub := &subjectFilter{
subject: filter,
hasWildcard: subjectHasWildcard(filter),
tokenizedSubject: tokenizeSubjectIntoSlice(nil, filter),
}
c.subjf = append(c.subjf, sub)
}
return &c
}
func filterSubjects(n int) []string {
fs := make([]string, 0, n)
for {
literals := []string{"foo", "bar", nuid.Next(), "xyz", "abcdef"}
fs = append(fs, strings.Join(literals, "."))
if len(fs) == n {
return fs
}
// Create more filterSubjects by going through the literals and replacing one with the '*' wildcard.
l := len(literals)
for i := 0; i < l; i++ {
e := make([]string, l)
for j := 0; j < l; j++ {
if j == i {
e[j] = "*"
} else {
e[j] = literals[j]
}
}
fs = append(fs, strings.Join(e, "."))
if len(fs) == n {
return fs
}
}
}
}
func TestJetStreamConsumerIsFilteredMatch(t *testing.T) {
for _, test := range []struct {
name string
filterSubjects []string
subject string
result bool
}{
{"no filter", []string{}, "foo.bar", true},
{"literal match", []string{"foo.baz", "foo.bar"}, "foo.bar", true},
{"literal mismatch", []string{"foo.baz", "foo.bar"}, "foo.ban", false},
{"wildcard > match", []string{"bar.>", "foo.>"}, "foo.bar", true},
{"wildcard > match", []string{"bar.>", "foo.>"}, "bar.foo", true},
{"wildcard > mismatch", []string{"bar.>", "foo.>"}, "baz.foo", false},
{"wildcard * match", []string{"bar.*", "foo.*"}, "foo.bar", true},
{"wildcard * match", []string{"bar.*", "foo.*"}, "bar.foo", true},
{"wildcard * mismatch", []string{"bar.*", "foo.*"}, "baz.foo", false},
{"wildcard * match", []string{"foo.*.x", "foo.*.y"}, "foo.bar.x", true},
{"wildcard * match", []string{"foo.*.x", "foo.*.y", "foo.*.z"}, "foo.bar.z", true},
{"many mismatch", filterSubjects(100), "foo.bar.do.not.match.any.filter.subject", false},
{"many match", filterSubjects(100), "foo.bar.12345.xyz.abcdef", true}, // will be matched by "foo.bar.*.xyz.abcdef"
} {
test := test
t.Run(test.name, func(t *testing.T) {
t.Parallel()
c := consumerWithFilterSubjects(test.filterSubjects)
if res := c.isFilteredMatch(test.subject); res != test.result {
t.Fatalf("Subject %q filtered match of %v, should be %v, got %v",
test.subject, test.filterSubjects, test.result, res)
}
})
}
}
func TestJetStreamConsumerWorkQueuePolicyOverlap(t *testing.T) {
s := RunBasicJetStreamServer(t)
defer s.Shutdown()
nc, js := jsClientConnect(t, s)
defer nc.Close()
_, err := js.AddStream(&nats.StreamConfig{
Name: "TEST",
Subjects: []string{"foo.*.*"},
Retention: nats.WorkQueuePolicy,
})
require_NoError(t, err)
_, err = js.AddConsumer("TEST", &nats.ConsumerConfig{
Durable: "ConsumerA",
FilterSubject: "foo.bar.*",
AckPolicy: nats.AckExplicitPolicy,
})
require_NoError(t, err)
_, err = js.AddConsumer("TEST", &nats.ConsumerConfig{
Durable: "ConsumerB",
FilterSubject: "foo.*.bar",
AckPolicy: nats.AckExplicitPolicy,
})
require_Error(t, err)
require_True(t, strings.Contains(err.Error(), "unique"))
}
func TestJetStreamConsumerIsEqualOrSubsetMatch(t *testing.T) {
for _, test := range []struct {
name string
filterSubjects []string
subject string
result bool
}{
{"no filter", []string{}, "foo.bar", false},
{"literal match", []string{"foo.baz", "foo.bar"}, "foo.bar", true},
{"literal mismatch", []string{"foo.baz", "foo.bar"}, "foo.ban", false},
{"literal match", []string{"bar.>", "foo.>"}, "foo.>", true},
{"subset match", []string{"bar.foo.>", "foo.bar.>"}, "bar.>", true},
{"subset mismatch", []string{"bar.>", "foo.>"}, "baz.foo.>", false},
{"literal match", filterSubjects(100), "foo.bar.*.xyz.abcdef", true},
{"subset match", filterSubjects(100), "foo.bar.>", true},
} {
test := test
t.Run(test.name, func(t *testing.T) {
t.Parallel()
c := consumerWithFilterSubjects(test.filterSubjects)
if res := c.isEqualOrSubsetMatch(test.subject); res != test.result {
t.Fatalf("Subject %q subset match of %v, should be %v, got %v",
test.subject, test.filterSubjects, test.result, res)
}
})
}
}
func TestJetStreamConsumerBackOff(t *testing.T) {
s := RunBasicJetStreamServer(t)
defer s.Shutdown()
nc, js := jsClientConnect(t, s)
defer nc.Close()
for _, test := range []struct {
name string
config nats.ConsumerConfig
shouldErr bool
}{
{
name: "backoff_with_max_deliver",
config: nats.ConsumerConfig{
MaxDeliver: 3,
BackOff: []time.Duration{time.Second, time.Minute},
},
shouldErr: false,
},
{
name: "backoff_with_max_deliver_equal",
config: nats.ConsumerConfig{
MaxDeliver: 3,
BackOff: []time.Duration{time.Second, time.Minute, time.Hour},
},
shouldErr: false,
},
{
name: "backoff_with_max_deliver_equal_to_zero",
config: nats.ConsumerConfig{
MaxDeliver: 0,
BackOff: []time.Duration{},
},
shouldErr: false,
},
{
name: "backoff_with_max_deliver_smaller",
config: nats.ConsumerConfig{
MaxDeliver: 2,
BackOff: []time.Duration{time.Second, time.Minute, time.Hour},
},
shouldErr: true,
},
{
name: "backoff_with_default_max_deliver",
config: nats.ConsumerConfig{
BackOff: []time.Duration{time.Second, time.Minute, time.Hour},
},
shouldErr: false,
},
} {
t.Run(test.name, func(t *testing.T) {
_, err := js.AddStream(&nats.StreamConfig{
Name: test.name,
Subjects: []string{test.name},
})
require_NoError(t, err)
_, err = js.AddConsumer(test.name, &test.config)
require_True(t, test.shouldErr == (err != nil))
if test.shouldErr {
require_True(t, strings.Contains(err.Error(), "max deliver"))
}
// test if updating consumers works too.
test.config.Durable = "consumer"
_, err = js.AddConsumer(test.name, &nats.ConsumerConfig{
Durable: test.config.Durable,
})
require_NoError(t, err)
test.config.Description = "Updated"
_, err = js.UpdateConsumer(test.name, &test.config)
require_True(t, test.shouldErr == (err != nil))
if test.shouldErr {
require_True(t, strings.Contains(err.Error(), "max deliver"))
}
})
}
}
func TestJetStreamConsumerDelete(t *testing.T) {
tests := []struct {
name string
replicas int
}{
{"single server", 1},
{"clustered", 3},
}
for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
var s *Server
if test.replicas == 1 {
s = RunBasicJetStreamServer(t)
defer s.Shutdown()
} else {
c := createJetStreamClusterExplicit(t, "R3S", test.replicas)
defer c.shutdown()
s = c.randomServer()
}
nc, js := jsClientConnect(t, s)
defer nc.Close()
_, err := js.AddStream(&nats.StreamConfig{
Name: "TEST",
Subjects: []string{"events.>"},
MaxAge: time.Second * 90,
Replicas: test.replicas,
})
require_NoError(t, err)
_, err = js.AddConsumer("TEST", &nats.ConsumerConfig{
Durable: "consumer",
FilterSubject: "events.>",
Replicas: test.replicas,
})
require_NoError(t, err)
js.Publish("events.1", []byte("hello"))
cr := JSApiConsumerGetNextRequest{
Batch: 10,
Expires: time.Second * 30,
}
crBytes, err := json.Marshal(cr)
require_NoError(t, err)
inbox := nats.NewInbox()
consumerSub, err := nc.SubscribeSync(inbox)
require_NoError(t, err)
err = nc.PublishRequest(fmt.Sprintf(JSApiRequestNextT, "TEST", "consumer"), inbox, crBytes)
require_NoError(t, err)
msg, err := consumerSub.NextMsg(time.Second * 30)
require_NoError(t, err)
require_Equal(t, "hello", string(msg.Data))
js.DeleteConsumer("TEST", "consumer")
msg, err = consumerSub.NextMsg(time.Second * 30)
require_NoError(t, err)
if !strings.Contains(string(msg.Header.Get("Description")), "Consumer Deleted") {
t.Fatalf("Expected exclusive consumer error, got %q", msg.Header.Get("Description"))
}
})
}
}
func TestJetStreamConsumerFetchWithDrain(t *testing.T) {
t.Skip()
test := func(t *testing.T, cc *nats.ConsumerConfig) {
s := RunBasicJetStreamServer(t)
defer s.Shutdown()
nc, js := jsClientConnect(t, s)
defer nc.Close()
_, err := js.AddStream(&nats.StreamConfig{
Name: "TEST",
Subjects: []string{"foo"},
Retention: nats.LimitsPolicy,
})
require_NoError(t, err)
_, err = js.AddConsumer("TEST", cc)
require_NoError(t, err)
const messages = 10_000
for i := 0; i < messages; i++ {
sendStreamMsg(t, nc, "foo", fmt.Sprintf("%d", i+1))
}
cr := JSApiConsumerGetNextRequest{
Batch: 100_000,
Expires: 10 * time.Second,
}
crBytes, err := json.Marshal(cr)
require_NoError(t, err)
msgs := make(map[int]int)
processMsg := func(t *testing.T, sub *nats.Subscription, msgs map[int]int) bool {
msg, err := sub.NextMsg(time.Second)
if err != nil {
return false
}
metadata, err := msg.Metadata()
require_NoError(t, err)
require_NoError(t, msg.Ack())
v, err := strconv.Atoi(string(msg.Data))
require_NoError(t, err)
require_Equal(t, uint64(v), metadata.Sequence.Stream)
if _, ok := msgs[int(metadata.Sequence.Stream-1)]; !ok && len(msgs) > 0 {
t.Logf("Stream Sequence gap detected: current %d", metadata.Sequence.Stream)
}
if _, ok := msgs[int(metadata.Sequence.Stream)]; ok {
t.Fatalf("Message for seq %d has been seen before", metadata.Sequence.Stream)
}
// We do not expect official redeliveries here so this should always be 1.
if metadata.NumDelivered != 1 {
t.Errorf("Expected NumDelivered of 1, got %d for seq %d",
metadata.NumDelivered, metadata.Sequence.Stream)
}
msgs[int(metadata.Sequence.Stream)] = int(metadata.NumDelivered)
return true
}
for {
inbox := nats.NewInbox()
sub, err := nc.SubscribeSync(inbox)
require_NoError(t, err)
err = nc.PublishRequest(fmt.Sprintf(JSApiRequestNextT, "TEST", "C"), inbox, crBytes)
require_NoError(t, err)
// Drain after first message processed.
processMsg(t, sub, msgs)
sub.Drain()
for {
if !processMsg(t, sub, msgs) {
if len(msgs) == messages {
return
}
break
}
}
}
}
t.Run("no-backoff", func(t *testing.T) {
test(t, &nats.ConsumerConfig{
Durable: "C",
AckPolicy: nats.AckExplicitPolicy,
AckWait: 20 * time.Second,
})
})
t.Run("with-backoff", func(t *testing.T) {
test(t, &nats.ConsumerConfig{
Durable: "C",
AckPolicy: nats.AckExplicitPolicy,
AckWait: 20 * time.Second,
BackOff: []time.Duration{25 * time.Millisecond, 100 * time.Millisecond, 250 * time.Millisecond},
})
})
}
func TestJetStreamConsumerLongSubjectHang(t *testing.T) {
s := RunBasicJetStreamServer(t)
defer s.Shutdown()
nc, js := jsClientConnect(t, s)
defer nc.Close()
readSubj := "a1."
purgeSubj := "a2."
_, err := js.AddStream(&nats.StreamConfig{
Name: "TEST",
Subjects: []string{readSubj + ">", purgeSubj + ">"},
AllowRollup: true,
})
require_NoError(t, err)
prefix := strings.Repeat("a", 22)
for i := 0; i < 2; i++ {
subj := readSubj + prefix + fmt.Sprintf("%d", i)
_, err = js.Publish(subj, []byte("hello"))
require_NoError(t, err)
chunkSubj := purgeSubj + fmt.Sprintf("%d", i)
_, err = js.Publish(chunkSubj, []byte("contents"))
require_NoError(t, err)
}
err = js.PurgeStream("TEST", &nats.StreamPurgeRequest{Subject: purgeSubj + ">"})
require_NoError(t, err)
si, err := js.StreamInfo("TEST")
require_NoError(t, err)
// we should have 2 msgs left after purge
require_Equal(t, si.State.Msgs, 2)
sub, err := js.SubscribeSync(readSubj+">", nats.OrderedConsumer(), nats.DeliverLastPerSubject())
require_NoError(t, err)
defer sub.Unsubscribe()
for i := 0; i < 2; i++ {
m, err := sub.NextMsg(500 * time.Millisecond)
require_NoError(t, err)
require_True(t, string(m.Data) == "hello")
}
}
func TestJetStreamConsumerPedanticMode(t *testing.T) {
singleServerTemplate := `
listen: 127.0.0.1:-1
jetstream: {
max_mem_store: 2MB,
max_file_store: 8MB,
store_dir: '%s',
limits: {max_request_batch: 250}
}
no_auth_user: u
accounts {
ONE {
users = [ { user: "u", pass: "s3cr3t!" } ]
jetstream: enabled
}
$SYS { users = [ { user: "admin", pass: "s3cr3t!" } ] }
}`
clusterTemplate := `
listen: 127.0.0.1:-1
server_name: %s
jetstream: {
max_mem_store: 2MB,
max_file_store: 8MB,
store_dir: '%s',
limits: {max_request_batch: 250}
}
cluster {
name: %s
listen: 127.0.0.1:%d
routes = [%s]
}
no_auth_user: u
accounts {
ONE {
users = [ { user: "u", pass: "s3cr3t!" } ]
jetstream: enabled
}
$SYS { users = [ { user: "admin", pass: "s3cr3t!" } ] }
}`
s := RunBasicJetStreamServer(t)
defer s.Shutdown()
nc, _ := jsClientConnect(t, s)
defer nc.Close()
tests := []struct {
name string
givenConfig ConsumerConfig
givenLimits nats.StreamConsumerLimits
serverTemplateSingle string
serverTemplateCluster string
shouldError bool
pedantic bool
replicas int
}{
{
name: "default_non_pedantic",
givenConfig: ConsumerConfig{
Durable: "durable",
},
givenLimits: nats.StreamConsumerLimits{
InactiveThreshold: time.Minute,
MaxAckPending: 100,
},
shouldError: false,
pedantic: false,
},
{
name: "default_pedantic_inactive_threshold",
givenConfig: ConsumerConfig{
Durable: "durable",
},
givenLimits: nats.StreamConsumerLimits{
InactiveThreshold: time.Minute,
},
shouldError: true,
pedantic: true,
},
{
name: "default_pedantic_max_ack_pending",
givenConfig: ConsumerConfig{
Durable: "durable",
},
givenLimits: nats.StreamConsumerLimits{
MaxAckPending: 100,
},
shouldError: true,
pedantic: true,
},
{
name: "pedantic_backoff_no_ack_wait",
givenConfig: ConsumerConfig{
Durable: "durable",
BackOff: []time.Duration{time.Second, time.Minute},
},
pedantic: true,
shouldError: true,
},
{
name: "backoff_no_ack_wait",
givenConfig: ConsumerConfig{
Durable: "durable",
BackOff: []time.Duration{time.Second, time.Minute},
},
pedantic: false,
shouldError: false,
},
{
name: "max_batch_requests",
givenConfig: ConsumerConfig{
Durable: "durable",
},
serverTemplateSingle: singleServerTemplate,
serverTemplateCluster: clusterTemplate,
pedantic: false,
shouldError: false,
},
{
name: "pedantic_max_batch_requests",
givenConfig: ConsumerConfig{
Durable: "durable",
},
serverTemplateSingle: singleServerTemplate,
serverTemplateCluster: clusterTemplate,
pedantic: true,
shouldError: true,
},
}
for _, test := range tests {
for _, mode := range []string{"clustered", "single"} {
t.Run(fmt.Sprintf("%v_%v", mode, test.name), func(t *testing.T) {
var s *Server
if mode == "single" {
s = RunBasicJetStreamServer(t)
defer s.Shutdown()
} else {
c := createJetStreamClusterExplicit(t, "R3S", 3)
defer c.shutdown()
s = c.randomServer()
}
replicas := 1
if mode == "clustered" {
replicas = 3
}
nc, js := jsClientConnect(t, s)
defer nc.Close()
js.AddStream(&nats.StreamConfig{
Name: test.name,
Subjects: []string{"foo"},
Replicas: replicas,
ConsumerLimits: nats.StreamConsumerLimits{
InactiveThreshold: time.Minute,
MaxAckPending: 100,
},
})
_, err := addConsumerWithError(t, nc, &CreateConsumerRequest{
Stream: test.name,
Config: test.givenConfig,
Action: ActionCreateOrUpdate,
Pedantic: test.pedantic,
})
require_True(t, (err != nil) == test.shouldError)
if err != nil {
require_True(t, strings.Contains(err.Error(), "pedantic"))
}
})
}
}
}
func TestJetStreamConsumerStuckAckPending(t *testing.T) {
s := RunBasicJetStreamServer(t)
defer s.Shutdown()
nc, js := jsClientConnect(t, s)
defer nc.Close()
type ActiveWorkItem struct {
ID int
Expiry time.Time
}
_, err := js.AddStream(&nats.StreamConfig{
Name: "TEST_ACTIVE_WORK_ITEMS",
Discard: nats.DiscardOld,
MaxMsgsPerSubject: 1,
Subjects: []string{"TEST_ACTIVE_WORK_ITEMS.>"},
})
require_NoError(t, err)
_, err = js.AddConsumer("TEST_ACTIVE_WORK_ITEMS", &nats.ConsumerConfig{
Durable: "testactiveworkitemsconsumer",
AckPolicy: nats.AckExplicitPolicy,
MaxAckPending: -1,
MaxWaiting: 20000,
AckWait: 15 * time.Second,
})
require_NoError(t, err)
sub, err := js.PullSubscribe("TEST_ACTIVE_WORK_ITEMS.>", "testactiveworkitemsconsumer", nats.BindStream("TEST_ACTIVE_WORK_ITEMS"))
require_NoError(t, err)
errs := make(chan error)
go func() {
for {
msgs, err := sub.Fetch(200)
if err != nil {
// test is done. stop the loop.
if errors.Is(err, nats.ErrSubscriptionClosed) || errors.Is(err, nats.ErrConnectionClosed) {
return
}
if !errors.Is(err, nats.ErrTimeout) {
errs <- err
return
}
continue
}
for _, msg := range msgs {
msg := msg
var workItem ActiveWorkItem
if err := json.Unmarshal(msg.Data, &workItem); err != nil {
errs <- err
return
}
now := time.Now()
// If the work item has not expired, nak it with the respective delay.
if workItem.Expiry.After(now) {
msg.NakWithDelay(workItem.Expiry.Sub(now))
} else {
msg.Ack()
}
}
}
}()
for i := 0; i < 25_000; i++ {
// Publish item to TEST_ACTIVE_WORK_ITEMS stream with an expiry time.
workItem := ActiveWorkItem{ID: i, Expiry: time.Now().Add(30 * time.Second)}
data, err := json.Marshal(workItem)
require_NoError(t, err)
_, err = js.Publish(fmt.Sprintf("TEST_ACTIVE_WORK_ITEMS.%d", i), data)
require_NoError(t, err)
// Update expiry time and republish item to TEST_ACTIVE_WORK_ITEMS stream.
workItem.Expiry = time.Now().Add(3 * time.Second)
data, err = json.Marshal(workItem)
require_NoError(t, err)
_, err = js.Publish(fmt.Sprintf("TEST_ACTIVE_WORK_ITEMS.%d", i), data)
require_NoError(t, err)
}
noChange := false
lastNumAckPending := 0
checkFor(t, 60*time.Second, 3*time.Second, func() error {
select {
case err := <-errs:
t.Fatalf("consumer goroutine failed: %v", err)
default:
}
ci, err := js.ConsumerInfo("TEST_ACTIVE_WORK_ITEMS", "testactiveworkitemsconsumer")
require_NoError(t, err)
if lastNumAckPending != 0 && lastNumAckPending == ci.NumAckPending {
noChange = true
}
lastNumAckPending = ci.NumAckPending
// If we have no change since last check, we can fail the test before `totalWait` timeout.
if ci.NumAckPending > 0 && ci.NumPending == 0 {
if noChange {
_, err := sub.Fetch(1)
if err != nil && errors.Is(err, nats.ErrTimeout) {
t.Fatalf("num ack pending: %d\t num pending: %v\n", ci.NumAckPending, ci.NumPending)
}
}
return fmt.Errorf("num ack pending: %d\t num pending: %v\n", ci.NumAckPending, ci.NumPending)
}
return nil
})
}
func TestJetStreamConsumerPinned(t *testing.T) {
s := RunBasicJetStreamServer(t)
defer s.Shutdown()
nc, js := jsClientConnect(t, s)
defer nc.Close()
acc := s.GlobalAccount()
mset, err := acc.addStream(&StreamConfig{
Name: "TEST",
Subjects: []string{"foo.>", "bar", "baz"},
Retention: LimitsPolicy,
})
require_NoError(t, err)
_, err = mset.addConsumer(&ConsumerConfig{
Durable: "C",
FilterSubject: "foo.>",
PriorityGroups: []string{"A"},
PriorityPolicy: PriorityPinnedClient,
AckPolicy: AckExplicit,
PinnedTTL: 10 * time.Second,
})
require_NoError(t, err)
for i := 0; i < 100; i++ {
msg := nats.NewMsg(fmt.Sprintf("foo.%d", i))
msg.Data = []byte(fmt.Sprintf("msg-%d", i))
// Add headers to check if we properly serialize Nats-Pin-Id with and without headers.
if i%2 == 0 {
msg.Header.Add("Some-Header", "Value")
}
_, err = js.PublishMsg(msg)
require_NoError(t, err)
}
req := JSApiConsumerGetNextRequest{Batch: 3, Expires: 5 * time.Second, PriorityGroup: PriorityGroup{
Group: "A",
}}
reqb, _ := json.Marshal(req)
reply := "ONE"
replies, err := nc.SubscribeSync(reply)
require_NoError(t, err)
defer replies.Drain()
require_NoError(t, nc.PublishRequest("$JS.API.CONSUMER.MSG.NEXT.TEST.C", reply, reqb))
reply2 := "TWO"
replies2, err := nc.SubscribeSync(reply2)
require_NoError(t, err)
defer replies2.Drain()
require_NoError(t, nc.PublishRequest("$JS.API.CONSUMER.MSG.NEXT.TEST.C", reply2, reqb))
// This is the first Pull Request, so it should become the pinned one.
msg, err := replies.NextMsg(time.Second)
require_NoError(t, err)
require_NotNil(t, msg)
// Check if we are really pinned.
pinned := msg.Header.Get("Nats-Pin-Id")
if pinned == "" {
t.Fatalf("Expected pinned message, got none")
}
// Here, we should have pull request that just idles, as it is not pinned.
_, err = replies2.NextMsg(time.Second)
require_Error(t, err)
// While the pinned one continues to get messages.
msg, err = replies.NextMsg(time.Second)
require_NoError(t, err)
require_NotNil(t, msg)
// Just making sure that the other one does not get round-robined message.
_, err = replies2.NextMsg(time.Second)
require_Error(t, err)
// Now let's send a request with wrong pinned id.
req = JSApiConsumerGetNextRequest{Batch: 3, Expires: 250 * time.Millisecond, PriorityGroup: PriorityGroup{
Id: "WRONG",
Group: "A",
}}
reqBad, err := json.Marshal(req)
require_NoError(t, err)
replies3, err := nc.SubscribeSync(reply)
require_NoError(t, err)
defer replies3.Drain()
require_NoError(t, nc.PublishRequest("$JS.API.CONSUMER.MSG.NEXT.TEST.C", reply, reqBad))
// and make sure we got error telling us it's wrong ID.
msg, err = replies3.NextMsg(time.Second)
require_NoError(t, err)
if msg.Header.Get("Status") != "423" {
t.Fatalf("Expected 423, got %v", msg.Header.Get("Status"))
}
// Send a new request with a good pinned ID.
req = JSApiConsumerGetNextRequest{Batch: 3, Expires: 250 * time.Millisecond, PriorityGroup: PriorityGroup{
Id: pinned,
Group: "A",
}}
reqb, _ = json.Marshal(req)
reply = "FOUR"
replies4, err := nc.SubscribeSync(reply)
require_NoError(t, err)
defer replies4.Drain()
require_NoError(t, nc.PublishRequest("$JS.API.CONSUMER.MSG.NEXT.TEST.C", reply, reqb))
// and check that we got a message.
msg, err = replies4.NextMsg(time.Second)
require_NoError(t, err)
require_NotNil(t, msg)
advisories, err := nc.SubscribeSync("$JS.EVENT.ADVISORY.CONSUMER.*.TEST.C")
require_NoError(t, err)
// Send a new request without pin ID, which should work after the TTL.
req = JSApiConsumerGetNextRequest{Batch: 3, Expires: 50 * time.Second, PriorityGroup: PriorityGroup{
Group: "A",
}}
reqb, _ = json.Marshal(req)
reply = "FIVE"
replies5, err := nc.SubscribeSync(reply)
require_NoError(t, err)
defer replies5.Drain()
require_NoError(t, nc.PublishRequest("$JS.API.CONSUMER.MSG.NEXT.TEST.C", reply, reqb))
checkFor(t, 20*time.Second, 1*time.Second, func() error {
_, err = replies5.NextMsg(500 * time.Millisecond)
if err == nil {
return nil
}
return err
})
advisory, err := advisories.NextMsg(time.Second)
require_NoError(t, err)
require_Equal(t, fmt.Sprintf("%s.TEST.C", JSAdvisoryConsumerUnpinnedPre), advisory.Subject)
advisory, err = advisories.NextMsg(time.Second)
require_NoError(t, err)
require_Equal(t, fmt.Sprintf("%s.TEST.C", JSAdvisoryConsumerPinnedPre), advisory.Subject)
// Manually unpin the current fetch request.
request := JSApiConsumerUnpinRequest{Group: "A"}
requestData, err := json.Marshal(request)
require_NoError(t, err)
msg, err = nc.Request("$JS.API.CONSUMER.UNPIN.TEST.C", requestData, time.Second*1)
require_NoError(t, err)
var response JSApiConsumerUnpinResponse
err = json.Unmarshal(msg.Data, &response)
require_NoError(t, err)
require_True(t, response.Error == nil)
// check if we got proper advisories.
advisory, err = advisories.NextMsg(time.Second)
require_NoError(t, err)
require_Equal(t, fmt.Sprintf("%s.TEST.C", JSAdvisoryConsumerUnpinnedPre), advisory.Subject)
replies6, err := nc.SubscribeSync(reply)
require_NoError(t, err)
defer replies6.Drain()
require_NoError(t, nc.PublishRequest("$JS.API.CONSUMER.MSG.NEXT.TEST.C", reply, reqBad))
_, err = replies6.NextMsg(time.Second * 5)
require_NoError(t, err)
}
func TestJetStreamConsumerPinnedUnsetsAfterAtMostPinnedTTL(t *testing.T) {
test := func(t *testing.T, publish bool) {
s := RunBasicJetStreamServer(t)
defer s.Shutdown()
nc, js := jsClientConnect(t, s)
defer nc.Close()
acc := s.GlobalAccount()
mset, err := acc.addStream(&StreamConfig{
Name: "TEST",
Subjects: []string{"foo"},
Retention: LimitsPolicy,
})
require_NoError(t, err)
if publish {
_, err = js.Publish("foo", nil)
require_NoError(t, err)
}
o, err := mset.addConsumer(&ConsumerConfig{
Durable: "C",
FilterSubject: "foo",
PriorityGroups: []string{"A"},
PriorityPolicy: PriorityPinnedClient,
AckPolicy: AckExplicit,
PinnedTTL: time.Second,
})
require_NoError(t, err)
// We have a too large expiry with respect to the PinnedTTL, we can only keep the pin for 2xPinnedTTL at most.
req := JSApiConsumerGetNextRequest{Batch: 2, Expires: 2 * time.Second, PriorityGroup: PriorityGroup{Group: "A"}}
reqb, err := json.Marshal(req)
require_NoError(t, err)
reply := "ONE"
sub, err := nc.SubscribeSync(reply)
require_NoError(t, err)
defer sub.Drain()
require_NoError(t, nc.PublishRequest(fmt.Sprintf(JSApiRequestNextT, "TEST", "C"), reply, reqb))
// The pin should be held for PinnedTTL at most, especially since the expiry is way larger.
var pinId string
start := time.Now()
for time.Since(start) < 750*time.Millisecond {
o.mu.RLock()
currentPinId := o.currentPinId
o.mu.RUnlock()
if pinId == _EMPTY_ {
pinId = currentPinId
} else {
// The pin ID shouldn't change.
require_Equal(t, pinId, currentPinId)
}
time.Sleep(100 * time.Millisecond)
}
// If a message was published, we should be pinned when this message was delivered.
if publish {
require_NotEqual(t, pinId, _EMPTY_)
// Wait some time so we can check we're not unpinned. After PinnedTTL it should be reset,
// even if the client's expiry was longer or its pull request is still there.
time.Sleep(500 * time.Millisecond)
}
// If we didn't get any messages delivered (or after timeout) the consumer should be unpinned.
o.mu.RLock()
currentPinId := o.currentPinId
o.mu.RUnlock()
require_Equal(t, currentPinId, _EMPTY_)
// Finally, check that:
// - We received the published message first (if it was published).
// - We receive the request timeout for the pull expiring.
if publish {
msg, err := sub.NextMsg(200 * time.Millisecond)
require_NoError(t, err)
require_Equal(t, msg.Header.Get("Nats-Pin-Id"), pinId)
require_Equal(t, msg.Subject, "foo")
}
msg, err := sub.NextMsg(3 * time.Second)
require_NoError(t, err)
require_Equal(t, msg.Header.Get("Status"), "408")
require_Equal(t, msg.Header.Get("Description"), "Request Timeout")
}
t.Run("Publish", func(t *testing.T) { test(t, true) })
t.Run("NoMessages", func(t *testing.T) { test(t, false) })
}
func TestJetStreamConsumerPinnedUnsubscribeOnPinned(t *testing.T) {
s := RunBasicJetStreamServer(t)
defer s.Shutdown()
nc, js := jsClientConnect(t, s)
defer nc.Close()
acc := s.GlobalAccount()
mset, err := acc.addStream(&StreamConfig{
Name: "TEST",
Subjects: []string{"foo.>", "bar", "baz"},
Retention: LimitsPolicy,
})
require_NoError(t, err)
_, err = mset.addConsumer(&ConsumerConfig{
Durable: "C",
FilterSubject: "foo.>",
PriorityGroups: []string{"A"},
PriorityPolicy: PriorityPinnedClient,
AckPolicy: AckExplicit,
PinnedTTL: time.Second,
})
require_NoError(t, err)
_, err = js.Publish("foo.1", []byte("hello"))
require_NoError(t, err)
req := JSApiConsumerGetNextRequest{Batch: 3, Expires: 5 * time.Second, Heartbeat: 250 * time.Millisecond, PriorityGroup: PriorityGroup{
Group: "A",
}}
reqb, _ := json.Marshal(req)
reply := "ONE"
replies, err := nc.SubscribeSync(reply)
require_NoError(t, err)
defer replies.Drain()
require_NoError(t, nc.PublishRequest("$JS.API.CONSUMER.MSG.NEXT.TEST.C", reply, reqb))
reply2 := "TWO"
replies2, err := nc.SubscribeSync(reply2)
require_NoError(t, err)
defer replies2.Drain()
require_NoError(t, nc.PublishRequest("$JS.API.CONSUMER.MSG.NEXT.TEST.C", reply2, reqb))
// This is the first Pull Request, so it should become the pinned one.
msg, err := replies.NextMsg(time.Second)
require_NoError(t, err)
require_NotNil(t, msg)
// Check if we are really pinned.
pinned := msg.Header.Get("Nats-Pin-Id")
if pinned == "" {
t.Fatalf("Expected pinned message, got none")
}
err = replies.Unsubscribe()
require_NoError(t, err)
_, err = js.Publish("foo.1", []byte("hello"))
require_NoError(t, err)
// Here, we should receive a heartbeat message.
msg, err = replies2.NextMsg(time.Second)
require_NoError(t, err)
require_NotNil(t, msg)
require_Equal(t, msg.Header.Get("Status"), "100")
// receive heartbeats until pinned ttl expires
// and we should get a new message with new pin id
for {
msg, err = replies2.NextMsg(time.Second)
require_NoError(t, err)
require_NotNil(t, msg)
if msg.Header.Get("Status") == "100" {
continue
}
if msg.Header.Get("Nats-Pin-Id") == "" {
t.Fatalf("Expected pinned message, got none")
}
break
}
}
// This tests if Unpin works correctly when there are no pending messages.
// It checks if the next pinned client will be different than the first one
// after new messages is published.
func TestJetStreamConsumerUnpinNoMessages(t *testing.T) {
s := RunBasicJetStreamServer(t)
defer s.Shutdown()
nc, _ := jsClientConnect(t, s)
defer nc.Close()
acc := s.GlobalAccount()
mset, err := acc.addStream(&StreamConfig{
Name: "TEST",
Subjects: []string{"foo"},
Retention: LimitsPolicy,
})
require_NoError(t, err)
_, err = mset.addConsumer(&ConsumerConfig{
Durable: "C",
FilterSubject: "foo",
PriorityGroups: []string{"A"},
PriorityPolicy: PriorityPinnedClient,
AckPolicy: AckExplicit,
PinnedTTL: 30 * time.Second,
})
require_NoError(t, err)
req := JSApiConsumerGetNextRequest{Batch: 30, Expires: 60 * time.Second, PriorityGroup: PriorityGroup{
Group: "A",
}}
reqb, _ := json.Marshal(req)
reply := "ONE"
replies, err := nc.SubscribeSync(reply)
require_NoError(t, err)
defer replies.Drain()
require_NoError(t, nc.PublishRequest("$JS.API.CONSUMER.MSG.NEXT.TEST.C", reply, reqb))
reply2 := "TWO"
replies2, err := nc.SubscribeSync(reply2)
require_NoError(t, err)
defer replies2.Drain()
require_NoError(t, nc.PublishRequest("$JS.API.CONSUMER.MSG.NEXT.TEST.C", reply2, reqb))
sendStreamMsg(t, nc, "foo", "data")
sendStreamMsg(t, nc, "foo", "data")
msg, err := replies.NextMsg(1 * time.Second)
pinId := msg.Header.Get("Nats-Pin-Id")
require_NotEqual(t, pinId, "")
require_NoError(t, err)
_, err = replies.NextMsg(1 * time.Second)
require_NoError(t, err)
_, err = replies2.NextMsg(1 * time.Second)
require_Error(t, err)
unpinRequest := func(t *testing.T, nc *nats.Conn, stream, consumer, group string) *ApiError {
var response JSApiConsumerUnpinResponse
request := JSApiConsumerUnpinRequest{Group: group}
requestData, err := json.Marshal(request)
require_NoError(t, err)
msg, err := nc.Request(fmt.Sprintf("$JS.API.CONSUMER.UNPIN.%s.%s", stream, consumer), requestData, time.Second*1)
require_NoError(t, err)
err = json.Unmarshal(msg.Data, &response)
require_NoError(t, err)
return response.Error
}
unpinError := unpinRequest(t, nc, "TEST", "C", "A")
require_True(t, unpinError == nil)
sendStreamMsg(t, nc, "foo", "data")
sendStreamMsg(t, nc, "foo", "data")
// Old pinned client should get info that it is no longer pinned.
msg, err = replies.NextMsg(1 * time.Second)
require_NoError(t, err)
require_Equal(t, msg.Header.Get("Status"), "423")
// While the new one should get the message and new pin.
msg, err = replies2.NextMsg(1 * time.Second)
require_NoError(t, err)
require_Equal(t, string(msg.Data), "data")
require_NotEqual(t, msg.Header.Get("Nats-Pin-Id"), pinId)
}
// In some scenarios, if the next waiting request is the same as the old pinned, it could be picked as a new pin.
// This test replicates that behavior and checks if the new pin is different than the old one.
func TestJetStreamConsumerUnpinPickDifferentRequest(t *testing.T) {
s := RunBasicJetStreamServer(t)
defer s.Shutdown()
nc, _ := jsClientConnect(t, s)
defer nc.Close()
acc := s.GlobalAccount()
mset, err := acc.addStream(&StreamConfig{
Name: "TEST",
Subjects: []string{"foo"},
Retention: LimitsPolicy,
})
require_NoError(t, err)
_, err = mset.addConsumer(&ConsumerConfig{
Durable: "C",
FilterSubject: "foo",
PriorityGroups: []string{"A"},
PriorityPolicy: PriorityPinnedClient,
AckPolicy: AckExplicit,
PinnedTTL: 30 * time.Second,
})
require_NoError(t, err)
sendStreamMsg(t, nc, "foo", "data")
req := JSApiConsumerGetNextRequest{Batch: 5, Expires: 15 * time.Second, PriorityGroup: PriorityGroup{
Group: "A",
}}
reqBytes, err := json.Marshal(req)
require_NoError(t, err)
firstInbox := "FIRST"
firstReplies, err := nc.SubscribeSync(firstInbox)
require_NoError(t, err)
nc.PublishRequest("$JS.API.CONSUMER.MSG.NEXT.TEST.C", firstInbox, reqBytes)
msg, err := firstReplies.NextMsg(1 * time.Second)
require_NoError(t, err)
pinId := msg.Header.Get("Nats-Pin-Id")
require_NotEqual(t, pinId, "")
reqPinned := JSApiConsumerGetNextRequest{Batch: 5, Expires: 15 * time.Second, PriorityGroup: PriorityGroup{
Group: "A",
Id: pinId,
}}
_, err = json.Marshal(reqPinned)
require_NoError(t, err)
secondInbox := "SECOND"
secondReplies, err := nc.SubscribeSync(secondInbox)
require_NoError(t, err)
nc.PublishRequest("$JS.API.CONSUMER.MSG.NEXT.TEST.C", secondInbox, reqBytes)
_, err = secondReplies.NextMsg(1 * time.Second)
require_Error(t, err)
unpinRequest := func(t *testing.T, nc *nats.Conn, stream, consumer, group string) *ApiError {
var response JSApiConsumerUnpinResponse
request := JSApiConsumerUnpinRequest{Group: group}
requestData, err := json.Marshal(request)
require_NoError(t, err)
msg, err := nc.Request(fmt.Sprintf("$JS.API.CONSUMER.UNPIN.%s.%s", stream, consumer), requestData, time.Second*1)
require_NoError(t, err)
err = json.Unmarshal(msg.Data, &response)
require_NoError(t, err)
return response.Error
}
unpinRequest(t, nc, "TEST", "C", "A")
_, err = firstReplies.NextMsg(1 * time.Second)
// If there are no messages in the stream, do not expect unpin message to arrive.
// Advisory will be sent immediately, but messages with headers - only when there is anything to be sent.
require_Error(t, err)
// Send a new message to the stream.
sendStreamMsg(t, nc, "foo", "data")
// Check if the old pinned will get the information about bad pin.
msg, err = firstReplies.NextMsg(1 * time.Second)
require_NoError(t, err)
require_Equal(t, msg.Header.Get("Status"), "423")
// Make sure that the old pin is cleared.
require_Equal(t, msg.Header.Get("Nats-Pin-Id"), "")
// Try different wr.
msg, err = secondReplies.NextMsg(1 * time.Second)
require_NoError(t, err)
// Make sure that its pin is different than the old one and not empty.
require_NotEqual(t, msg.Header.Get("Nats-Pin-Id"), pinId)
require_NotEqual(t, msg.Header.Get("Nats-Pin-Id"), "")
}
func TestJetStreamConsumerPinnedTTL(t *testing.T) {
s := RunBasicJetStreamServer(t)
defer s.Shutdown()
nc, _ := jsClientConnect(t, s)
defer nc.Close()
acc := s.GlobalAccount()
mset, err := acc.addStream(&StreamConfig{
Name: "TEST",
Subjects: []string{"foo"},
Retention: LimitsPolicy,
})
require_NoError(t, err)
_, err = mset.addConsumer(&ConsumerConfig{
Durable: "C",
FilterSubject: "foo",
PriorityGroups: []string{"A"},
PriorityPolicy: PriorityPinnedClient,
AckPolicy: AckExplicit,
PinnedTTL: 3 * time.Second,
})
require_NoError(t, err)
for i := 0; i < 10; i++ {
sendStreamMsg(t, nc, "foo", "data")
}
req := JSApiConsumerGetNextRequest{Batch: 1, Expires: 10 * time.Second, PriorityGroup: PriorityGroup{
Group: "A",
}}
reqBytes, err := json.Marshal(req)
require_NoError(t, err)
firstInbox := "FIRST"
firstReplies, err := nc.SubscribeSync(firstInbox)
require_NoError(t, err)
nc.PublishRequest("$JS.API.CONSUMER.MSG.NEXT.TEST.C", firstInbox, reqBytes)
msg, err := firstReplies.NextMsg(1 * time.Second)
require_NoError(t, err)
pinId := msg.Header.Get("Nats-Pin-Id")
require_NotEqual(t, pinId, "")
secondInbox := "SECOND"
secondReplies, err := nc.SubscribeSync(secondInbox)
require_NoError(t, err)
nc.PublishRequest("$JS.API.CONSUMER.MSG.NEXT.TEST.C", secondInbox, reqBytes)
// Expect error, as first request should be still pinned.
_, err = secondReplies.NextMsg(1 * time.Second)
require_Error(t, err)
// During the 5 second window, the first Pin should time out and this request
// should become the pinned one and get the message.
msg, err = secondReplies.NextMsg(5 * time.Second)
require_NoError(t, err)
newPinId := msg.Header.Get("Nats-Pin-Id")
require_NotEqual(t, newPinId, pinId)
require_NotEqual(t, newPinId, "")
thirdInbox := "THIRD"
thirdReplies, err := nc.SubscribeSync(thirdInbox)
require_NoError(t, err)
nc.PublishRequest("$JS.API.CONSUMER.MSG.NEXT.TEST.C", thirdInbox, reqBytes)
// The same process as above, but tests different codepath - one where Pin
// is set on existing waiting request.
msg, err = thirdReplies.NextMsg(5 * time.Second)
require_NoError(t, err)
require_NotEqual(t, msg.Header.Get("Nats-Pin-Id"), pinId)
require_NotEqual(t, msg.Header.Get("Nats-Pin-Id"), newPinId)
require_NotEqual(t, newPinId, "")
}
func TestJetStreamConsumerUnpin(t *testing.T) {
single := RunBasicJetStreamServer(t)
defer single.Shutdown()
nc, js := jsClientConnect(t, single)
defer nc.Close()
cluster := createJetStreamClusterExplicit(t, "R3S", 3)
defer cluster.shutdown()
cnc, cjs := jsClientConnect(t, cluster.randomServer())
defer cnc.Close()
// Create a stream and consumer for both single server and clustered mode.
for _, server := range []struct {
replicas int
js nats.JetStreamContext
nc *nats.Conn
}{
{1, js, nc},
{3, cjs, cnc},
} {
_, err := server.js.AddStream(&nats.StreamConfig{
Name: "TEST",
Subjects: []string{"foo.>", "bar", "baz"},
Replicas: server.replicas,
})
require_NoError(t, err)
consumerConfig := CreateConsumerRequest{
Stream: "TEST",
Action: ActionCreate,
Config: ConsumerConfig{
Durable: "C",
FilterSubject: "foo.>",
PriorityGroups: []string{"A"},
PriorityPolicy: PriorityPinnedClient,
AckPolicy: AckExplicit,
PinnedTTL: 10 * time.Second,
},
}
req, err := json.Marshal(consumerConfig)
require_NoError(t, err)
rmsg, err := server.nc.Request(fmt.Sprintf(JSApiDurableCreateT, consumerConfig.Stream, consumerConfig.Config.Durable), req, 5*time.Second)
require_NoError(t, err)
var resp JSApiConsumerCreateResponse
err = json.Unmarshal(rmsg.Data, &resp)
require_NoError(t, err)
require_True(t, resp.Error == nil)
}
cluster.waitOnStreamLeader("$G", "TEST")
cluster.waitOnConsumerLeader("$G", "TEST", "C")
unpinRequest := func(t *testing.T, nc *nats.Conn, stream, consumer, group string) *ApiError {
var response JSApiConsumerUnpinResponse
request := JSApiConsumerUnpinRequest{Group: group}
requestData, err := json.Marshal(request)
require_NoError(t, err)
msg, err := nc.Request(fmt.Sprintf("$JS.API.CONSUMER.UNPIN.%s.%s", stream, consumer), requestData, time.Second*1)
require_NoError(t, err)
err = json.Unmarshal(msg.Data, &response)
require_NoError(t, err)
return response.Error
}
for _, test := range []struct {
name string
nc *nats.Conn
stream string
consumer string
group string
err *ApiError
}{
{"unpin non-existing group", nc, "TEST", "C", "B", &ApiError{ErrCode: uint16(JSConsumerInvalidPriorityGroupErr)}},
{"unpin on missing stream", nc, "NOT_EXIST", "C", "A", &ApiError{ErrCode: uint16(JSStreamNotFoundErr)}},
{"unpin on missing consumer", nc, "TEST", "NOT_EXIST", "A", &ApiError{ErrCode: uint16(JSConsumerNotFoundErr)}},
{"unpin missing group", nc, "TEST", "C", "", &ApiError{ErrCode: uint16(JSInvalidJSONErr)}},
{"unpin bad group name", nc, "TEST", "C", "group name\r\n", &ApiError{ErrCode: uint16(JSConsumerInvalidGroupNameErr)}},
{"ok unpin", nc, "TEST", "C", "A", nil},
} {
t.Run(test.name, func(t *testing.T) {
err := unpinRequest(t, nc, test.stream, test.consumer, test.group)
if test.err != nil {
require_True(t, err.ErrCode == test.err.ErrCode)
} else {
require_True(t, err == nil)
}
})
t.Run(fmt.Sprintf("%s clustered", test.name), func(t *testing.T) {
err := unpinRequest(t, cnc, test.stream, test.consumer, test.group)
if test.err != nil {
require_True(t, err.ErrCode == test.err.ErrCode)
} else {
require_True(t, err == nil)
}
})
}
}
func TestJetStreamConsumerWithPriorityGroups(t *testing.T) {
single := RunBasicJetStreamServer(t)
defer single.Shutdown()
nc, js := jsClientConnect(t, single)
defer nc.Close()
cluster := createJetStreamClusterExplicit(t, "R3S", 3)
defer cluster.shutdown()
cnc, cjs := jsClientConnect(t, cluster.randomServer())
defer cnc.Close()
// Create a stream and consumer for both single server and clustered mode.
for _, server := range []struct {
replicas int
js nats.JetStreamContext
}{
{1, js},
{3, cjs},
} {
_, err := server.js.AddStream(&nats.StreamConfig{
Name: "TEST",
Subjects: []string{"foo.>", "bar", "baz"},
Replicas: server.replicas,
})
require_NoError(t, err)
}
cluster.waitOnStreamLeader("$G", "TEST")
for _, test := range []struct {
name string
nc *nats.Conn
stream string
consumer string
groups []string
mode PriorityPolicy
deliverSubject string
err *ApiError
}{
{"Pinned Consumer with Priority Group", nc, "TEST", "PINNED", []string{"A"}, PriorityPinnedClient, "", nil},
{"Pinned Consumer with Priority Group, clustered", cnc, "TEST", "PINNED", []string{"A"}, PriorityPinnedClient, "", nil},
{"Overflow Consumer with Priority Group", nc, "TEST", "OVERFLOW", []string{"A"}, PriorityOverflow, "", nil},
{"Overflow Consumer with Priority Group, clustered", cnc, "TEST", "OVERFLOW", []string{"A"}, PriorityOverflow, "", nil},
{"Pinned Consumer without Priority Group", nc, "TEST", "PINNED_NO_GROUP", nil, PriorityPinnedClient, "", &ApiError{ErrCode: uint16(JSConsumerPriorityPolicyWithoutGroup)}},
{"Pinned Consumer without Priority Group, clustered", cnc, "TEST", "PINNED_NO_GROUP", nil, PriorityPinnedClient, "", &ApiError{ErrCode: uint16(JSConsumerPriorityPolicyWithoutGroup)}},
{"Overflow Consumer without Priority Group", nc, "TEST", "PINNED_NO_GROUP", nil, PriorityOverflow, "", &ApiError{ErrCode: uint16(JSConsumerPriorityPolicyWithoutGroup)}},
{"Overflow Consumer without Priority Group, clustered", cnc, "TEST", "PINNED_NO_GROUP", nil, PriorityOverflow, "", &ApiError{ErrCode: uint16(JSConsumerPriorityPolicyWithoutGroup)}},
{"Pinned Consumer with empty Priority Group", nc, "TEST", "PINNED_NO_GROUP", []string{""}, PriorityPinnedClient, "", &ApiError{ErrCode: uint16(JSConsumerEmptyGroupName)}},
{"Pinned Consumer with empty Priority Group, clustered", cnc, "TEST", "PINNED_NO_GROUP", []string{""}, PriorityPinnedClient, "", &ApiError{ErrCode: uint16(JSConsumerEmptyGroupName)}},
{"Pinned Consumer with empty Priority Group", nc, "TEST", "PINNED_NO_GROUP", []string{""}, PriorityOverflow, "", &ApiError{ErrCode: uint16(JSConsumerEmptyGroupName)}},
{"Pinned Consumer with empty Priority Group, clustered", cnc, "TEST", "PINNED_NO_GROUP", []string{""}, PriorityOverflow, "", &ApiError{ErrCode: uint16(JSConsumerEmptyGroupName)}},
{"Consumer with `none` policy priority and no pinned TTL set", nc, "TEST", "NONE", []string{}, PriorityNone, "", &ApiError{ErrCode: uint16(JSConsumerPinnedTTLWithoutPriorityPolicyNone)}},
{"Consumer with `none` policy priority and Priority Group set", nc, "TEST", "NONE_WITH_GROUPS", []string{"A"}, PriorityNone, "", &ApiError{ErrCode: uint16(JSConsumerPriorityGroupWithPolicyNone)}},
{"Push consumer with Priority Group", nc, "TEST", "PUSH_WITH_POLICY", []string{"A"}, PriorityOverflow, "subject", &ApiError{ErrCode: uint16(JSConsumerPushWithPriorityGroupErr)}},
} {
t.Run(test.name, func(t *testing.T) {
consumerConfig := CreateConsumerRequest{
Stream: "TEST",
Action: ActionCreate,
Config: ConsumerConfig{
Durable: test.consumer,
FilterSubject: "foo.>",
PriorityGroups: test.groups,
PriorityPolicy: test.mode,
AckPolicy: AckExplicit,
DeliverSubject: test.deliverSubject,
PinnedTTL: 10 * time.Second,
},
}
req, err := json.Marshal(consumerConfig)
require_NoError(t, err)
rmsg, err := test.nc.Request(fmt.Sprintf(JSApiDurableCreateT, consumerConfig.Stream, consumerConfig.Config.Durable), req, 5*time.Second)
require_NoError(t, err)
var resp JSApiConsumerCreateResponse
err = json.Unmarshal(rmsg.Data, &resp)
require_NoError(t, err)
if test.err != nil {
require_True(t, resp.Error.ErrCode == test.err.ErrCode)
} else {
require_True(t, resp.Error == nil)
}
})
}
}
func TestJetStreamConsumerPriorityPullRequests(t *testing.T) {
single := RunBasicJetStreamServer(t)
defer single.Shutdown()
nc, js := jsClientConnect(t, single)
defer nc.Close()
cluster := createJetStreamClusterExplicit(t, "R3S", 3)
defer cluster.shutdown()
_, err := js.AddStream(&nats.StreamConfig{
Name: "TEST",
Subjects: []string{"pinned.>", "overflow.>"},
})
require_NoError(t, err)
_, err = js.AddConsumer("TEST", &nats.ConsumerConfig{
Durable: "STANDARD",
})
require_NoError(t, err)
consumerConfig := CreateConsumerRequest{
Stream: "TEST",
Action: ActionCreate,
Config: ConsumerConfig{
Durable: "PINNED",
FilterSubject: "pinned.>",
PriorityGroups: []string{"A"},
PriorityPolicy: PriorityPinnedClient,
AckPolicy: AckExplicit,
PinnedTTL: 10 * time.Second,
},
}
req, err := json.Marshal(consumerConfig)
require_NoError(t, err)
rmsg, err := nc.Request(fmt.Sprintf(JSApiDurableCreateT, consumerConfig.Stream, consumerConfig.Config.Durable), req, 5*time.Second)
require_NoError(t, err)
var resp JSApiConsumerCreateResponse
err = json.Unmarshal(rmsg.Data, &resp)
require_NoError(t, err)
require_True(t, resp.Error == nil)
consumerConfig = CreateConsumerRequest{
Stream: "TEST",
Action: ActionCreate,
Config: ConsumerConfig{
Durable: "OVERFLOW",
FilterSubject: "overflow.>",
PriorityGroups: []string{"A"},
PriorityPolicy: PriorityOverflow,
AckPolicy: AckExplicit,
PinnedTTL: 5 * time.Second,
},
}
req, err = json.Marshal(consumerConfig)
require_NoError(t, err)
rmsg, err = nc.Request(fmt.Sprintf(JSApiDurableCreateT, consumerConfig.Stream, consumerConfig.Config.Durable), req, 5*time.Second)
require_NoError(t, err)
err = json.Unmarshal(rmsg.Data, &resp)
require_NoError(t, err)
require_True(t, resp.Error == nil)
for i := 0; i < 50; i++ {
sendStreamMsg(t, nc, "pinned.1", fmt.Sprintf("msg-%d", i))
sendStreamMsg(t, nc, "overflow.1", fmt.Sprintf("msg-%d", i))
}
for _, test := range []struct {
name string
nc *nats.Conn
consumer string
request JSApiConsumerGetNextRequest
description string
}{
{"Pinned Pull Request", nc, "PINNED", JSApiConsumerGetNextRequest{Batch: 1, Expires: 5 * time.Second, PriorityGroup: PriorityGroup{Group: "A"}}, ""},
{"Pinned Pull Request, no group", nc, "PINNED", JSApiConsumerGetNextRequest{Batch: 1, Expires: 5 * time.Second, PriorityGroup: PriorityGroup{}}, "Bad Request - Priority Group missing"},
{"Pinned Pull Request, bad group", nc, "PINNED", JSApiConsumerGetNextRequest{Batch: 1, Expires: 5 * time.Second, PriorityGroup: PriorityGroup{Group: "Bad"}}, "Bad Request - Invalid Priority Group"},
{"Pinned Pull Request, against Overflow", nc, "OVERFLOW", JSApiConsumerGetNextRequest{Batch: 1, Expires: 5 * time.Second, PriorityGroup: PriorityGroup{Group: "A", Id: "PINNED-ID"}}, "Bad Request - Not a Pinned Client Priority consumer"},
{"Pinned Pull Request, against standard consumer", nc, "STANDARD", JSApiConsumerGetNextRequest{Batch: 1, Expires: 5 * time.Second, PriorityGroup: PriorityGroup{Group: "A", Id: "PINNED-ID"}}, "Bad Request - Not a Pinned Client Priority consumer"},
{"Overflow Pull Request, overflow below threshold", nc, "OVERFLOW", JSApiConsumerGetNextRequest{Batch: 1, Expires: 5 * time.Second, PriorityGroup: PriorityGroup{Group: "A", MinPending: 1000}}, "Request Timeout"},
{"Overflow Pull Request, overflow above threshold", nc, "OVERFLOW", JSApiConsumerGetNextRequest{Batch: 1, Expires: 5 * time.Second, PriorityGroup: PriorityGroup{Group: "A", MinPending: 10}}, ""},
{"Overflow Pull Request, minPending OR minAckPending above threshold", nc, "OVERFLOW", JSApiConsumerGetNextRequest{Batch: 1, Expires: 5 * time.Second, PriorityGroup: PriorityGroup{Group: "A", MinPending: 10, MinAckPending: 5}}, ""},
{"Overflow Pull Request, against pinned", nc, "PINNED", JSApiConsumerGetNextRequest{Batch: 1, Expires: 5 * time.Second, PriorityGroup: PriorityGroup{Group: "A", MinPending: 10}}, "Bad Request - Not a Overflow Priority consumer"},
{"Overflow Pull Request, against standard consumer", nc, "STANDARD", JSApiConsumerGetNextRequest{Batch: 1, Expires: 5 * time.Second, PriorityGroup: PriorityGroup{Group: "A", MinPending: 10}}, "Bad Request - Not a Overflow Priority consumer"},
} {
t.Run(test.name, func(t *testing.T) {
inbox := nats.NewInbox()
replies, err := test.nc.SubscribeSync(inbox)
reqb, _ := json.Marshal(test.request)
require_NoError(t, err)
nc.PublishRequest(fmt.Sprintf("$JS.API.CONSUMER.MSG.NEXT.TEST.%s", test.consumer), inbox, reqb)
require_NoError(t, err)
msg, err := replies.NextMsg(10 * time.Second)
require_NoError(t, err)
require_Equal(t, test.description, msg.Header.Get("Description"))
})
}
}
func TestJetStreamConsumerOverflow(t *testing.T) {
s := RunBasicJetStreamServer(t)
defer s.Shutdown()
nc, _ := jsClientConnect(t, s)
defer nc.Close()
acc := s.GlobalAccount()
mset, err := acc.addStream(&StreamConfig{
Name: "TEST",
Subjects: []string{"foo.>", "bar", "baz"},
Retention: LimitsPolicy,
Storage: FileStorage,
})
require_NoError(t, err)
_, err = mset.addConsumer(&ConsumerConfig{
Durable: "C",
FilterSubject: "foo.>",
PriorityGroups: []string{"A"},
PriorityPolicy: PriorityOverflow,
AckPolicy: AckExplicit,
})
require_NoError(t, err)
sendStreamMsg(t, nc, "foo.1", "msg-1")
// nothing unacked, so should return nothing.
req := JSApiConsumerGetNextRequest{Batch: 1, Expires: 90 * time.Second, PriorityGroup: PriorityGroup{
MinAckPending: 1,
Group: "A",
}}
ackPending1 := sendRequest(t, nc, "ackPending", req)
_, err = ackPending1.NextMsg(time.Second)
require_Error(t, err)
// one pending message, so should return it.
req = JSApiConsumerGetNextRequest{Batch: 1, Expires: 90 * time.Second, PriorityGroup: PriorityGroup{
MinPending: 1,
Group: "A",
}}
numPending1 := sendRequest(t, nc, "singleOverflow", req)
msg, err := numPending1.NextMsg(time.Second)
require_NoError(t, err)
require_NotNil(t, msg)
sendStreamMsg(t, nc, "foo.1", "msg-2")
sendStreamMsg(t, nc, "foo.1", "msg-3")
// overflow set to 10, so we should not get any messages, as there are only few pending.
req = JSApiConsumerGetNextRequest{Batch: 1, Expires: 90 * time.Second, PriorityGroup: PriorityGroup{
MinPending: 10,
Group: "A",
}}
numPending10 := sendRequest(t, nc, "overflow", req)
_, err = numPending10.NextMsg(time.Second)
require_Error(t, err)
// without overflow, we should get messages.
req = JSApiConsumerGetNextRequest{Batch: 1, Expires: 90 * time.Second}
fetchNoOverflow := sendRequest(t, nc, "without_overflow", req)
noOverflowMsg, err := fetchNoOverflow.NextMsg(time.Second)
require_NoError(t, err)
require_NotNil(t, noOverflowMsg)
// Now add more messages.
for i := 0; i < 100; i++ {
sendStreamMsg(t, nc, "foo.1", "msg-1")
}
// and previous batch should receive messages now.
msg, err = numPending10.NextMsg(time.Second * 5)
require_NoError(t, err)
require_NotNil(t, msg)
// But one with max ack pending should get nothing.
req = JSApiConsumerGetNextRequest{Batch: 1, Expires: 90 * time.Second, PriorityGroup: PriorityGroup{
MinAckPending: 50,
Group: "A",
}}
maxAckPending50 := sendRequest(t, nc, "maxAckPending", req)
_, err = maxAckPending50.NextMsg(time.Second)
require_Error(t, err)
// However, when we miss a lot of acks, we should get messages on overflow with max ack pending.
req = JSApiConsumerGetNextRequest{Batch: 200, Expires: 90 * time.Second, PriorityGroup: PriorityGroup{
Group: "A",
}}
fetchNoOverflow = sendRequest(t, nc, "without_overflow", req)
noOverflowMsg, err = fetchNoOverflow.NextMsg(time.Second)
require_NoError(t, err)
require_NotNil(t, noOverflowMsg)
msg, err = maxAckPending50.NextMsg(time.Second)
require_NoError(t, err)
require_NotNil(t, msg)
}
func TestJetStreamConsumerMultipleFitersWithStartDate(t *testing.T) {
s := RunBasicJetStreamServer(t)
defer s.Shutdown()
nc, js := jsClientConnect(t, s)
defer nc.Close()
past := time.Now().Add(-90 * time.Second)
_, err := js.AddStream(&nats.StreamConfig{
Name: "TEST",
Subjects: []string{"events.>"},
})
require_NoError(t, err)
sendStreamMsg(t, nc, "events.foo", "msg-1")
sendStreamMsg(t, nc, "events.bar", "msg-2")
sendStreamMsg(t, nc, "events.baz", "msg-3")
sendStreamMsg(t, nc, "events.biz", "msg-4")
sendStreamMsg(t, nc, "events.faz", "msg-5")
sendStreamMsg(t, nc, "events.foo", "msg-6")
sendStreamMsg(t, nc, "events.biz", "msg-7")
for _, test := range []struct {
name string
filterSubjects []string
startTime time.Time
expectedMessages uint64
expectedStreamSequence uint64
}{
{"Single-Filter-first-sequence", []string{"events.foo"}, past, 2, 0},
{"Multiple-Filter-first-sequence", []string{"events.foo", "events.bar", "events.baz"}, past, 4, 0},
{"Multiple-Filters-second-subject", []string{"events.bar", "events.baz"}, past, 2, 1},
{"Multiple-Filters-first-last-subject", []string{"events.foo", "events.biz"}, past, 4, 0},
{"Multiple-Filters-in-future", []string{"events.foo", "events.biz"}, time.Now().Add(1 * time.Minute), 0, 7},
} {
t.Run(test.name, func(t *testing.T) {
info, err := js.AddConsumer("TEST", &nats.ConsumerConfig{
Durable: test.name,
FilterSubjects: test.filterSubjects,
DeliverPolicy: nats.DeliverByStartTimePolicy,
OptStartTime: &test.startTime,
})
require_NoError(t, err)
require_Equal(t, test.expectedStreamSequence, info.Delivered.Stream)
require_Equal(t, test.expectedMessages, info.NumPending)
})
}
}
func TestPriorityGroupNameRegex(t *testing.T) {
for _, test := range []struct {
name string
group string
valid bool
}{
{"valid-short", "A", true},
{"valid-with-accepted-special-chars", "group/consumer=A", true},
{"empty", "", false},
{"with-space", "A B", false},
{"with-tab", "A B", false},
{"too-long-name", "group-name-that-is-too-long", false},
{"line-termination", "\r\n", false},
} {
t.Run(test.name, func(t *testing.T) {
require_Equal(t, test.valid, validGroupName.MatchString(test.group))
})
}
}
func sendRequest(t *testing.T, nc *nats.Conn, reply string, req JSApiConsumerGetNextRequest) *nats.Subscription {
reqb, _ := json.Marshal(req)
replies, err := nc.SubscribeSync(reply)
nc.PublishRequest("$JS.API.CONSUMER.MSG.NEXT.TEST.C", reply, reqb)
require_NoError(t, err)
return replies
}
func Benchmark____JetStreamConsumerIsFilteredMatch(b *testing.B) {
subject := "foo.bar.do.not.match.any.filter.subject"
for n := 1; n <= 1024; n *= 2 {
name := fmt.Sprintf("%d filter subjects", int(n))
c := consumerWithFilterSubjects(filterSubjects(int(n)))
b.Run(name, func(b *testing.B) {
c.isFilteredMatch(subject)
})
}
}
// https://github.com/nats-io/nats-server/issues/6085
func TestJetStreamConsumerBackoffNotRespectedWithMultipleInflightRedeliveries(t *testing.T) {
s := RunBasicJetStreamServer(t)
defer s.Shutdown()
nc, js := jsClientConnect(t, s)
defer nc.Close()
_, err := js.AddStream(&nats.StreamConfig{
Name: "TEST",
Subjects: []string{"events.>"},
})
require_NoError(t, err)
maxDeliver := 3
backoff := []time.Duration{2 * time.Second, 4 * time.Second}
sub, err := js.SubscribeSync(
"events.>",
nats.MaxDeliver(maxDeliver),
nats.BackOff(backoff),
nats.AckExplicit(),
)
require_NoError(t, err)
calculateExpectedBackoff := func(numDelivered int) time.Duration {
expectedBackoff := 500 * time.Millisecond
for i := 0; i < numDelivered-1 && i < len(backoff); i++ {
expectedBackoff += backoff[i]
}
return expectedBackoff
}
// We get one message to be redelivered using the final backoff duration.
firstMsgSent := time.Now()
sendStreamMsg(t, nc, "events.first", "msg-1")
_, err = sub.NextMsg(time.Second)
require_NoError(t, err)
require_LessThan(t, time.Since(firstMsgSent), calculateExpectedBackoff(1))
_, err = sub.NextMsg(5 * time.Second)
require_NoError(t, err)
require_LessThan(t, time.Since(firstMsgSent), calculateExpectedBackoff(2))
// This message will be redelivered with the final/highest backoff below.
// If we now send a new message, the pending timer should be reset to the first backoff.
// Otherwise, if it remains at the final backoff duration we'll get this message redelivered too late.
sendStreamMsg(t, nc, "events.second", "msg-2")
for {
msg, err := sub.NextMsg(5 * time.Second)
require_NoError(t, err)
if msg.Subject == "events.first" {
require_LessThan(t, time.Since(firstMsgSent), calculateExpectedBackoff(3))
continue
}
// We expect the second message to be redelivered using the specified backoff strategy.
// Before, the first redelivery of the second message would be sent after the highest backoff duration.
metadata, err := msg.Metadata()
require_NoError(t, err)
numDelivered := int(metadata.NumDelivered)
expectedBackoff := calculateExpectedBackoff(numDelivered)
require_LessThan(t, time.Since(metadata.Timestamp), expectedBackoff)
// We've received all message, test passed.
if numDelivered >= maxDeliver {
break
}
}
}
func TestJetStreamConsumerBackoffWhenBackoffLengthIsEqualToMaxDeliverConfig(t *testing.T) {
s := RunBasicJetStreamServer(t)
defer s.Shutdown()
nc, js := jsClientConnect(t, s)
defer nc.Close()
_, err := js.AddStream(&nats.StreamConfig{
Name: "TEST",
Subjects: []string{"events.>"},
})
require_NoError(t, err)
maxDeliver := 3
backoff := []time.Duration{time.Second, 2 * time.Second, 3 * time.Second}
sub, err := js.SubscribeSync(
"events.>",
nats.MaxDeliver(maxDeliver),
nats.BackOff(backoff),
nats.AckExplicit(),
)
require_NoError(t, err)
calculateExpectedBackoff := func(numDelivered int) time.Duration {
return backoff[numDelivered-1] + 50*time.Millisecond // 50ms of margin to system overhead
}
// message to be redelivered using backoff duration.
firstMsgSent := time.Now()
sendStreamMsg(t, nc, "events.first", "msg-1")
_, err = sub.NextMsg(time.Second)
require_NoError(t, err)
require_LessThan(t, time.Since(firstMsgSent), calculateExpectedBackoff(1))
_, err = sub.NextMsg(2 * time.Second)
require_NoError(t, err)
require_LessThan(t, time.Since(firstMsgSent), calculateExpectedBackoff(2))
_, err = sub.NextMsg(3 * time.Second)
require_NoError(t, err)
require_LessThan(t, time.Since(firstMsgSent), calculateExpectedBackoff(3))
}
func TestJetStreamConsumerRetryAckAfterTimeout(t *testing.T) {
for _, ack := range []struct {
title string
policy nats.SubOpt
}{
{title: "AckExplicit", policy: nats.AckExplicit()},
{title: "AckAll", policy: nats.AckAll()},
} {
t.Run(ack.title, func(t *testing.T) {
s := RunBasicJetStreamServer(t)
defer s.Shutdown()
nc, js := jsClientConnect(t, s)
defer nc.Close()
_, err := js.AddStream(&nats.StreamConfig{
Name: "TEST",
Subjects: []string{"foo"},
})
require_NoError(t, err)
_, err = js.Publish("foo", nil)
require_NoError(t, err)
sub, err := js.PullSubscribe("foo", "CONSUMER", ack.policy)
require_NoError(t, err)
msgs, err := sub.Fetch(1)
require_NoError(t, err)
require_Len(t, len(msgs), 1)
msg := msgs[0]
// Send core request so the client is unaware of the ack being sent.
_, err = nc.Request(msg.Reply, nil, time.Second)
require_NoError(t, err)
// It could be we have already acked this specific message, but we haven't received the success response.
// Retrying the ack should not time out and still signal success.
err = msg.AckSync()
require_NoError(t, err)
})
}
}
func TestJetStreamConsumerSwitchLeaderDuringInflightAck(t *testing.T) {
s := RunBasicJetStreamServer(t)
defer s.Shutdown()
nc, js := jsClientConnect(t, s)
defer nc.Close()
_, err := js.AddStream(&nats.StreamConfig{
Name: "TEST",
Subjects: []string{"foo"},
})
require_NoError(t, err)
for i := 0; i < 2_000; i++ {
_, err = js.Publish("foo", nil)
require_NoError(t, err)
}
sub, err := js.PullSubscribe(
"foo",
"CONSUMER",
nats.MaxAckPending(2_000),
nats.ManualAck(),
nats.AckExplicit(),
nats.AckWait(2*time.Second),
)
require_NoError(t, err)
acc, err := s.lookupAccount(globalAccountName)
require_NoError(t, err)
mset, err := acc.lookupStream("TEST")
require_NoError(t, err)
o := mset.lookupConsumer("CONSUMER")
require_NotNil(t, o)
msgs, err := sub.Fetch(2_000)
require_NoError(t, err)
require_Len(t, len(msgs), 2_000)
// Simulate an ack being pushed, and o.setLeader(false) being called before the ack is processed and resets o.awl
atomic.AddInt64(&o.awl, 1)
o.setLeader(false)
o.setLeader(true)
msgs, err = sub.Fetch(1, nats.MaxWait(5*time.Second))
require_NoError(t, err)
require_Len(t, len(msgs), 1)
}
func TestJetStreamConsumerMessageDeletedDuringRedelivery(t *testing.T) {
storageTypes := []nats.StorageType{nats.MemoryStorage, nats.FileStorage}
for _, storageType := range storageTypes {
t.Run(storageType.String(), func(t *testing.T) {
s := RunBasicJetStreamServer(t)
defer s.Shutdown()
nc, js := jsClientConnect(t, s)
defer nc.Close()
_, err := js.AddStream(&nats.StreamConfig{
Name: "TEST",
Subjects: []string{"foo"},
Storage: storageType,
})
require_NoError(t, err)
for i := 0; i < 3; i++ {
_, err = js.Publish("foo", nil)
require_NoError(t, err)
}
sub, err := js.PullSubscribe(
"foo",
"CONSUMER",
nats.ManualAck(),
nats.AckExplicit(),
nats.AckWait(time.Second),
)
require_NoError(t, err)
acc, err := s.lookupAccount(globalAccountName)
require_NoError(t, err)
mset, err := acc.lookupStream("TEST")
require_NoError(t, err)
o := mset.lookupConsumer("CONSUMER")
require_NotNil(t, o)
msgs, err := sub.Fetch(3)
require_NoError(t, err)
require_Len(t, len(msgs), 3)
err = js.DeleteMsg("TEST", 2)
require_NoError(t, err)
// Wait for mset.storeUpdates to call into o.decStreamPending which runs
// the o.processTerm goroutine, removing one message from pending.
checkFor(t, 2*time.Second, 100*time.Millisecond, func() error {
o.mu.RLock()
defer o.mu.RUnlock()
if len(o.pending) != 2 {
return fmt.Errorf("expected 2 pending, but got %d", len(o.pending))
}
return nil
})
// Now empty the redelivery queue and reset the pending state.
o.mu.Lock()
for _, seq := range o.rdq {
o.removeFromRedeliverQueue(seq)
}
o.pending = make(map[uint64]*Pending)
o.pending[2] = &Pending{}
o.addToRedeliverQueue(2)
// Also reset delivery/ack floors to confirm they get corrected.
o.adflr, o.asflr = 0, 0
o.dseq, o.sseq = 11, 11
// Getting the next message should skip seq 2, as that's deleted, but must not touch state.
_, _, err = o.getNextMsg()
o.mu.Unlock()
require_Error(t, err, ErrStoreEOF)
require_Len(t, len(o.pending), 1)
// Simulate the o.processTerm goroutine running after a call to o.getNextMsg.
// Pending state and delivery/ack floors should be corrected.
o.processTerm(2, 2, 1, ackTermUnackedLimitsReason, _EMPTY_)
o.mu.RLock()
defer o.mu.RUnlock()
require_Len(t, len(o.pending), 0)
require_Equal(t, o.adflr, 10)
require_Equal(t, o.asflr, 10)
})
}
}
func TestJetStreamConsumerDeliveryCount(t *testing.T) {
s := RunBasicJetStreamServer(t)
defer s.Shutdown()
nc, js := jsClientConnect(t, s)
defer nc.Close()
_, err := js.AddStream(&nats.StreamConfig{
Name: "TEST",
Subjects: []string{"foo"},
})
require_NoError(t, err)
for i := 0; i < 2; i++ {
_, err = js.Publish("foo", nil)
require_NoError(t, err)
}
sub, err := js.PullSubscribe(
"foo",
"CONSUMER",
nats.ManualAck(),
nats.AckExplicit(),
nats.AckWait(time.Second),
nats.MaxDeliver(1),
)
require_NoError(t, err)
acc, err := s.lookupAccount(globalAccountName)
require_NoError(t, err)
mset, err := acc.lookupStream("TEST")
require_NoError(t, err)
o := mset.lookupConsumer("CONSUMER")
require_NotNil(t, o)
msgs, err := sub.Fetch(2)
require_NoError(t, err)
require_Len(t, len(msgs), 2)
require_NoError(t, msgs[1].Nak())
require_Equal(t, o.deliveryCount(1), 1)
require_Equal(t, o.deliveryCount(2), 1)
// max deliver 1 so this will fail
_, err = sub.Fetch(1, nats.MaxWait(250*time.Millisecond))
require_Error(t, err)
// This would previously report delivery count 0, because o.rdc!=nil
require_Equal(t, o.deliveryCount(1), 1)
require_Equal(t, o.deliveryCount(2), 1)
}
func TestJetStreamConsumerCreate(t *testing.T) {
cases := []struct {
name string
mconfig *StreamConfig
}{
{"MemoryStore", &StreamConfig{Name: "foo", Storage: MemoryStorage, Subjects: []string{"foo", "bar"}, Retention: WorkQueuePolicy}},
{"FileStore", &StreamConfig{Name: "foo", Storage: FileStorage, Subjects: []string{"foo", "bar"}, Retention: WorkQueuePolicy}},
}
for _, c := range cases {
t.Run(c.name, func(t *testing.T) {
s := RunBasicJetStreamServer(t)
defer s.Shutdown()
mset, err := s.GlobalAccount().addStream(c.mconfig)
if err != nil {
t.Fatalf("Unexpected error adding stream: %v", err)
}
defer mset.delete()
// Check for basic errors.
if _, err := mset.addConsumer(nil); err == nil {
t.Fatalf("Expected an error for no config")
}
// No deliver subject, meaning its in pull mode, work queue mode means it is required to
// do explicit ack.
if _, err := mset.addConsumer(&ConsumerConfig{}); err == nil {
t.Fatalf("Expected an error on work queue / pull mode without explicit ack mode")
}
// Check for delivery subject errors.
// Literal delivery subject required.
if _, err := mset.addConsumer(&ConsumerConfig{DeliverSubject: "foo.*"}); err == nil {
t.Fatalf("Expected an error on bad delivery subject")
}
// Check for cycles
if _, err := mset.addConsumer(&ConsumerConfig{DeliverSubject: "foo"}); err == nil {
t.Fatalf("Expected an error on delivery subject that forms a cycle")
}
if _, err := mset.addConsumer(&ConsumerConfig{DeliverSubject: "bar"}); err == nil {
t.Fatalf("Expected an error on delivery subject that forms a cycle")
}
if _, err := mset.addConsumer(&ConsumerConfig{DeliverSubject: "*"}); err == nil {
t.Fatalf("Expected an error on delivery subject that forms a cycle")
}
// StartPosition conflicts
now := time.Now().UTC()
if _, err := mset.addConsumer(&ConsumerConfig{
DeliverSubject: "A",
OptStartSeq: 1,
OptStartTime: &now,
}); err == nil {
t.Fatalf("Expected an error on start position conflicts")
}
if _, err := mset.addConsumer(&ConsumerConfig{
DeliverSubject: "A",
OptStartTime: &now,
}); err == nil {
t.Fatalf("Expected an error on start position conflicts")
}
// Non-Durables need to have subscription to delivery subject.
delivery := nats.NewInbox()
nc := clientConnectToServer(t, s)
defer nc.Close()
sub, _ := nc.SubscribeSync(delivery)
defer sub.Unsubscribe()
nc.Flush()
o, err := mset.addConsumer(&ConsumerConfig{DeliverSubject: delivery, AckPolicy: AckExplicit})
if err != nil {
t.Fatalf("Expected no error with registered interest, got %v", err)
}
if err := mset.deleteConsumer(o); err != nil {
t.Fatalf("Expected no error on delete, got %v", err)
}
// Now let's check that durables can be created and a duplicate call to add will be ok.
dcfg := &ConsumerConfig{
Durable: "ddd",
DeliverSubject: delivery,
AckPolicy: AckExplicit,
}
if _, err = mset.addConsumer(dcfg); err != nil {
t.Fatalf("Unexpected error creating consumer: %v", err)
}
if _, err = mset.addConsumer(dcfg); err != nil {
t.Fatalf("Unexpected error creating second identical consumer: %v", err)
}
// Not test that we can change the delivery subject if that is only thing that has not
// changed and we are not active.
sub.Unsubscribe()
sub, _ = nc.SubscribeSync("d.d.d")
nc.Flush()
defer sub.Unsubscribe()
dcfg.DeliverSubject = "d.d.d"
if _, err = mset.addConsumer(dcfg); err != nil {
t.Fatalf("Unexpected error creating third consumer with just deliver subject changed: %v", err)
}
})
}
}
func TestJetStreamConsumerAndStreamDescriptions(t *testing.T) {
s := RunBasicJetStreamServer(t)
defer s.Shutdown()
descr := "foo asset"
acc := s.GlobalAccount()
// Check stream's first.
mset, err := acc.addStream(&StreamConfig{Name: "foo", Description: descr})
if err != nil {
t.Fatalf("Unexpected error adding stream: %v", err)
}
if cfg := mset.config(); cfg.Description != descr {
t.Fatalf("Expected a description of %q, got %q", descr, cfg.Description)
}
// Now consumer
edescr := "analytics"
o, err := mset.addConsumer(&ConsumerConfig{
Description: edescr,
DeliverSubject: "to",
AckPolicy: AckNone})
if err != nil {
t.Fatalf("Unexpected error adding consumer: %v", err)
}
if cfg := o.config(); cfg.Description != edescr {
t.Fatalf("Expected a description of %q, got %q", edescr, cfg.Description)
}
// Test max.
data := make([]byte, JSMaxDescriptionLen+1)
crand.Read(data)
bigDescr := base64.StdEncoding.EncodeToString(data)
_, err = acc.addStream(&StreamConfig{Name: "bar", Description: bigDescr})
if err == nil || !strings.Contains(err.Error(), "description is too long") {
t.Fatalf("Expected an error but got none")
}
_, err = mset.addConsumer(&ConsumerConfig{
Description: bigDescr,
DeliverSubject: "to",
AckPolicy: AckNone})
if err == nil || !strings.Contains(err.Error(), "description is too long") {
t.Fatalf("Expected an error but got none")
}
}
func TestJetStreamConsumerWithNameAndDurable(t *testing.T) {
s := RunBasicJetStreamServer(t)
defer s.Shutdown()
descr := "foo asset"
name := "name"
durable := "durable"
acc := s.GlobalAccount()
// Check stream's first.
mset, err := acc.addStream(&StreamConfig{Name: "foo", Description: descr})
if err != nil {
t.Fatalf("Unexpected error adding stream: %v", err)
}
if cfg := mset.config(); cfg.Description != descr {
t.Fatalf("Expected a description of %q, got %q", descr, cfg.Description)
}
// it's ok to specify both durable and name, but they have to be the same.
_, err = mset.addConsumer(&ConsumerConfig{
DeliverSubject: "to",
Durable: "consumer",
Name: "consumer",
AckPolicy: AckNone})
if err != nil {
t.Fatalf("Unexpected error adding consumer: %v", err)
}
// if they're not the same, expect error
_, err = mset.addConsumer(&ConsumerConfig{
DeliverSubject: "to",
Durable: durable,
Name: name,
AckPolicy: AckNone})
if !strings.Contains(err.Error(), "Consumer Durable and Name have to be equal") {
t.Fatalf("Wrong error while adding consumer with not matching Name and Durable: %v", err)
}
}
func TestJetStreamConsumerWithStartTime(t *testing.T) {
subj := "my_stream"
cases := []struct {
name string
mconfig *StreamConfig
}{
{"MemoryStore", &StreamConfig{Name: subj, Storage: MemoryStorage}},
{"FileStore", &StreamConfig{Name: subj, Storage: FileStorage}},
}
for _, c := range cases {
t.Run(c.name, func(t *testing.T) {
s := RunBasicJetStreamServer(t)
defer s.Shutdown()
fsCfg := &FileStoreConfig{BlockSize: 100}
mset, err := s.GlobalAccount().addStreamWithStore(c.mconfig, fsCfg)
if err != nil {
t.Fatalf("Unexpected error adding stream: %v", err)
}
defer mset.delete()
nc := clientConnectToServer(t, s)
defer nc.Close()
toSend := 250
for i := 0; i < toSend; i++ {
sendStreamMsg(t, nc, subj, fmt.Sprintf("MSG: %d", i+1))
}
time.Sleep(10 * time.Millisecond)
startTime := time.Now().UTC()
for i := 0; i < toSend; i++ {
sendStreamMsg(t, nc, subj, fmt.Sprintf("MSG: %d", i+1))
}
if msgs := mset.state().Msgs; msgs != uint64(toSend*2) {
t.Fatalf("Expected %d messages, got %d", toSend*2, msgs)
}
o, err := mset.addConsumer(&ConsumerConfig{
Durable: "d",
DeliverPolicy: DeliverByStartTime,
OptStartTime: &startTime,
AckPolicy: AckExplicit,
})
require_NoError(t, err)
defer o.delete()
msg, err := nc.Request(o.requestNextMsgSubject(), nil, time.Second)
require_NoError(t, err)
sseq, dseq, _, _, _ := replyInfo(msg.Reply)
if dseq != 1 {
t.Fatalf("Expected delivered seq of 1, got %d", dseq)
}
if sseq != uint64(toSend+1) {
t.Fatalf("Expected to get store seq of %d, got %d", toSend+1, sseq)
}
})
}
}
// Test for https://github.com/nats-io/jetstream/issues/143
func TestJetStreamConsumerWithMultipleStartOptions(t *testing.T) {
subj := "my_stream"
cases := []struct {
name string
mconfig *StreamConfig
}{
{"MemoryStore", &StreamConfig{Name: subj, Subjects: []string{"foo.>"}, Storage: MemoryStorage}},
{"FileStore", &StreamConfig{Name: subj, Subjects: []string{"foo.>"}, Storage: FileStorage}},
}
for _, c := range cases {
t.Run(c.name, func(t *testing.T) {
s := RunBasicJetStreamServer(t)
defer s.Shutdown()
mset, err := s.GlobalAccount().addStream(c.mconfig)
if err != nil {
t.Fatalf("Unexpected error adding stream: %v", err)
}
defer mset.delete()
nc := clientConnectToServer(t, s)
defer nc.Close()
obsReq := CreateConsumerRequest{
Stream: subj,
Config: ConsumerConfig{
Durable: "d",
DeliverPolicy: DeliverLast,
FilterSubject: "foo.22",
AckPolicy: AckExplicit,
},
}
req, err := json.Marshal(obsReq)
require_NoError(t, err)
_, err = nc.Request(fmt.Sprintf(JSApiConsumerCreateT, subj), req, time.Second)
require_NoError(t, err)
nc.Close()
s.Shutdown()
})
}
}
func TestJetStreamConsumerMaxDeliveries(t *testing.T) {
cases := []struct {
name string
mconfig *StreamConfig
}{
{"MemoryStore", &StreamConfig{Name: "MY_WQ", Storage: MemoryStorage}},
{"FileStore", &StreamConfig{Name: "MY_WQ", Storage: FileStorage}},
}
for _, c := range cases {
t.Run(c.name, func(t *testing.T) {
s := RunBasicJetStreamServer(t)
defer s.Shutdown()
mset, err := s.GlobalAccount().addStream(c.mconfig)
if err != nil {
t.Fatalf("Unexpected error adding stream: %v", err)
}
defer mset.delete()
nc := clientConnectToServer(t, s)
defer nc.Close()
// Queue up our work item.
sendStreamMsg(t, nc, c.mconfig.Name, "Hello World!")
sub, _ := nc.SubscribeSync(nats.NewInbox())
defer sub.Unsubscribe()
nc.Flush()
maxDeliver := 5
ackWait := 10 * time.Millisecond
o, err := mset.addConsumer(&ConsumerConfig{
DeliverSubject: sub.Subject,
AckPolicy: AckExplicit,
AckWait: ackWait,
MaxDeliver: maxDeliver,
})
require_NoError(t, err)
defer o.delete()
// Wait for redeliveries to pile up.
checkFor(t, 250*time.Millisecond, 10*time.Millisecond, func() error {
if nmsgs, _, err := sub.Pending(); err != nil || nmsgs != maxDeliver {
return fmt.Errorf("Did not receive correct number of messages: %d vs %d", nmsgs, maxDeliver)
}
return nil
})
// Now wait a bit longer and make sure we do not have more than maxDeliveries.
time.Sleep(2 * ackWait)
if nmsgs, _, _ := sub.Pending(); err != nil || nmsgs != maxDeliver {
t.Fatalf("Did not receive correct number of messages: %d vs %d", nmsgs, maxDeliver)
}
})
}
}
func TestJetStreamConsumerSingleTokenSubject(t *testing.T) {
s := RunBasicJetStreamServer(t)
defer s.Shutdown()
nc, js := jsClientConnect(t, s)
defer nc.Close()
filterSubject := "foo"
_, err := js.AddStream(&nats.StreamConfig{
Name: "TEST",
Subjects: []string{filterSubject},
})
require_NoError(t, err)
req, err := json.Marshal(&CreateConsumerRequest{Stream: "TEST", Config: ConsumerConfig{
FilterSubject: filterSubject,
Name: "name",
}})
if err != nil {
t.Fatalf("failed to marshal consumer create request: %v", err)
}
resp, err := nc.Request(fmt.Sprintf("$JS.API.CONSUMER.CREATE.%s.%s.%s", "TEST", "name", "not_filter_subject"), req, time.Second*10)
var apiResp ApiResponse
json.Unmarshal(resp.Data, &apiResp)
if err != nil {
t.Fatalf("failed to unmarshal response: %v", err)
}
if apiResp.Error == nil {
t.Fatalf("expected error, got nil")
}
if apiResp.Error.ErrCode != 10131 {
t.Fatalf("expected error 10131, got %v", apiResp.Error)
}
}
func TestJetStreamConsumerPullDelayedFirstPullWithReplayOriginal(t *testing.T) {
cases := []struct {
name string
mconfig *StreamConfig
}{
{"MemoryStore", &StreamConfig{Name: "MY_WQ", Storage: MemoryStorage}},
{"FileStore", &StreamConfig{Name: "MY_WQ", Storage: FileStorage}},
}
for _, c := range cases {
t.Run(c.name, func(t *testing.T) {
s := RunBasicJetStreamServer(t)
defer s.Shutdown()
mset, err := s.GlobalAccount().addStream(c.mconfig)
if err != nil {
t.Fatalf("Unexpected error adding stream: %v", err)
}
defer mset.delete()
nc := clientConnectToServer(t, s)
defer nc.Close()
// Queue up our work item.
sendStreamMsg(t, nc, c.mconfig.Name, "Hello World!")
o, err := mset.addConsumer(&ConsumerConfig{
Durable: "d",
AckPolicy: AckExplicit,
ReplayPolicy: ReplayOriginal,
})
require_NoError(t, err)
defer o.delete()
// Force delay here which triggers the bug.
time.Sleep(250 * time.Millisecond)
if _, err = nc.Request(o.requestNextMsgSubject(), nil, time.Second); err != nil {
t.Fatalf("Unexpected error: %v", err)
}
})
}
}
func TestJetStreamConsumerAckFloorFill(t *testing.T) {
cases := []struct {
name string
mconfig *StreamConfig
}{
{"MemoryStore", &StreamConfig{Name: "MQ", Storage: MemoryStorage}},
{"FileStore", &StreamConfig{Name: "MQ", Storage: FileStorage}},
}
for _, c := range cases {
t.Run(c.name, func(t *testing.T) {
s := RunBasicJetStreamServer(t)
defer s.Shutdown()
mset, err := s.GlobalAccount().addStream(c.mconfig)
if err != nil {
t.Fatalf("Unexpected error adding stream: %v", err)
}
defer mset.delete()
nc := clientConnectToServer(t, s)
defer nc.Close()
for i := 1; i <= 4; i++ {
sendStreamMsg(t, nc, c.mconfig.Name, fmt.Sprintf("msg-%d", i))
}
sub, _ := nc.SubscribeSync(nats.NewInbox())
defer sub.Unsubscribe()
nc.Flush()
o, err := mset.addConsumer(&ConsumerConfig{
Durable: "d",
DeliverSubject: sub.Subject,
AckPolicy: AckExplicit,
})
require_NoError(t, err)
defer o.delete()
var first *nats.Msg
for i := 1; i <= 3; i++ {
m, err := sub.NextMsg(time.Second)
if err != nil {
t.Fatalf("Error receiving message %d: %v", i, err)
}
// Don't ack 1 or 4.
if i == 1 {
first = m
} else if i == 2 || i == 3 {
m.Respond(nil)
}
}
nc.Flush()
if info := o.info(); info.AckFloor.Consumer != 0 {
t.Fatalf("Expected the ack floor to be 0, got %d", info.AckFloor.Consumer)
}
// Now ack first, should move ack floor to 3.
first.Respond(nil)
nc.Flush()
checkFor(t, time.Second, 50*time.Millisecond, func() error {
if info := o.info(); info.AckFloor.Consumer != 3 {
return fmt.Errorf("Expected the ack floor to be 3, got %d", info.AckFloor.Consumer)
}
return nil
})
})
}
}
func TestJetStreamConsumerAckAck(t *testing.T) {
s := RunBasicJetStreamServer(t)
defer s.Shutdown()
mname := "ACK-ACK"
mset, err := s.GlobalAccount().addStream(&StreamConfig{Name: mname, Storage: MemoryStorage})
if err != nil {
t.Fatalf("Unexpected error adding stream: %v", err)
}
defer mset.delete()
o, err := mset.addConsumer(&ConsumerConfig{Durable: "worker", AckPolicy: AckExplicit})
if err != nil {
t.Fatalf("Expected no error with registered interest, got %v", err)
}
defer o.delete()
rqn := o.requestNextMsgSubject()
nc := clientConnectToServer(t, s)
defer nc.Close()
// 4 for number of ack protocols to test them all.
for i := 0; i < 4; i++ {
sendStreamMsg(t, nc, mname, "Hello World!")
}
testAck := func(ackType []byte) {
m, err := nc.Request(rqn, nil, 10*time.Millisecond)
if err != nil {
t.Fatalf("Unexpected error: %v", err)
}
// Send a request for the ack and make sure the server "ack's" the ack.
if _, err := nc.Request(m.Reply, ackType, 10*time.Millisecond); err != nil {
t.Fatalf("Unexpected error on ack/ack: %v", err)
}
}
testAck(AckAck)
testAck(AckNak)
testAck(AckProgress)
testAck(AckTerm)
}
func TestJetStreamConsumerRateLimit(t *testing.T) {
s := RunBasicJetStreamServer(t)
defer s.Shutdown()
mname := "RATELIMIT"
mset, err := s.GlobalAccount().addStream(&StreamConfig{Name: mname, Storage: FileStorage})
if err != nil {
t.Fatalf("Unexpected error adding stream: %v", err)
}
nc := clientConnectToServer(t, s)
defer nc.Close()
msgSize := 128 * 1024
msg := make([]byte, msgSize)
crand.Read(msg)
// 10MB
totalSize := 10 * 1024 * 1024
toSend := totalSize / msgSize
for i := 0; i < toSend; i++ {
nc.Publish(mname, msg)
}
nc.Flush()
checkFor(t, 5*time.Second, 100*time.Millisecond, func() error {
state := mset.state()
if state.Msgs != uint64(toSend) {
return fmt.Errorf("Expected %d messages, got %d", toSend, state.Msgs)
}
return nil
})
// 100Mbit
rateLimit := uint64(100 * 1024 * 1024)
// Make sure if you set a rate with a pull based consumer it errors.
_, err = mset.addConsumer(&ConsumerConfig{Durable: "to", AckPolicy: AckExplicit, RateLimit: rateLimit})
if err == nil {
t.Fatalf("Expected an error, got none")
}
// Now create one and measure the rate delivered.
o, err := mset.addConsumer(&ConsumerConfig{
Durable: "rate",
DeliverSubject: "to",
RateLimit: rateLimit,
AckPolicy: AckNone})
require_NoError(t, err)
defer o.delete()
var received int
done := make(chan bool)
start := time.Now()
nc.Subscribe("to", func(m *nats.Msg) {
received++
if received >= toSend {
done <- true
}
})
nc.Flush()
select {
case <-done:
case <-time.After(5 * time.Second):
t.Fatalf("Did not receive all the messages in time")
}
tt := time.Since(start)
rate := float64(8*toSend*msgSize) / tt.Seconds()
if rate > float64(rateLimit)*1.25 {
t.Fatalf("Exceeded desired rate of %d mbps, got %0.f mbps", rateLimit/(1024*1024), rate/(1024*1024))
}
}
func TestJetStreamConsumerEphemeralRecoveryAfterServerRestart(t *testing.T) {
s := RunBasicJetStreamServer(t)
defer s.Shutdown()
mname := "MYS"
mset, err := s.GlobalAccount().addStream(&StreamConfig{Name: mname, Storage: FileStorage})
if err != nil {
t.Fatalf("Unexpected error adding stream: %v", err)
}
defer mset.delete()
nc := clientConnectToServer(t, s)
defer nc.Close()
sub, _ := nc.SubscribeSync(nats.NewInbox())
defer sub.Unsubscribe()
nc.Flush()
o, err := mset.addConsumer(&ConsumerConfig{
DeliverSubject: sub.Subject,
AckPolicy: AckExplicit,
})
if err != nil {
t.Fatalf("Error creating consumer: %v", err)
}
defer o.delete()
// Snapshot our name.
oname := o.String()
// Send 100 messages
for i := 0; i < 100; i++ {
sendStreamMsg(t, nc, mname, "Hello World!")
}
if state := mset.state(); state.Msgs != 100 {
t.Fatalf("Expected %d messages, got %d", 100, state.Msgs)
}
// Read 6 messages
for i := 0; i <= 6; i++ {
if m, err := sub.NextMsg(time.Second); err == nil {
m.Respond(nil)
} else {
t.Fatalf("Unexpected error: %v", err)
}
}
// Capture port since it was dynamic.
u, _ := url.Parse(s.ClientURL())
port, _ := strconv.Atoi(u.Port())
restartServer := func() {
t.Helper()
// Stop current
sd := s.JetStreamConfig().StoreDir
s.Shutdown()
// Restart.
s = RunJetStreamServerOnPort(port, sd)
}
// Do twice
for i := 0; i < 2; i++ {
// Restart.
restartServer()
defer s.Shutdown()
mset, err = s.GlobalAccount().lookupStream(mname)
if err != nil {
t.Fatalf("Expected to find a stream for %q", mname)
}
o = mset.lookupConsumer(oname)
if o == nil {
t.Fatalf("Error looking up consumer %q", oname)
}
// Make sure config does not have durable.
if cfg := o.config(); cfg.Durable != _EMPTY_ {
t.Fatalf("Expected no durable to be set")
}
// Wait for it to become active
checkFor(t, 200*time.Millisecond, 10*time.Millisecond, func() error {
if !o.isActive() {
return fmt.Errorf("Consumer not active")
}
return nil
})
}
// Now close the connection. Make sure this acts like an ephemeral and goes away.
o.setInActiveDeleteThreshold(10 * time.Millisecond)
nc.Close()
checkFor(t, 200*time.Millisecond, 10*time.Millisecond, func() error {
if o := mset.lookupConsumer(oname); o != nil {
return fmt.Errorf("Consumer still active")
}
return nil
})
}
func TestJetStreamConsumerMaxDeliveryAndServerRestart(t *testing.T) {
s := RunBasicJetStreamServer(t)
defer s.Shutdown()
mname := "MYS"
mset, err := s.GlobalAccount().addStream(&StreamConfig{Name: mname, Storage: FileStorage})
if err != nil {
t.Fatalf("Unexpected error adding stream: %v", err)
}
defer mset.delete()
streamCreated := mset.createdTime()
dsubj := "D.TO"
max := 3
o, err := mset.addConsumer(&ConsumerConfig{
Durable: "TO",
DeliverSubject: dsubj,
AckPolicy: AckExplicit,
AckWait: 100 * time.Millisecond,
MaxDeliver: max,
})
defer o.delete()
consumerCreated := o.createdTime()
// For calculation of consumer created times below.
time.Sleep(5 * time.Millisecond)
nc := clientConnectToServer(t, s)
defer nc.Close()
sub, _ := nc.SubscribeSync(dsubj)
nc.Flush()
defer sub.Unsubscribe()
// Send one message.
sendStreamMsg(t, nc, mname, "order-1")
checkSubPending := func(numExpected int) {
t.Helper()
checkFor(t, time.Second, 10*time.Millisecond, func() error {
if nmsgs, _, _ := sub.Pending(); nmsgs != numExpected {
return fmt.Errorf("Did not receive correct number of messages: %d vs %d", nmsgs, numExpected)
}
return nil
})
}
checkNumMsgs := func(numExpected uint64) {
t.Helper()
mset, err = s.GlobalAccount().lookupStream(mname)
if err != nil {
t.Fatalf("Expected to find a stream for %q", mname)
}
state := mset.state()
if state.Msgs != numExpected {
t.Fatalf("Expected %d msgs, got %d", numExpected, state.Msgs)
}
}
// Wait til we know we have max queued up.
checkSubPending(max)
// Once here we have gone over the limit for the 1st message for max deliveries.
// Send second
sendStreamMsg(t, nc, mname, "order-2")
// Just wait for first delivery + one redelivery.
checkSubPending(max + 2)
// Capture port since it was dynamic.
u, _ := url.Parse(s.ClientURL())
port, _ := strconv.Atoi(u.Port())
restartServer := func() {
t.Helper()
sd := s.JetStreamConfig().StoreDir
// Stop current
s.Shutdown()
// Restart.
s = RunJetStreamServerOnPort(port, sd)
}
waitForClientReconnect := func() {
t.Helper()
require_NoError(t, nc.ForceReconnect())
checkFor(t, 2500*time.Millisecond, 5*time.Millisecond, func() error {
if !nc.IsConnected() {
return fmt.Errorf("Not connected")
}
return nil
})
}
// Restart.
restartServer()
defer s.Shutdown()
checkNumMsgs(2)
// Wait for client to be reconnected.
waitForClientReconnect()
// Once we are here send third order.
sendStreamMsg(t, nc, mname, "order-3")
checkNumMsgs(3)
// Restart.
restartServer()
defer s.Shutdown()
checkNumMsgs(3)
// Wait for client to be reconnected.
waitForClientReconnect()
// Now we should have max times three on our sub.
checkSubPending(max * 3)
// Now do some checks on created timestamps.
mset, err = s.GlobalAccount().lookupStream(mname)
if err != nil {
t.Fatalf("Expected to find a stream for %q", mname)
}
if mset.createdTime() != streamCreated {
t.Fatalf("Stream creation time not restored, wanted %v, got %v", streamCreated, mset.createdTime())
}
o = mset.lookupConsumer("TO")
if o == nil {
t.Fatalf("Error looking up consumer: %v", err)
}
// Consumer created times can have a very small skew.
delta := o.createdTime().Sub(consumerCreated)
if delta > 5*time.Millisecond {
t.Fatalf("Consumer creation time not restored, wanted %v, got %v", consumerCreated, o.createdTime())
}
}
func TestJetStreamConsumerDeleteAndServerRestart(t *testing.T) {
s := RunBasicJetStreamServer(t)
defer s.Shutdown()
sendSubj := "MYQ"
mset, err := s.GlobalAccount().addStream(&StreamConfig{Name: sendSubj, Storage: FileStorage})
if err != nil {
t.Fatalf("Unexpected error adding stream: %v", err)
}
defer mset.delete()
// Create basic work queue mode consumer.
oname := "WQ"
o, err := mset.addConsumer(workerModeConfig(oname))
if err != nil {
t.Fatalf("Expected no error with registered interest, got %v", err)
}
// Now delete and then we will restart the
o.delete()
if numo := mset.numConsumers(); numo != 0 {
t.Fatalf("Expected to have zero consumers, got %d", numo)
}
// Capture port since it was dynamic.
u, _ := url.Parse(s.ClientURL())
port, _ := strconv.Atoi(u.Port())
sd := s.JetStreamConfig().StoreDir
// Stop current
s.Shutdown()
// Restart.
s = RunJetStreamServerOnPort(port, sd)
defer s.Shutdown()
mset, err = s.GlobalAccount().lookupStream(sendSubj)
if err != nil {
t.Fatalf("Expected to find a stream for %q", sendSubj)
}
if numo := mset.numConsumers(); numo != 0 {
t.Fatalf("Expected to have zero consumers, got %d", numo)
}
}
func TestJetStreamConsumerDurableReconnectWithOnlyPending(t *testing.T) {
cases := []struct {
name string
mconfig *StreamConfig
}{
{"MemoryStore", &StreamConfig{Name: "DT", Storage: MemoryStorage, Subjects: []string{"foo.*"}}},
{"FileStore", &StreamConfig{Name: "DT", Storage: FileStorage, Subjects: []string{"foo.*"}}},
}
for _, c := range cases {
t.Run(c.name, func(t *testing.T) {
s := RunBasicJetStreamServer(t)
defer s.Shutdown()
mset, err := s.GlobalAccount().addStream(c.mconfig)
if err != nil {
t.Fatalf("Unexpected error adding stream: %v", err)
}
defer mset.delete()
nc := clientConnectToServer(t, s)
defer nc.Close()
dname := "d22"
subj1 := nats.NewInbox()
o, err := mset.addConsumer(&ConsumerConfig{
Durable: dname,
DeliverSubject: subj1,
AckPolicy: AckExplicit,
AckWait: 25 * time.Millisecond})
if err != nil {
t.Fatalf("Unexpected error: %v", err)
}
sendMsg := func(payload string) {
t.Helper()
if err := nc.Publish("foo.22", []byte(payload)); err != nil {
return
}
}
sendMsg("1")
sub, _ := nc.SubscribeSync(subj1)
defer sub.Unsubscribe()
checkFor(t, 500*time.Millisecond, 10*time.Millisecond, func() error {
if nmsgs, _, _ := sub.Pending(); err != nil || nmsgs != 1 {
return fmt.Errorf("Did not receive correct number of messages: %d vs %d", nmsgs, 1)
}
return nil
})
// Now unsubscribe and wait to become inactive
sub.Unsubscribe()
checkFor(t, 250*time.Millisecond, 50*time.Millisecond, func() error {
if o.isActive() {
return fmt.Errorf("Consumer is still active")
}
return nil
})
// Send the second message while delivery subscriber is not running
sendMsg("2")
// Now we should be able to replace the delivery subject.
subj2 := nats.NewInbox()
o, err = mset.addConsumer(&ConsumerConfig{
Durable: dname,
DeliverSubject: subj2,
AckPolicy: AckExplicit,
AckWait: 25 * time.Millisecond})
if err != nil {
t.Fatalf("Unexpected error trying to add a new durable consumer: %v", err)
}
sub, _ = nc.SubscribeSync(subj2)
defer sub.Unsubscribe()
nc.Flush()
// We should get msg "1" and "2" delivered. They will be reversed.
for i := 0; i < 2; i++ {
msg, err := sub.NextMsg(500 * time.Millisecond)
if err != nil {
t.Fatalf("Unexpected error: %v", err)
}
sseq, _, dc, _, _ := replyInfo(msg.Reply)
if sseq == 1 && dc == 1 {
t.Fatalf("Expected a redelivery count greater then 1 for sseq 1, got %d", dc)
}
if sseq != 1 && sseq != 2 {
t.Fatalf("Expected stream sequence of 1 or 2 but got %d", sseq)
}
}
})
}
}
func TestJetStreamConsumerDurableFilteredSubjectReconnect(t *testing.T) {
cases := []struct {
name string
mconfig *StreamConfig
}{
{"MemoryStore", &StreamConfig{Name: "DT", Storage: MemoryStorage, Subjects: []string{"foo.*"}}},
{"FileStore", &StreamConfig{Name: "DT", Storage: FileStorage, Subjects: []string{"foo.*"}}},
}
for _, c := range cases {
t.Run(c.name, func(t *testing.T) {
s := RunBasicJetStreamServer(t)
defer s.Shutdown()
mset, err := s.GlobalAccount().addStream(c.mconfig)
if err != nil {
t.Fatalf("Unexpected error adding stream: %v", err)
}
defer mset.delete()
nc, js := jsClientConnect(t, s)
defer nc.Close()
sendMsgs := func(toSend int) {
for i := 0; i < toSend; i++ {
var subj string
if i%2 == 0 {
subj = "foo.AA"
} else {
subj = "foo.ZZ"
}
_, err := js.Publish(subj, []byte("OK!"))
require_NoError(t, err)
}
}
// Send 50 msgs
toSend := 50
sendMsgs(toSend)
dname := "d33"
dsubj := nats.NewInbox()
// Now create an consumer for foo.AA, only requesting the last one.
_, err = mset.addConsumer(&ConsumerConfig{
Durable: dname,
DeliverSubject: dsubj,
FilterSubject: "foo.AA",
DeliverPolicy: DeliverLast,
AckPolicy: AckExplicit,
AckWait: 100 * time.Millisecond,
})
if err != nil {
t.Fatalf("Unexpected error: %v", err)
}
sub, _ := nc.SubscribeSync(dsubj)
defer sub.Unsubscribe()
// Used to calculate difference between store seq and delivery seq.
storeBaseOff := 47
getMsg := func(seq int) *nats.Msg {
t.Helper()
sseq := 2*seq + storeBaseOff
m, err := sub.NextMsg(time.Second)
if err != nil {
t.Fatalf("Unexpected error: %v", err)
}
rsseq, roseq, dcount, _, _ := replyInfo(m.Reply)
if roseq != uint64(seq) {
t.Fatalf("Expected consumer sequence of %d , got %d", seq, roseq)
}
if rsseq != uint64(sseq) {
t.Fatalf("Expected stream sequence of %d , got %d", sseq, rsseq)
}
if dcount != 1 {
t.Fatalf("Expected message to not be marked as redelivered")
}
return m
}
getRedeliveredMsg := func(seq int) *nats.Msg {
t.Helper()
m, err := sub.NextMsg(time.Second)
if err != nil {
t.Fatalf("Unexpected error: %v", err)
}
_, roseq, dcount, _, _ := replyInfo(m.Reply)
if roseq != uint64(seq) {
t.Fatalf("Expected consumer sequence of %d , got %d", seq, roseq)
}
if dcount < 2 {
t.Fatalf("Expected message to be marked as redelivered")
}
// Ack this message.
m.Respond(nil)
return m
}
// All consumers start at 1 and always have increasing sequence numbers.
m := getMsg(1)
m.Respond(nil)
// Now send 50 more, so 100 total, 26 (last + 50/2) for this consumer.
sendMsgs(toSend)
state := mset.state()
if state.Msgs != uint64(toSend*2) {
t.Fatalf("Expected %d messages, got %d", toSend*2, state.Msgs)
}
// For tracking next expected.
nextSeq := 2
noAcks := 0
for i := 0; i < toSend/2; i++ {
m := getMsg(nextSeq)
if i%2 == 0 {
m.Respond(nil) // Ack evens.
} else {
noAcks++
}
nextSeq++
}
// We should now get those redelivered.
for i := 0; i < noAcks; i++ {
getRedeliveredMsg(nextSeq)
nextSeq++
}
// Now send 50 more.
sendMsgs(toSend)
storeBaseOff -= noAcks * 2
for i := 0; i < toSend/2; i++ {
m := getMsg(nextSeq)
m.Respond(nil)
nextSeq++
}
})
}
}
func TestJetStreamConsumerInactiveNoDeadlock(t *testing.T) {
cases := []struct {
name string
mconfig *StreamConfig
}{
{"MemoryStore", &StreamConfig{Name: "DC", Storage: MemoryStorage}},
{"FileStore", &StreamConfig{Name: "DC", Storage: FileStorage}},
}
for _, c := range cases {
t.Run(c.name, func(t *testing.T) {
s := RunBasicJetStreamServer(t)
defer s.Shutdown()
mset, err := s.GlobalAccount().addStream(c.mconfig)
if err != nil {
t.Fatalf("Unexpected error adding stream: %v", err)
}
defer mset.delete()
nc, js := jsClientConnect(t, s)
defer nc.Close()
// Send lots of msgs and have them queued up.
for i := 0; i < 10000; i++ {
js.Publish("DC", []byte("OK!"))
}
if state := mset.state(); state.Msgs != 10000 {
t.Fatalf("Expected %d messages, got %d", 10000, state.Msgs)
}
sub, _ := nc.SubscribeSync(nats.NewInbox())
sub.SetPendingLimits(-1, -1)
defer sub.Unsubscribe()
nc.Flush()
o, err := mset.addConsumer(&ConsumerConfig{DeliverSubject: sub.Subject})
if err != nil {
t.Fatalf("Unexpected error: %v", err)
}
defer o.delete()
for i := 0; i < 10; i++ {
if _, err := sub.NextMsg(time.Second); err != nil {
t.Fatalf("Unexpected error: %v", err)
}
}
// Force us to become inactive but we want to make sure we do not lock up
// the internal sendq.
sub.Unsubscribe()
nc.Flush()
})
}
}
func TestJetStreamConsumerReconnect(t *testing.T) {
cases := []struct {
name string
mconfig *StreamConfig
}{
{"MemoryStore", &StreamConfig{Name: "ET", Storage: MemoryStorage, Subjects: []string{"foo.*"}}},
{"FileStore", &StreamConfig{Name: "ET", Storage: FileStorage, Subjects: []string{"foo.*"}}},
}
for _, c := range cases {
t.Run(c.name, func(t *testing.T) {
s := RunBasicJetStreamServer(t)
defer s.Shutdown()
mset, err := s.GlobalAccount().addStream(c.mconfig)
if err != nil {
t.Fatalf("Unexpected error adding stream: %v", err)
}
defer mset.delete()
nc := clientConnectToServer(t, s)
defer nc.Close()
sub, _ := nc.SubscribeSync(nats.NewInbox())
defer sub.Unsubscribe()
nc.Flush()
// Capture the subscription.
delivery := sub.Subject
o, err := mset.addConsumer(&ConsumerConfig{DeliverSubject: delivery, AckPolicy: AckExplicit})
if err != nil {
t.Fatalf("Unexpected error: %v", err)
}
if !o.isActive() {
t.Fatalf("Expected the consumer to be considered active")
}
if numo := mset.numConsumers(); numo != 1 {
t.Fatalf("Expected number of consumers to be 1, got %d", numo)
}
// We will simulate reconnect by unsubscribing on one connection and forming
// the same on another. Once we have cluster tests we will do more testing on
// reconnect scenarios.
getMsg := func(seqno int) *nats.Msg {
t.Helper()
m, err := sub.NextMsg(time.Second)
if err != nil {
t.Fatalf("Unexpected error for %d: %v", seqno, err)
}
if seq := o.seqFromReply(m.Reply); seq != uint64(seqno) {
t.Fatalf("Expected sequence of %d , got %d", seqno, seq)
}
m.Respond(nil)
return m
}
sendMsg := func() {
t.Helper()
if err := nc.Publish("foo.22", []byte("OK!")); err != nil {
return
}
}
checkForInActive := func() {
checkFor(t, 250*time.Millisecond, 50*time.Millisecond, func() error {
if o.isActive() {
return fmt.Errorf("Consumer is still active")
}
return nil
})
}
// Send and Pull first message.
sendMsg() // 1
getMsg(1)
// Cancel first one.
sub.Unsubscribe()
// Re-establish new sub on same subject.
sub, _ = nc.SubscribeSync(delivery)
nc.Flush()
// We should be getting 2 here.
sendMsg() // 2
getMsg(2)
sub.Unsubscribe()
checkForInActive()
// send 3-10
for i := 0; i <= 7; i++ {
sendMsg()
}
// Make sure they are all queued up with no interest.
nc.Flush()
// Restablish again.
sub, _ = nc.SubscribeSync(delivery)
nc.Flush()
// We should be getting 3-10 here.
for i := 3; i <= 10; i++ {
getMsg(i)
}
})
}
}
func TestJetStreamConsumerDurableReconnect(t *testing.T) {
cases := []struct {
name string
mconfig *StreamConfig
}{
{"MemoryStore", &StreamConfig{Name: "DT", Storage: MemoryStorage, Subjects: []string{"foo.*"}}},
{"FileStore", &StreamConfig{Name: "DT", Storage: FileStorage, Subjects: []string{"foo.*"}}},
}
for _, c := range cases {
t.Run(c.name, func(t *testing.T) {
s := RunBasicJetStreamServer(t)
defer s.Shutdown()
mset, err := s.GlobalAccount().addStream(c.mconfig)
if err != nil {
t.Fatalf("Unexpected error adding stream: %v", err)
}
defer mset.delete()
nc := clientConnectToServer(t, s)
defer nc.Close()
dname := "d22"
subj1 := nats.NewInbox()
o, err := mset.addConsumer(&ConsumerConfig{
Durable: dname,
DeliverSubject: subj1,
AckPolicy: AckExplicit,
AckWait: 50 * time.Millisecond})
if err != nil {
t.Fatalf("Unexpected error: %v", err)
}
sendMsg := func() {
t.Helper()
if err := nc.Publish("foo.22", []byte("OK!")); err != nil {
return
}
}
// Send 10 msgs
toSend := 10
for i := 0; i < toSend; i++ {
sendMsg()
}
sub, _ := nc.SubscribeSync(subj1)
defer sub.Unsubscribe()
checkFor(t, 500*time.Millisecond, 10*time.Millisecond, func() error {
if nmsgs, _, _ := sub.Pending(); err != nil || nmsgs != toSend {
return fmt.Errorf("Did not receive correct number of messages: %d vs %d", nmsgs, toSend)
}
return nil
})
getMsg := func(seqno int) *nats.Msg {
t.Helper()
m, err := sub.NextMsg(time.Second)
if err != nil {
t.Fatalf("Unexpected error: %v", err)
}
if seq := o.streamSeqFromReply(m.Reply); seq != uint64(seqno) {
t.Fatalf("Expected sequence of %d , got %d", seqno, seq)
}
m.Respond(nil)
return m
}
// Ack first half
for i := 1; i <= toSend/2; i++ {
m := getMsg(i)
m.Respond(nil)
}
// Now unsubscribe and wait to become inactive
sub.Unsubscribe()
checkFor(t, 250*time.Millisecond, 50*time.Millisecond, func() error {
if o.isActive() {
return fmt.Errorf("Consumer is still active")
}
return nil
})
// Now we should be able to replace the delivery subject.
subj2 := nats.NewInbox()
sub, _ = nc.SubscribeSync(subj2)
defer sub.Unsubscribe()
nc.Flush()
o, err = mset.addConsumer(&ConsumerConfig{
Durable: dname,
DeliverSubject: subj2,
AckPolicy: AckExplicit,
AckWait: 50 * time.Millisecond})
if err != nil {
t.Fatalf("Unexpected error trying to add a new durable consumer: %v", err)
}
// We should get the remaining messages here.
for i := toSend/2 + 1; i <= toSend; i++ {
m := getMsg(i)
m.Respond(nil)
}
})
}
}
func TestJetStreamConsumerReplayRate(t *testing.T) {
cases := []struct {
name string
mconfig *StreamConfig
}{
{"MemoryStore", &StreamConfig{Name: "DC", Storage: MemoryStorage}},
{"FileStore", &StreamConfig{Name: "DC", Storage: FileStorage}},
}
for _, c := range cases {
t.Run(c.name, func(t *testing.T) {
s := RunBasicJetStreamServer(t)
defer s.Shutdown()
mset, err := s.GlobalAccount().addStream(c.mconfig)
if err != nil {
t.Fatalf("Unexpected error adding stream: %v", err)
}
defer mset.delete()
nc := clientConnectToServer(t, s)
defer nc.Close()
// Send 10 msgs
totalMsgs := 10
var gaps []time.Duration
lst := time.Now()
for i := 0; i < totalMsgs; i++ {
gaps = append(gaps, time.Since(lst))
lst = time.Now()
nc.Publish("DC", []byte("OK!"))
// Calculate a gap between messages.
gap := 10*time.Millisecond + time.Duration(rand.Intn(20))*time.Millisecond
time.Sleep(gap)
}
if state := mset.state(); state.Msgs != uint64(totalMsgs) {
t.Fatalf("Expected %d messages, got %d", totalMsgs, state.Msgs)
}
sub, _ := nc.SubscribeSync(nats.NewInbox())
defer sub.Unsubscribe()
nc.Flush()
o, err := mset.addConsumer(&ConsumerConfig{DeliverSubject: sub.Subject})
if err != nil {
t.Fatalf("Unexpected error: %v", err)
}
defer o.delete()
// Firehose/instant which is default.
last := time.Now()
for i := 0; i < totalMsgs; i++ {
if _, err := sub.NextMsg(time.Second); err != nil {
t.Fatalf("Unexpected error: %v", err)
}
now := time.Now()
// Delivery from addConsumer starts in a go routine, so be
// more tolerant for the first message.
limit := 5 * time.Millisecond
if i == 0 {
limit = 10 * time.Millisecond
}
if now.Sub(last) > limit {
t.Fatalf("Expected firehose/instant delivery, got message gap of %v", now.Sub(last))
}
last = now
}
// Now do replay rate to match original.
o, err = mset.addConsumer(&ConsumerConfig{DeliverSubject: sub.Subject, ReplayPolicy: ReplayOriginal})
if err != nil {
t.Fatalf("Unexpected error: %v", err)
}
defer o.delete()
// Original rate messsages were received for push based consumer.
for i := 0; i < totalMsgs; i++ {
start := time.Now()
if _, err := sub.NextMsg(time.Second); err != nil {
t.Fatalf("Unexpected error: %v", err)
}
gap := time.Since(start)
// 15ms is high but on macs time.Sleep(delay) does not sleep only delay.
// Also on travis if things get bogged down this could be delayed.
gl, gh := gaps[i]-10*time.Millisecond, gaps[i]+15*time.Millisecond
if gap < gl || gap > gh {
t.Fatalf("Gap is off for %d, expected %v got %v", i, gaps[i], gap)
}
}
// Now create pull based.
oc := workerModeConfig("PM")
oc.ReplayPolicy = ReplayOriginal
o, err = mset.addConsumer(oc)
if err != nil {
t.Fatalf("Unexpected error: %v", err)
}
defer o.delete()
for i := 0; i < totalMsgs; i++ {
start := time.Now()
if _, err := nc.Request(o.requestNextMsgSubject(), nil, time.Second); err != nil {
t.Fatalf("Unexpected error: %v", err)
}
gap := time.Since(start)
// 10ms is high but on macs time.Sleep(delay) does not sleep only delay.
gl, gh := gaps[i]-5*time.Millisecond, gaps[i]+10*time.Millisecond
if gap < gl || gap > gh {
t.Fatalf("Gap is incorrect for %d, expected %v got %v", i, gaps[i], gap)
}
}
})
}
}
func TestJetStreamConsumerReplayRateNoAck(t *testing.T) {
cases := []struct {
name string
mconfig *StreamConfig
}{
{"MemoryStore", &StreamConfig{Name: "DC", Storage: MemoryStorage}},
{"FileStore", &StreamConfig{Name: "DC", Storage: FileStorage}},
}
for _, c := range cases {
t.Run(c.name, func(t *testing.T) {
s := RunBasicJetStreamServer(t)
defer s.Shutdown()
mset, err := s.GlobalAccount().addStream(c.mconfig)
if err != nil {
t.Fatalf("Unexpected error adding stream: %v", err)
}
defer mset.delete()
nc := clientConnectToServer(t, s)
defer nc.Close()
// Send 10 msgs
totalMsgs := 10
for i := 0; i < totalMsgs; i++ {
nc.Request("DC", []byte("Hello World"), time.Second)
time.Sleep(time.Duration(rand.Intn(5)) * time.Millisecond)
}
if state := mset.state(); state.Msgs != uint64(totalMsgs) {
t.Fatalf("Expected %d messages, got %d", totalMsgs, state.Msgs)
}
subj := "d.dc"
o, err := mset.addConsumer(&ConsumerConfig{
Durable: "derek",
DeliverSubject: subj,
AckPolicy: AckNone,
ReplayPolicy: ReplayOriginal,
})
if err != nil {
t.Fatalf("Unexpected error: %v", err)
}
defer o.delete()
// Sleep a random amount of time.
time.Sleep(time.Duration(rand.Intn(20)) * time.Millisecond)
sub, _ := nc.SubscribeSync(subj)
nc.Flush()
checkFor(t, time.Second, 25*time.Millisecond, func() error {
if nmsgs, _, _ := sub.Pending(); err != nil || nmsgs != totalMsgs {
return fmt.Errorf("Did not receive correct number of messages: %d vs %d", nmsgs, totalMsgs)
}
return nil
})
})
}
}
func TestJetStreamConsumerReplayQuit(t *testing.T) {
cases := []struct {
name string
mconfig *StreamConfig
}{
{"MemoryStore", &StreamConfig{Name: "DC", Storage: MemoryStorage}},
{"FileStore", &StreamConfig{Name: "DC", Storage: FileStorage}},
}
for _, c := range cases {
t.Run(c.name, func(t *testing.T) {
s := RunBasicJetStreamServer(t)
defer s.Shutdown()
mset, err := s.GlobalAccount().addStream(c.mconfig)
if err != nil {
t.Fatalf("Unexpected error adding stream: %v", err)
}
defer mset.delete()
nc := clientConnectToServer(t, s)
defer nc.Close()
// Send 2 msgs
nc.Request("DC", []byte("OK!"), time.Second)
time.Sleep(100 * time.Millisecond)
nc.Request("DC", []byte("OK!"), time.Second)
if state := mset.state(); state.Msgs != 2 {
t.Fatalf("Expected %d messages, got %d", 2, state.Msgs)
}
sub, _ := nc.SubscribeSync(nats.NewInbox())
defer sub.Unsubscribe()
nc.Flush()
// Now do replay rate to match original.
o, err := mset.addConsumer(&ConsumerConfig{DeliverSubject: sub.Subject, ReplayPolicy: ReplayOriginal})
if err != nil {
t.Fatalf("Unexpected error: %v", err)
}
// Allow loop and deliver / replay go routine to spin up
time.Sleep(50 * time.Millisecond)
base := runtime.NumGoroutine()
o.delete()
checkFor(t, 100*time.Millisecond, 10*time.Millisecond, func() error {
if runtime.NumGoroutine() >= base {
return fmt.Errorf("Consumer go routines still running")
}
return nil
})
})
}
}
func TestJetStreamConsumerPerf(t *testing.T) {
// Comment out to run, holding place for now.
t.SkipNow()
s := RunBasicJetStreamServer(t)
defer s.Shutdown()
acc := s.GlobalAccount()
msetConfig := StreamConfig{
Name: "sr22",
Storage: MemoryStorage,
Subjects: []string{"foo"},
}
mset, err := acc.addStream(&msetConfig)
if err != nil {
t.Fatalf("Unexpected error adding stream: %v", err)
}
nc := clientConnectToServer(t, s)
defer nc.Close()
payload := []byte("Hello World")
toStore := 2000000
for i := 0; i < toStore; i++ {
nc.Publish("foo", payload)
}
nc.Flush()
_, err = mset.addConsumer(&ConsumerConfig{
Durable: "d",
DeliverSubject: "d",
AckPolicy: AckNone,
})
if err != nil {
t.Fatalf("Error creating consumer: %v", err)
}
var received int
done := make(chan bool)
nc.Subscribe("d", func(m *nats.Msg) {
received++
if received >= toStore {
done <- true
}
})
start := time.Now()
nc.Flush()
<-done
tt := time.Since(start)
fmt.Printf("time is %v\n", tt)
fmt.Printf("%.0f msgs/sec\n", float64(toStore)/tt.Seconds())
}
func TestJetStreamConsumerAckFileStorePerf(t *testing.T) {
// Comment out to run, holding place for now.
t.SkipNow()
s := RunBasicJetStreamServer(t)
defer s.Shutdown()
acc := s.GlobalAccount()
msetConfig := StreamConfig{
Name: "sr22",
Storage: FileStorage,
Subjects: []string{"foo"},
}
mset, err := acc.addStream(&msetConfig)
if err != nil {
t.Fatalf("Unexpected error adding stream: %v", err)
}
nc := clientConnectToServer(t, s)
defer nc.Close()
payload := []byte("Hello World")
toStore := uint64(200000)
for i := uint64(0); i < toStore; i++ {
nc.Publish("foo", payload)
}
nc.Flush()
if msgs := mset.state().Msgs; msgs != uint64(toStore) {
t.Fatalf("Expected %d messages, got %d", toStore, msgs)
}
o, err := mset.addConsumer(&ConsumerConfig{
Durable: "d",
DeliverSubject: "d",
AckPolicy: AckExplicit,
AckWait: 10 * time.Minute,
})
if err != nil {
t.Fatalf("Error creating consumer: %v", err)
}
defer o.stop()
var received uint64
done := make(chan bool)
sub, _ := nc.Subscribe("d", func(m *nats.Msg) {
m.Respond(nil) // Ack
received++
if received >= toStore {
done <- true
}
})
sub.SetPendingLimits(-1, -1)
start := time.Now()
nc.Flush()
<-done
tt := time.Since(start)
fmt.Printf("time is %v\n", tt)
fmt.Printf("%.0f msgs/sec\n", float64(toStore)/tt.Seconds())
}
// This test is in support fo clients that want to match on subject, they
// can set the filter subject always. We always store the subject so that
// should the stream later be edited to expand into more subjects the consumer
// still gets what was actually requested
func TestJetStreamConsumerFilterSubject(t *testing.T) {
s := RunBasicJetStreamServer(t)
defer s.Shutdown()
sc := &StreamConfig{Name: "MY_STREAM", Subjects: []string{"foo"}}
mset, err := s.GlobalAccount().addStream(sc)
if err != nil {
t.Fatalf("Unexpected error adding stream: %v", err)
}
defer mset.delete()
cfg := &ConsumerConfig{
Durable: "d",
DeliverSubject: "A",
AckPolicy: AckExplicit,
FilterSubject: "foo",
}
o, err := mset.addConsumer(cfg)
if err != nil {
t.Fatalf("Unexpected error adding consumer: %v", err)
}
defer o.delete()
if o.info().Config.FilterSubject != "foo" {
t.Fatalf("Expected the filter to be stored")
}
// Now use the original cfg with updated delivery subject and make sure that works ok.
cfg = &ConsumerConfig{
Durable: "d",
DeliverSubject: "B",
AckPolicy: AckExplicit,
FilterSubject: "foo",
}
o, err = mset.addConsumer(cfg)
if err != nil {
t.Fatalf("Unexpected error adding consumer: %v", err)
}
defer o.delete()
}
func TestJetStreamConsumerUpdateRedelivery(t *testing.T) {
cases := []struct {
name string
mconfig *StreamConfig
}{
{"MemoryStore", &StreamConfig{
Name: "MY_STREAM",
Storage: MemoryStorage,
Subjects: []string{"foo.>"},
Retention: InterestPolicy,
}},
{"FileStore", &StreamConfig{
Name: "MY_STREAM",
Storage: FileStorage,
Subjects: []string{"foo.>"},
Retention: InterestPolicy,
}},
}
for _, c := range cases {
t.Run(c.name, func(t *testing.T) {
s := RunBasicJetStreamServer(t)
defer s.Shutdown()
mset, err := s.GlobalAccount().addStream(c.mconfig)
if err != nil {
t.Fatalf("Unexpected error adding stream: %v", err)
}
defer mset.delete()
nc := clientConnectToServer(t, s)
defer nc.Close()
// Create a durable consumer.
sub, _ := nc.SubscribeSync(nats.NewInbox())
defer sub.Unsubscribe()
o, err := mset.addConsumer(&ConsumerConfig{
Durable: "dur22",
DeliverSubject: sub.Subject,
FilterSubject: "foo.bar",
AckPolicy: AckExplicit,
AckWait: 100 * time.Millisecond,
MaxDeliver: 3,
})
if err != nil {
t.Fatalf("Unexpected error adding consumer: %v", err)
}
defer o.delete()
// Send 20 messages
toSend := 20
for i := 1; i <= toSend; i++ {
sendStreamMsg(t, nc, "foo.bar", fmt.Sprintf("msg-%v", i))
}
state := mset.state()
if state.Msgs != uint64(toSend) {
t.Fatalf("Expected %v messages, got %d", toSend, state.Msgs)
}
// Receive the messages and ack only every 4th
for i := 0; i < toSend; i++ {
m, err := sub.NextMsg(time.Second)
if err != nil {
t.Fatalf("Error getting message: %v", err)
}
seq, _, _, _, _ := replyInfo(m.Reply)
// 4, 8, 12, 16, 20
if seq%4 == 0 {
m.Respond(nil)
}
}
// Now close the sub and open a new one and update the consumer.
sub.Unsubscribe()
// Wait for it to become inactive
checkFor(t, 200*time.Millisecond, 10*time.Millisecond, func() error {
if o.isActive() {
return fmt.Errorf("Consumer still active")
}
return nil
})
// Send 20 more messages.
for i := toSend; i < toSend*2; i++ {
sendStreamMsg(t, nc, "foo.bar", fmt.Sprintf("msg-%v", i))
}
// Create new subscription.
sub, _ = nc.SubscribeSync(nats.NewInbox())
defer sub.Unsubscribe()
nc.Flush()
o, err = mset.addConsumer(&ConsumerConfig{
Durable: "dur22",
DeliverSubject: sub.Subject,
FilterSubject: "foo.bar",
AckPolicy: AckExplicit,
AckWait: 100 * time.Millisecond,
MaxDeliver: 3,
})
if err != nil {
t.Fatalf("Unexpected error adding consumer: %v", err)
}
defer o.delete()
expect := toSend + toSend - 5 // mod 4 acks
checkFor(t, time.Second, 5*time.Millisecond, func() error {
if nmsgs, _, _ := sub.Pending(); err != nil || nmsgs != expect {
return fmt.Errorf("Did not receive correct number of messages: %d vs %d", nmsgs, expect)
}
return nil
})
for i, eseq := 0, uint64(1); i < expect; i++ {
m, err := sub.NextMsg(time.Second)
if err != nil {
t.Fatalf("Error getting message: %v", err)
}
// Skip the ones we ack'd from above. We should not get them back here.
if eseq <= uint64(toSend) && eseq%4 == 0 {
eseq++
}
seq, _, dc, _, _ := replyInfo(m.Reply)
if seq != eseq {
t.Fatalf("Expected stream sequence of %d, got %d", eseq, seq)
}
if seq <= uint64(toSend) && dc != 2 {
t.Fatalf("Expected delivery count of 2 for sequence of %d, got %d", seq, dc)
}
if seq > uint64(toSend) && dc != 1 {
t.Fatalf("Expected delivery count of 1 for sequence of %d, got %d", seq, dc)
}
if seq > uint64(toSend) {
m.Respond(nil) // Ack
}
eseq++
}
// We should get the second half back since we did not ack those from above.
expect = toSend - 5
checkFor(t, time.Second, 5*time.Millisecond, func() error {
if nmsgs, _, _ := sub.Pending(); err != nil || nmsgs != expect {
return fmt.Errorf("Did not receive correct number of messages: %d vs %d", nmsgs, expect)
}
return nil
})
for i, eseq := 0, uint64(1); i < expect; i++ {
m, err := sub.NextMsg(time.Second)
if err != nil {
t.Fatalf("Error getting message: %v", err)
}
// Skip the ones we ack'd from above. We should not get them back here.
if eseq <= uint64(toSend) && eseq%4 == 0 {
eseq++
}
seq, _, dc, _, _ := replyInfo(m.Reply)
if seq != eseq {
t.Fatalf("Expected stream sequence of %d, got %d", eseq, seq)
}
if dc != 3 {
t.Fatalf("Expected delivery count of 3 for sequence of %d, got %d", seq, dc)
}
eseq++
}
})
}
}
func TestJetStreamConsumerMaxAckPending(t *testing.T) {
cases := []struct {
name string
mconfig *StreamConfig
}{
{"MemoryStore", &StreamConfig{
Name: "MY_STREAM",
Storage: MemoryStorage,
Subjects: []string{"foo.*"},
}},
{"FileStore", &StreamConfig{
Name: "MY_STREAM",
Storage: FileStorage,
Subjects: []string{"foo.*"},
}},
}
for _, c := range cases {
t.Run(c.name, func(t *testing.T) {
s := RunBasicJetStreamServer(t)
defer s.Shutdown()
mset, err := s.GlobalAccount().addStream(c.mconfig)
if err != nil {
t.Fatalf("Unexpected error adding stream: %v", err)
}
defer mset.delete()
nc := clientConnectToServer(t, s)
defer nc.Close()
// Do error scenarios.
_, err = mset.addConsumer(&ConsumerConfig{
Durable: "d22",
DeliverSubject: nats.NewInbox(),
AckPolicy: AckNone,
MaxAckPending: 1,
})
if err == nil {
t.Fatalf("Expected error, MaxAckPending only applicable to ack != AckNone")
}
// Queue up 100 messages.
toSend := 100
for i := 0; i < toSend; i++ {
sendStreamMsg(t, nc, "foo.bar", fmt.Sprintf("MSG: %d", i+1))
}
// Limit to 33
maxAckPending := 33
o, err := mset.addConsumer(&ConsumerConfig{
Durable: "d22",
DeliverSubject: nats.NewInbox(),
AckPolicy: AckExplicit,
MaxAckPending: maxAckPending,
})
require_NoError(t, err)
defer o.delete()
sub, _ := nc.SubscribeSync(o.info().Config.DeliverSubject)
defer sub.Unsubscribe()
checkSubPending := func(numExpected int) {
t.Helper()
checkFor(t, time.Second, 20*time.Millisecond, func() error {
if nmsgs, _, _ := sub.Pending(); err != nil || nmsgs != numExpected {
return fmt.Errorf("Did not receive correct number of messages: %d vs %d", nmsgs, numExpected)
}
return nil
})
}
checkSubPending(maxAckPending)
// We hit the limit, double check we stayed there.
if nmsgs, _, _ := sub.Pending(); err != nil || nmsgs != maxAckPending {
t.Fatalf("Too many messages received: %d vs %d", nmsgs, maxAckPending)
}
// Now ack them all.
for i := 0; i < maxAckPending; i++ {
m, err := sub.NextMsg(time.Second)
if err != nil {
t.Fatalf("Error receiving message %d: %v", i, err)
}
m.Respond(nil)
}
checkSubPending(maxAckPending)
o.stop()
mset.purge(nil)
// Now test a consumer that is live while we publish messages to the stream.
o, err = mset.addConsumer(&ConsumerConfig{
Durable: "d33",
DeliverSubject: nats.NewInbox(),
AckPolicy: AckExplicit,
MaxAckPending: maxAckPending,
})
require_NoError(t, err)
defer o.delete()
sub, _ = nc.SubscribeSync(o.info().Config.DeliverSubject)
defer sub.Unsubscribe()
nc.Flush()
checkSubPending(0)
// Now stream more then maxAckPending.
for i := 0; i < toSend; i++ {
sendStreamMsg(t, nc, "foo.baz", fmt.Sprintf("MSG: %d", i+1))
}
checkSubPending(maxAckPending)
// We hit the limit, double check we stayed there.
if nmsgs, _, _ := sub.Pending(); err != nil || nmsgs != maxAckPending {
t.Fatalf("Too many messages received: %d vs %d", nmsgs, maxAckPending)
}
})
}
}
func TestJetStreamConsumerPullMaxAckPending(t *testing.T) {
cases := []struct {
name string
mconfig *StreamConfig
}{
{"MemoryStore", &StreamConfig{
Name: "MY_STREAM",
Storage: MemoryStorage,
Subjects: []string{"foo.*"},
}},
{"FileStore", &StreamConfig{
Name: "MY_STREAM",
Storage: FileStorage,
Subjects: []string{"foo.*"},
}},
}
for _, c := range cases {
t.Run(c.name, func(t *testing.T) {
s := RunBasicJetStreamServer(t)
defer s.Shutdown()
mset, err := s.GlobalAccount().addStream(c.mconfig)
if err != nil {
t.Fatalf("Unexpected error adding stream: %v", err)
}
defer mset.delete()
nc := clientConnectToServer(t, s)
defer nc.Close()
// Queue up 100 messages.
toSend := 100
for i := 0; i < toSend; i++ {
sendStreamMsg(t, nc, "foo.bar", fmt.Sprintf("MSG: %d", i+1))
}
// Limit to 33
maxAckPending := 33
o, err := mset.addConsumer(&ConsumerConfig{
Durable: "d22",
AckPolicy: AckExplicit,
MaxAckPending: maxAckPending,
})
require_NoError(t, err)
defer o.delete()
getSubj := o.requestNextMsgSubject()
var toAck []*nats.Msg
for i := 0; i < maxAckPending; i++ {
if m, err := nc.Request(getSubj, nil, time.Second); err != nil {
t.Fatalf("Unexpected error: %v", err)
} else {
toAck = append(toAck, m)
}
}
// Now ack them all.
for _, m := range toAck {
m.Respond(nil)
}
// Now do batch above the max.
sub, _ := nc.SubscribeSync(nats.NewInbox())
defer sub.Unsubscribe()
checkSubPending := func(numExpected int) {
t.Helper()
checkFor(t, time.Second, 10*time.Millisecond, func() error {
if nmsgs, _, _ := sub.Pending(); err != nil || nmsgs != numExpected {
return fmt.Errorf("Did not receive correct number of messages: %d vs %d", nmsgs, numExpected)
}
return nil
})
}
req := &JSApiConsumerGetNextRequest{Batch: maxAckPending}
jreq, _ := json.Marshal(req)
nc.PublishRequest(getSubj, sub.Subject, jreq)
checkSubPending(maxAckPending)
// We hit the limit, double check we stayed there.
if nmsgs, _, _ := sub.Pending(); err != nil || nmsgs != maxAckPending {
t.Fatalf("Too many messages received: %d vs %d", nmsgs, maxAckPending)
}
})
}
}
func TestJetStreamConsumerPullMaxAckPendingRedeliveries(t *testing.T) {
cases := []struct {
name string
mconfig *StreamConfig
}{
{"MemoryStore", &StreamConfig{
Name: "MY_STREAM",
Storage: MemoryStorage,
Subjects: []string{"foo.*"},
}},
{"FileStore", &StreamConfig{
Name: "MY_STREAM",
Storage: FileStorage,
Subjects: []string{"foo.*"},
}},
}
for _, c := range cases {
t.Run(c.name, func(t *testing.T) {
s := RunBasicJetStreamServer(t)
defer s.Shutdown()
mset, err := s.GlobalAccount().addStream(c.mconfig)
if err != nil {
t.Fatalf("Unexpected error adding stream: %v", err)
}
defer mset.delete()
nc := clientConnectToServer(t, s)
defer nc.Close()
// Queue up 10 messages.
toSend := 10
for i := 0; i < toSend; i++ {
sendStreamMsg(t, nc, "foo.bar", fmt.Sprintf("MSG: %d", i+1))
}
// Limit to 1
maxAckPending := 1
ackWait := 20 * time.Millisecond
expSeq := uint64(4)
o, err := mset.addConsumer(&ConsumerConfig{
Durable: "d22",
DeliverPolicy: DeliverByStartSequence,
OptStartSeq: expSeq,
AckPolicy: AckExplicit,
AckWait: ackWait,
MaxAckPending: maxAckPending,
})
require_NoError(t, err)
defer o.delete()
getSubj := o.requestNextMsgSubject()
delivery := uint64(1)
getNext := func() {
t.Helper()
m, err := nc.Request(getSubj, nil, time.Second)
if err != nil {
t.Fatalf("Unexpected error: %v", err)
}
sseq, dseq, dcount, _, pending := replyInfo(m.Reply)
if sseq != expSeq {
t.Fatalf("Expected stream sequence of %d, got %d", expSeq, sseq)
}
if dseq != delivery {
t.Fatalf("Expected consumer sequence of %d, got %d", delivery, dseq)
}
if dcount != delivery {
t.Fatalf("Expected delivery count of %d, got %d", delivery, dcount)
}
if pending != uint64(toSend)-expSeq {
t.Fatalf("Expected pending to be %d, got %d", uint64(toSend)-expSeq, pending)
}
delivery++
}
getNext()
getNext()
getNext()
getNext()
getNext()
})
}
}
// User report of bug.
func TestJetStreamConsumerBadNumPending(t *testing.T) {
s := RunBasicJetStreamServer(t)
defer s.Shutdown()
// Client for API requests.
nc, js := jsClientConnect(t, s)
defer nc.Close()
_, err := js.AddStream(&nats.StreamConfig{
Name: "ORDERS",
Subjects: []string{"orders.*"},
})
require_NoError(t, err)
newOrders := func(n int) {
// Queue up new orders.
for i := 0; i < n; i++ {
js.Publish("orders.created", []byte("NEW"))
}
}
newOrders(10)
// Create to subscribers.
process := func(m *nats.Msg) {
js.Publish("orders.approved", []byte("APPROVED"))
}
op, err := js.Subscribe("orders.created", process)
require_NoError(t, err)
defer op.Unsubscribe()
mon, err := js.SubscribeSync("orders.*")
require_NoError(t, err)
defer mon.Unsubscribe()
waitForMsgs := func(n uint64) {
t.Helper()
checkFor(t, 2*time.Second, 100*time.Millisecond, func() error {
si, err := js.StreamInfo("ORDERS")
if err != nil {
t.Fatalf("Unexpected error: %v", err)
}
if si.State.Msgs != n {
return fmt.Errorf("Expected %d msgs, got state: %+v", n, si.State)
}
return nil
})
}
checkForNoPending := func(sub *nats.Subscription) {
t.Helper()
if ci, err := sub.ConsumerInfo(); err != nil || ci == nil || ci.NumPending != 0 {
if ci != nil && ci.NumPending != 0 {
t.Fatalf("Bad consumer NumPending, expected 0 but got %d", ci.NumPending)
} else {
t.Fatalf("Bad consumer info: %+v", ci)
}
}
}
waitForMsgs(20)
checkForNoPending(op)
checkForNoPending(mon)
newOrders(10)
waitForMsgs(40)
checkForNoPending(op)
checkForNoPending(mon)
}
// We had a report of a consumer delete crashing the server when in interest retention mode.
// This I believe is only really possible in clustered mode, but we will force the issue here.
func TestJetStreamConsumerCleanupWithRetentionPolicy(t *testing.T) {
s := RunBasicJetStreamServer(t)
defer s.Shutdown()
// Client for API requests.
nc, js := jsClientConnect(t, s)
defer nc.Close()
_, err := js.AddStream(&nats.StreamConfig{
Name: "ORDERS",
Subjects: []string{"orders.*"},
Retention: nats.InterestPolicy,
})
require_NoError(t, err)
sub, err := js.SubscribeSync("orders.*")
require_NoError(t, err)
payload := []byte("Hello World")
for i := 0; i < 10; i++ {
subj := fmt.Sprintf("orders.%d", i+1)
js.Publish(subj, payload)
}
checkSubsPending(t, sub, 10)
for i := 0; i < 10; i++ {
m, err := sub.NextMsg(time.Second)
if err != nil {
t.Fatalf("Unexpected error: %v", err)
}
m.AckSync()
}
ci, err := sub.ConsumerInfo()
if err != nil {
t.Fatalf("Unexpected error getting consumer info: %v", err)
}
acc := s.GlobalAccount()
mset, err := acc.lookupStream("ORDERS")
require_NoError(t, err)
o := mset.lookupConsumer(ci.Name)
if o == nil {
t.Fatalf("Error looking up consumer %q", ci.Name)
}
lseq := mset.lastSeq()
o.mu.Lock()
// Force boundary condition here.
o.asflr = lseq + 2
o.mu.Unlock()
sub.Unsubscribe()
// Make sure server still available.
if _, err := js.StreamInfo("ORDERS"); err != nil {
t.Fatalf("Unexpected error: %v", err)
}
}
func TestJetStreamConsumerPendingBugWithKV(t *testing.T) {
msc := StreamConfig{
Name: "KV",
Subjects: []string{"kv.>"},
Storage: MemoryStorage,
MaxMsgsPer: 1,
}
fsc := msc
fsc.Storage = FileStorage
cases := []struct {
name string
mconfig *StreamConfig
}{
{"MemoryStore", &msc},
{"FileStore", &fsc},
}
for _, c := range cases {
t.Run(c.name, func(t *testing.T) {
s := RunBasicJetStreamServer(t)
defer s.Shutdown()
// Client based API
nc, js := jsClientConnect(t, s)
defer nc.Close()
// Not in Go client under server yet.
mset, err := s.GlobalAccount().addStream(c.mconfig)
if err != nil {
t.Fatalf("Unexpected error: %v", err)
}
js.Publish("kv.1", []byte("1"))
js.Publish("kv.2", []byte("2"))
js.Publish("kv.3", []byte("3"))
js.Publish("kv.1", []byte("4"))
si, err := js.StreamInfo("KV")
if err != nil {
t.Fatalf("Unexpected error: %v", err)
}
if si.State.Msgs != 3 {
t.Fatalf("Expected 3 total msgs, got %d", si.State.Msgs)
}
o, err := mset.addConsumer(&ConsumerConfig{
Durable: "dlc",
DeliverSubject: "xxx",
DeliverPolicy: DeliverLastPerSubject,
FilterSubject: ">",
})
if err != nil {
t.Fatalf("Unexpected error: %v", err)
}
if ci := o.info(); ci.NumPending != 3 {
t.Fatalf("Expected pending of 3, got %d", ci.NumPending)
}
})
}
}
// Issue #2423
func TestJetStreamConsumerBadCreateErr(t *testing.T) {
s := RunBasicJetStreamServer(t)
defer s.Shutdown()
// Client for API requests.
nc, js := jsClientConnect(t, s)
defer nc.Close()
_, err := js.AddStream(&nats.StreamConfig{
Name: "TEST",
Subjects: []string{"foo.*"},
Storage: nats.MemoryStorage,
})
require_NoError(t, err)
// When adding a consumer with both deliver subject and max wait (push vs pull),
// we got the wrong err about deliver subject having a wildcard.
_, err = js.AddConsumer("TEST", &nats.ConsumerConfig{
Durable: "nowcerr",
DeliverSubject: "X",
MaxWaiting: 100,
})
if err == nil {
t.Fatalf("Expected an error but got none")
}
if !strings.Contains(err.Error(), "push mode can not set max waiting") {
t.Fatalf("Incorrect error returned: %v", err)
}
}
func TestJetStreamConsumerPushBound(t *testing.T) {
s := RunBasicJetStreamServer(t)
defer s.Shutdown()
nc, js := jsClientConnect(t, s)
defer nc.Close()
cfg := &nats.StreamConfig{
Name: "TEST",
Storage: nats.MemoryStorage,
Subjects: []string{"foo"},
}
if _, err := js.AddStream(cfg); err != nil {
t.Fatalf("Unexpected error: %v", err)
}
// We want to test extended consumer info for push based consumers.
// We need to do these by hand for now.
createConsumer := func(name, deliver string) {
t.Helper()
creq := CreateConsumerRequest{
Stream: "TEST",
Config: ConsumerConfig{
Durable: name,
DeliverSubject: deliver,
AckPolicy: AckExplicit,
},
}
req, err := json.Marshal(creq)
if err != nil {
t.Fatalf("Unexpected error: %v", err)
}
resp, err := nc.Request(fmt.Sprintf(JSApiDurableCreateT, "TEST", name), req, time.Second)
if err != nil {
t.Fatalf("Unexpected error: %v", err)
}
var ccResp JSApiConsumerCreateResponse
if err := json.Unmarshal(resp.Data, &ccResp); err != nil {
t.Fatalf("Unexpected error: %v", err)
}
if ccResp.ConsumerInfo == nil || ccResp.Error != nil {
t.Fatalf("Got a bad response %+v", ccResp)
}
}
consumerInfo := func(name string) *ConsumerInfo {
t.Helper()
resp, err := nc.Request(fmt.Sprintf(JSApiConsumerInfoT, "TEST", name), nil, time.Second)
if err != nil {
t.Fatalf("Unexpected error: %v", err)
}
var cinfo JSApiConsumerInfoResponse
if err := json.Unmarshal(resp.Data, &cinfo); err != nil {
t.Fatalf("Unexpected error: %v", err)
}
if cinfo.ConsumerInfo == nil || cinfo.Error != nil {
t.Fatalf("Got a bad response %+v", cinfo)
}
return cinfo.ConsumerInfo
}
// First create a durable push and make sure we show now active status.
createConsumer("dlc", "d.X")
if ci := consumerInfo("dlc"); ci.PushBound {
t.Fatalf("Expected push bound to be false")
}
// Now bind the deliver subject.
sub, _ := nc.SubscribeSync("d.X")
nc.Flush() // Make sure it registers.
// Check that its reported.
if ci := consumerInfo("dlc"); !ci.PushBound {
t.Fatalf("Expected push bound to be set")
}
sub.Unsubscribe()
nc.Flush() // Make sure it registers.
if ci := consumerInfo("dlc"); ci.PushBound {
t.Fatalf("Expected push bound to be false")
}
// Now make sure we have queue groups indictated as needed.
createConsumer("ik", "d.Z")
// Now bind the deliver subject with a queue group.
sub, _ = nc.QueueSubscribeSync("d.Z", "g22")
defer sub.Unsubscribe()
nc.Flush() // Make sure it registers.
// Check that queue group is not reported.
if ci := consumerInfo("ik"); ci.PushBound {
t.Fatalf("Expected push bound to be false")
}
sub.Unsubscribe()
nc.Flush() // Make sure it registers.
if ci := consumerInfo("ik"); ci.PushBound {
t.Fatalf("Expected push bound to be false")
}
// Make sure pull consumers report PushBound as false by default.
createConsumer("rip", _EMPTY_)
if ci := consumerInfo("rip"); ci.PushBound {
t.Fatalf("Expected push bound to be false")
}
}
// Got a report of memory leaking, tracked it to internal clients for consumers.
func TestJetStreamConsumerInternalClientLeak(t *testing.T) {
s := RunBasicJetStreamServer(t)
defer s.Shutdown()
nc, js := jsClientConnect(t, s)
defer nc.Close()
cfg := &nats.StreamConfig{
Name: "TEST",
Storage: nats.MemoryStorage,
}
if _, err := js.AddStream(cfg); err != nil {
t.Fatalf("Unexpected error: %v", err)
}
ga, sa := s.GlobalAccount(), s.SystemAccount()
ncb, nscb := ga.NumConnections(), sa.NumConnections()
// Create 10 consumers
for i := 0; i < 10; i++ {
ci, err := js.AddConsumer("TEST", &nats.ConsumerConfig{DeliverSubject: "x"})
if err != nil {
t.Fatalf("Unexpected error: %v", err)
}
// Accelerate ephemeral cleanup.
mset, err := ga.lookupStream("TEST")
if err != nil {
t.Fatalf("Expected to find a stream for %q", "TEST")
}
o := mset.lookupConsumer(ci.Name)
if o == nil {
t.Fatalf("Error looking up consumer %q", ci.Name)
}
o.setInActiveDeleteThreshold(500 * time.Millisecond)
}
// Wait for them to all go away.
checkFor(t, 2*time.Second, 100*time.Millisecond, func() error {
si, err := js.StreamInfo("TEST")
if err != nil {
t.Fatalf("Unexpected error: %v", err)
}
if si.State.Consumers == 0 {
return nil
}
return fmt.Errorf("Consumers still present")
})
// Make sure we are not leaking clients/connections.
// Server does not see these so need to look at account.
if nca := ga.NumConnections(); nca != ncb {
t.Fatalf("Leaked clients in global account: %d vs %d", ncb, nca)
}
if nsca := sa.NumConnections(); nsca != nscb {
t.Fatalf("Leaked clients in system account: %d vs %d", nscb, nsca)
}
}
func TestJetStreamConsumerEventingRaceOnShutdown(t *testing.T) {
s := RunBasicJetStreamServer(t)
defer s.Shutdown()
nc, js := jsClientConnect(t, s, nats.NoReconnect())
defer nc.Close()
cfg := &nats.StreamConfig{
Name: "TEST",
Subjects: []string{"foo"},
Storage: nats.MemoryStorage,
}
if _, err := js.AddStream(cfg); err != nil {
t.Fatalf("Unexpected error: %v", err)
}
wg := sync.WaitGroup{}
wg.Add(1)
go func() {
defer wg.Done()
for {
if _, err := js.SubscribeSync("foo", nats.BindStream("TEST")); err != nil {
return
}
}
}()
time.Sleep(50 * time.Millisecond)
s.Shutdown()
wg.Wait()
}
func TestJetStreamConsumerNoMsgPayload(t *testing.T) {
s := RunBasicJetStreamServer(t)
defer s.Shutdown()
nc, js := jsClientConnect(t, s)
defer nc.Close()
_, err := js.AddStream(&nats.StreamConfig{Name: "S"})
require_NoError(t, err)
msg := nats.NewMsg("S")
msg.Header.Set("name", "derek")
msg.Data = bytes.Repeat([]byte("A"), 128)
for i := 0; i < 10; i++ {
msg.Reply = _EMPTY_ // Fixed in Go client but not in embdedded on yet.
_, err = js.PublishMsgAsync(msg)
require_NoError(t, err)
}
mset, err := s.GlobalAccount().lookupStream("S")
require_NoError(t, err)
// Now create our consumer with no payload option.
_, err = mset.addConsumer(&ConsumerConfig{DeliverSubject: "_d_", Durable: "d22", HeadersOnly: true})
require_NoError(t, err)
sub, err := js.SubscribeSync("S", nats.Durable("d22"))
require_NoError(t, err)
for i := 0; i < 10; i++ {
m, err := sub.NextMsg(time.Second)
require_NoError(t, err)
if len(m.Data) > 0 {
t.Fatalf("Expected no payload")
}
if ms := m.Header.Get(JSMsgSize); ms != "128" {
t.Fatalf("Expected a header with msg size, got %q", ms)
}
}
}
func TestJetStreamConsumerUpdateSurvival(t *testing.T) {
s := RunBasicJetStreamServer(t)
defer s.Shutdown()
nc, js := jsClientConnect(t, s)
defer nc.Close()
_, err := js.AddStream(&nats.StreamConfig{Name: "X"})
require_NoError(t, err)
// First create a consumer with max ack pending.
_, err = js.AddConsumer("X", &nats.ConsumerConfig{
Durable: "dlc",
AckPolicy: nats.AckExplicitPolicy,
MaxAckPending: 1024,
})
require_NoError(t, err)
// Now do same name but pull. This will update the MaxAcKPending
ci, err := js.UpdateConsumer("X", &nats.ConsumerConfig{
Durable: "dlc",
AckPolicy: nats.AckExplicitPolicy,
MaxAckPending: 22,
})
require_NoError(t, err)
if ci.Config.MaxAckPending != 22 {
t.Fatalf("Expected MaxAckPending to be 22, got %d", ci.Config.MaxAckPending)
}
// Make sure this survives across a restart.
sd := s.JetStreamConfig().StoreDir
s.Shutdown()
// Restart.
s = RunJetStreamServerOnPort(-1, sd)
defer s.Shutdown()
nc, js = jsClientConnect(t, s)
defer nc.Close()
ci, err = js.ConsumerInfo("X", "dlc")
require_NoError(t, err)
if ci.Config.MaxAckPending != 22 {
t.Fatalf("Expected MaxAckPending to be 22, got %d", ci.Config.MaxAckPending)
}
}
func TestJetStreamConsumerPendingCountWithRedeliveries(t *testing.T) {
s := RunBasicJetStreamServer(t)
defer s.Shutdown()
nc, js := jsClientConnect(t, s)
defer nc.Close()
_, err := js.AddStream(&nats.StreamConfig{Name: "TEST", Subjects: []string{"foo"}})
require_NoError(t, err)
_, err = js.AddConsumer("TEST", &nats.ConsumerConfig{
Durable: "test",
AckPolicy: nats.AckExplicitPolicy,
AckWait: 50 * time.Millisecond,
MaxDeliver: 1,
})
require_NoError(t, err)
// Publish 1st message
_, err = js.Publish("foo", []byte("msg1"))
require_NoError(t, err)
sub, err := js.PullSubscribe("foo", "test")
require_NoError(t, err)
msgs, err := sub.Fetch(1)
require_NoError(t, err)
for _, m := range msgs {
require_Equal(t, string(m.Data), "msg1")
// Do not ack the message
}
// Check consumer info, pending should be 0
ci, err := js.ConsumerInfo("TEST", "test")
require_NoError(t, err)
if ci.NumPending != 0 {
t.Fatalf("Expected consumer info pending count to be 0, got %v", ci.NumPending)
}
// Wait for more than expiration
time.Sleep(100 * time.Millisecond)
// Publish 2nd message
_, err = js.Publish("foo", []byte("msg2"))
require_NoError(t, err)
msgs, err = sub.Fetch(1)
require_NoError(t, err)
for _, m := range msgs {
require_Equal(t, string(m.Data), "msg2")
// Its deliver count should be 1
meta, err := m.Metadata()
require_NoError(t, err)
if meta.NumDelivered != 1 {
t.Fatalf("Expected message's deliver count to be 1, got %v", meta.NumDelivered)
}
}
// Check consumer info, pending should be 0
ci, err = js.ConsumerInfo("TEST", "test")
require_NoError(t, err)
if ci.NumPending != 0 {
t.Fatalf("Expected consumer info pending count to be 0, got %v", ci.NumPending)
}
}
func TestJetStreamConsumerPullHeartBeats(t *testing.T) {
s := RunBasicJetStreamServer(t)
defer s.Shutdown()
nc, js := jsClientConnect(t, s)
defer nc.Close()
_, err := js.AddStream(&nats.StreamConfig{Name: "T", Storage: nats.MemoryStorage})
require_NoError(t, err)
_, err = js.AddConsumer("T", &nats.ConsumerConfig{Durable: "dlc", AckPolicy: nats.AckExplicitPolicy})
require_NoError(t, err)
rsubj := fmt.Sprintf(JSApiRequestNextT, "T", "dlc")
type tsMsg struct {
received time.Time
msg *nats.Msg
}
doReq := func(batch int, hb, expires time.Duration, expected int) []*tsMsg {
t.Helper()
req := &JSApiConsumerGetNextRequest{Batch: batch, Expires: expires, Heartbeat: hb}
jreq, err := json.Marshal(req)
require_NoError(t, err)
reply := nats.NewInbox()
var msgs []*tsMsg
var mu sync.Mutex
sub, err := nc.Subscribe(reply, func(m *nats.Msg) {
mu.Lock()
msgs = append(msgs, &tsMsg{time.Now(), m})
mu.Unlock()
})
require_NoError(t, err)
err = nc.PublishRequest(rsubj, reply, jreq)
require_NoError(t, err)
checkFor(t, time.Second, 50*time.Millisecond, func() error {
mu.Lock()
nr := len(msgs)
mu.Unlock()
if nr >= expected {
return nil
}
return fmt.Errorf("Only have seen %d of %d responses", nr, expected)
})
sub.Unsubscribe()
return msgs
}
reqBad := nats.Header{"Status": []string{"400"}, "Description": []string{"Bad Request - heartbeat value too large"}}
expectErr := func(msgs []*tsMsg) {
t.Helper()
if len(msgs) != 1 {
t.Fatalf("Expected 1 msg, got %d", len(msgs))
}
if !reflect.DeepEqual(msgs[0].msg.Header, reqBad) {
t.Fatalf("Expected %+v hdr, got %+v", reqBad, msgs[0].msg.Header)
}
}
// Test errors first.
// Setting HB with no expires.
expectErr(doReq(1, 100*time.Millisecond, 0, 1))
// If HB larger than 50% of expires..
expectErr(doReq(1, 75*time.Millisecond, 100*time.Millisecond, 1))
expectHBs := func(start time.Time, msgs []*tsMsg, expected int, hbi time.Duration) {
t.Helper()
if len(msgs) != expected {
t.Fatalf("Expected %d but got %d", expected, len(msgs))
}
// expected -1 should be all HBs.
for i, ts := 0, start; i < expected-1; i++ {
tr, m := msgs[i].received, msgs[i].msg
if m.Header.Get("Status") != "100" {
t.Fatalf("Expected a 100 status code, got %q", m.Header.Get("Status"))
}
if m.Header.Get("Description") != "Idle Heartbeat" {
t.Fatalf("Wrong description, got %q", m.Header.Get("Description"))
}
ts = ts.Add(hbi)
if tr.Before(ts) {
t.Fatalf("Received at wrong time: %v vs %v", tr, ts)
}
}
// Last msg should be timeout.
lm := msgs[len(msgs)-1].msg
if key := lm.Header.Get("Status"); key != "408" {
t.Fatalf("Expected 408 Request Timeout, got %s", key)
}
}
// These should work. Test idle first.
start, msgs := time.Now(), doReq(1, 50*time.Millisecond, 250*time.Millisecond, 5)
expectHBs(start, msgs, 5, 50*time.Millisecond)
// Now test that we do not send heartbeats while we receive traffic.
go func() {
for i := 0; i < 5; i++ {
time.Sleep(50 * time.Millisecond)
js.Publish("T", nil)
}
}()
msgs = doReq(10, 75*time.Millisecond, 350*time.Millisecond, 6)
// The first 5 should be msgs, no HBs.
for i := 0; i < 5; i++ {
if m := msgs[i].msg; len(m.Header) > 0 {
t.Fatalf("Got a potential heartbeat msg when we should not have: %+v", m.Header)
}
}
// Last should be timeout.
lm := msgs[len(msgs)-1].msg
if key := lm.Header.Get("Status"); key != "408" {
t.Fatalf("Expected 408 Request Timeout, got %s", key)
}
}
func TestJetStreamConsumerAckSampling(t *testing.T) {
s := RunBasicJetStreamServer(t)
defer s.Shutdown()
nc, js := jsClientConnect(t, s)
defer nc.Close()
_, err := js.AddStream(&nats.StreamConfig{Name: "TEST", Subjects: []string{"foo"}})
require_NoError(t, err)
_, err = js.AddConsumer("TEST", &nats.ConsumerConfig{
Durable: "dlc",
AckPolicy: nats.AckExplicitPolicy,
FilterSubject: "foo",
SampleFrequency: "100%",
})
require_NoError(t, err)
sub, err := js.PullSubscribe("foo", "dlc")
require_NoError(t, err)
_, err = js.Publish("foo", []byte("Hello"))
require_NoError(t, err)
msub, err := nc.SubscribeSync("$JS.EVENT.METRIC.>")
require_NoError(t, err)
for _, m := range fetchMsgs(t, sub, 1, time.Second) {
err = m.AckSync()
require_NoError(t, err)
}
m, err := msub.NextMsg(time.Second)
require_NoError(t, err)
var am JSConsumerAckMetric
err = json.Unmarshal(m.Data, &am)
require_NoError(t, err)
if am.Stream != "TEST" || am.Consumer != "dlc" || am.ConsumerSeq != 1 {
t.Fatalf("Not a proper ack metric: %+v", am)
}
// Do less than 100%
_, err = js.AddConsumer("TEST", &nats.ConsumerConfig{
Durable: "alps",
AckPolicy: nats.AckExplicitPolicy,
FilterSubject: "foo",
SampleFrequency: "50%",
})
require_NoError(t, err)
asub, err := js.PullSubscribe("foo", "alps")
require_NoError(t, err)
total := 500
for i := 0; i < total; i++ {
_, err = js.Publish("foo", []byte("Hello"))
require_NoError(t, err)
}
mp := 0
for _, m := range fetchMsgs(t, asub, total, time.Second) {
err = m.AckSync()
require_NoError(t, err)
mp++
}
nc.Flush()
if mp != total {
t.Fatalf("Got only %d msgs out of %d", mp, total)
}
nmsgs, _, err := msub.Pending()
require_NoError(t, err)
// Should be ~250
if nmsgs < 200 || nmsgs > 300 {
t.Fatalf("Expected about 250, got %d", nmsgs)
}
}
func TestJetStreamConsumerAckSamplingSpecifiedUsingUpdateConsumer(t *testing.T) {
s := RunBasicJetStreamServer(t)
defer s.Shutdown()
nc, js := jsClientConnect(t, s)
defer nc.Close()
_, err := js.AddStream(&nats.StreamConfig{Name: "TEST", Subjects: []string{"foo"}})
require_NoError(t, err)
_, err = js.AddConsumer("TEST", &nats.ConsumerConfig{
Durable: "dlc",
AckPolicy: nats.AckExplicitPolicy,
FilterSubject: "foo",
})
require_NoError(t, err)
_, err = js.UpdateConsumer("TEST", &nats.ConsumerConfig{
Durable: "dlc",
AckPolicy: nats.AckExplicitPolicy,
FilterSubject: "foo",
SampleFrequency: "100%",
})
require_NoError(t, err)
sub, err := js.PullSubscribe("foo", "dlc")
require_NoError(t, err)
_, err = js.Publish("foo", []byte("Hello"))
require_NoError(t, err)
msub, err := nc.SubscribeSync("$JS.EVENT.METRIC.>")
require_NoError(t, err)
for _, m := range fetchMsgs(t, sub, 1, time.Second) {
err = m.AckSync()
require_NoError(t, err)
}
m, err := msub.NextMsg(time.Second)
require_NoError(t, err)
var am JSConsumerAckMetric
err = json.Unmarshal(m.Data, &am)
require_NoError(t, err)
if am.Stream != "TEST" || am.Consumer != "dlc" || am.ConsumerSeq != 1 {
t.Fatalf("Not a proper ack metric: %+v", am)
}
}
func TestJetStreamConsumerMaxDeliverUpdate(t *testing.T) {
s := RunBasicJetStreamServer(t)
defer s.Shutdown()
nc, js := jsClientConnect(t, s)
defer nc.Close()
_, err := js.AddStream(&nats.StreamConfig{Name: "TEST", Subjects: []string{"foo"}})
require_NoError(t, err)
maxDeliver := 2
_, err = js.AddConsumer("TEST", &nats.ConsumerConfig{
Durable: "ard",
AckPolicy: nats.AckExplicitPolicy,
FilterSubject: "foo",
MaxDeliver: maxDeliver,
})
require_NoError(t, err)
sub, err := js.PullSubscribe("foo", "ard")
require_NoError(t, err)
checkMaxDeliver := func() {
t.Helper()
for i := 0; i <= maxDeliver; i++ {
msgs, err := sub.Fetch(2, nats.MaxWait(100*time.Millisecond))
if i < maxDeliver {
require_NoError(t, err)
require_Len(t, 1, len(msgs))
_ = msgs[0].Nak()
} else {
require_Error(t, err, nats.ErrTimeout)
}
}
}
_, err = js.Publish("foo", []byte("Hello"))
require_NoError(t, err)
checkMaxDeliver()
// update maxDeliver
maxDeliver++
_, err = js.UpdateConsumer("TEST", &nats.ConsumerConfig{
Durable: "ard",
AckPolicy: nats.AckExplicitPolicy,
FilterSubject: "foo",
MaxDeliver: maxDeliver,
})
require_NoError(t, err)
_, err = js.Publish("foo", []byte("Hello"))
require_NoError(t, err)
checkMaxDeliver()
}
func TestJetStreamConsumerStreamUpdate(t *testing.T) {
test := func(t *testing.T, s *Server, replica int) {
nc := natsConnect(t, s.ClientURL())
defer nc.Close()
js, err := nc.JetStream()
require_NoError(t, err)
_, err = js.AddStream(&nats.StreamConfig{Name: "foo", Duplicates: 1 * time.Minute, Replicas: replica})
defer js.DeleteStream("foo")
require_NoError(t, err)
// Update with no change
_, err = js.UpdateStream(&nats.StreamConfig{Name: "foo", Duplicates: 1 * time.Minute, Replicas: replica})
require_NoError(t, err)
// Update with change
_, err = js.UpdateStream(&nats.StreamConfig{Description: "stream", Name: "foo", Duplicates: 1 * time.Minute, Replicas: replica})
require_NoError(t, err)
_, err = js.AddConsumer("foo", &nats.ConsumerConfig{Durable: "dur1", AckPolicy: nats.AckExplicitPolicy})
require_NoError(t, err)
// Update with no change
_, err = js.UpdateConsumer("foo", &nats.ConsumerConfig{Durable: "dur1", AckPolicy: nats.AckExplicitPolicy})
require_NoError(t, err)
// Update with change
_, err = js.UpdateConsumer("foo", &nats.ConsumerConfig{Description: "consumer", Durable: "dur1", AckPolicy: nats.AckExplicitPolicy})
require_NoError(t, err)
}
t.Run("clustered", func(t *testing.T) {
c := createJetStreamClusterWithTemplate(t, `
listen: 127.0.0.1:-1
server_name: %s
jetstream: {
max_mem_store: 2MB,
max_file_store: 8MB,
store_dir: '%s',
}
cluster {
name: %s
listen: 127.0.0.1:%d
routes = [%s]
}
no_auth_user: u
accounts {
ONE {
users = [ { user: "u", pass: "s3cr3t!" } ]
jetstream: enabled
}
$SYS { users = [ { user: "admin", pass: "s3cr3t!" } ] }
}`, "clust", 3)
defer c.shutdown()
s := c.randomServer()
t.Run("r3", func(t *testing.T) {
test(t, s, 3)
})
t.Run("r1", func(t *testing.T) {
test(t, s, 1)
})
})
t.Run("single", func(t *testing.T) {
storeDir := t.TempDir()
conf := createConfFile(t, []byte(fmt.Sprintf(`
listen: 127.0.0.1:-1
jetstream: {max_mem_store: 2MB, max_file_store: 8MB, store_dir: '%s'}`,
storeDir)))
s, _ := RunServerWithConfig(conf)
defer s.Shutdown()
test(t, s, 1)
})
}
func TestJetStreamConsumerDeliverNewNotConsumingBeforeRestart(t *testing.T) {
s := RunBasicJetStreamServer(t)
defer s.Shutdown()
nc, js := jsClientConnect(t, s)
defer nc.Close()
_, err := js.AddStream(&nats.StreamConfig{
Name: "TEST",
Subjects: []string{"foo"},
})
require_NoError(t, err)
inbox := nats.NewInbox()
_, err = js.AddConsumer("TEST", &nats.ConsumerConfig{
DeliverSubject: inbox,
Durable: "dur",
AckPolicy: nats.AckExplicitPolicy,
DeliverPolicy: nats.DeliverNewPolicy,
FilterSubject: "foo",
})
require_NoError(t, err)
for i := 0; i < 10; i++ {
sendStreamMsg(t, nc, "foo", "msg")
}
checkCount := func(expected int) {
t.Helper()
checkFor(t, 2*time.Second, 15*time.Millisecond, func() error {
ci, err := js.ConsumerInfo("TEST", "dur")
if err != nil {
return err
}
if n := int(ci.NumPending); n != expected {
return fmt.Errorf("Expected %v pending, got %v", expected, n)
}
return nil
})
}
checkCount(10)
time.Sleep(300 * time.Millisecond)
// Check server restart
nc.Close()
sd := s.JetStreamConfig().StoreDir
s.Shutdown()
// Restart.
s = RunJetStreamServerOnPort(-1, sd)
defer s.Shutdown()
nc, js = jsClientConnect(t, s)
defer nc.Close()
checkCount(10)
// Make sure messages can be consumed
sub := natsSubSync(t, nc, inbox)
for i := 0; i < 10; i++ {
msg, err := sub.NextMsg(time.Second)
if err != nil {
t.Fatalf("i=%v next msg error: %v", i, err)
}
msg.AckSync()
}
checkCount(0)
}
func TestJetStreamConsumerNumPendingWithMaxPerSubjectGreaterThanOne(t *testing.T) {
s := RunBasicJetStreamServer(t)
defer s.Shutdown()
nc, js := jsClientConnect(t, s)
defer nc.Close()
test := func(t *testing.T, st nats.StorageType) {
_, err := js.AddStream(&nats.StreamConfig{
Name: "TEST",
Subjects: []string{"KV.*.*"},
MaxMsgsPerSubject: 2,
Storage: st,
})
require_NoError(t, err)
// If we allow more than one msg per subject, consumer's num pending can be off (bug in store level).
// This requires a filtered state, simple states work ok.
// Since we now rely on stream's filtered state when asked directly for consumer info in >=2.8.3.
js.PublishAsync("KV.plans.foo", []byte("OK"))
js.PublishAsync("KV.plans.bar", []byte("OK"))
js.PublishAsync("KV.plans.baz", []byte("OK"))
// These are required, the consumer needs to filter these out to see the bug.
js.PublishAsync("KV.config.foo", []byte("OK"))
js.PublishAsync("KV.config.bar", []byte("OK"))
js.PublishAsync("KV.config.baz", []byte("OK"))
// Double up some now.
js.PublishAsync("KV.plans.bar", []byte("OK"))
js.PublishAsync("KV.plans.baz", []byte("OK"))
ci, err := js.AddConsumer("TEST", &nats.ConsumerConfig{
Durable: "d",
AckPolicy: nats.AckExplicitPolicy,
DeliverPolicy: nats.DeliverLastPerSubjectPolicy,
FilterSubject: "KV.plans.*",
})
require_NoError(t, err)
err = js.DeleteStream("TEST")
require_NoError(t, err)
if ci.NumPending != 3 {
t.Fatalf("Expected 3 NumPending, but got %d", ci.NumPending)
}
}
t.Run("MemoryStore", func(t *testing.T) { test(t, nats.MemoryStorage) })
t.Run("FileStore", func(t *testing.T) { test(t, nats.FileStorage) })
}
func TestJetStreamConsumerAndStreamNamesWithPathSeparators(t *testing.T) {
s := RunBasicJetStreamServer(t)
defer s.Shutdown()
nc, js := jsClientConnect(t, s)
defer nc.Close()
_, err := js.AddStream(&nats.StreamConfig{Name: "usr/bin"})
require_Error(t, err, NewJSStreamNameContainsPathSeparatorsError(), nats.ErrInvalidStreamName)
_, err = js.AddStream(&nats.StreamConfig{Name: `Documents\readme.txt`})
require_Error(t, err, NewJSStreamNameContainsPathSeparatorsError(), nats.ErrInvalidStreamName)
// Now consumers.
_, err = js.AddStream(&nats.StreamConfig{Name: "T"})
require_NoError(t, err)
_, err = js.AddConsumer("T", &nats.ConsumerConfig{Durable: "a/b", AckPolicy: nats.AckExplicitPolicy})
require_Error(t, err, NewJSConsumerNameContainsPathSeparatorsError(), nats.ErrInvalidConsumerName)
_, err = js.AddConsumer("T", &nats.ConsumerConfig{Durable: `a\b`, AckPolicy: nats.AckExplicitPolicy})
require_Error(t, err, NewJSConsumerNameContainsPathSeparatorsError(), nats.ErrInvalidConsumerName)
}
func TestJetStreamConsumerUpdateFilterSubject(t *testing.T) {
s := RunBasicJetStreamServer(t)
defer s.Shutdown()
nc, js := jsClientConnect(t, s)
defer nc.Close()
_, err := js.AddStream(&nats.StreamConfig{Name: "T", Subjects: []string{"foo", "bar", "baz"}})
require_NoError(t, err)
// 10 foo
for i := 0; i < 10; i++ {
js.PublishAsync("foo", []byte("OK"))
}
// 20 bar
for i := 0; i < 20; i++ {
js.PublishAsync("bar", []byte("OK"))
}
select {
case <-js.PublishAsyncComplete():
case <-time.After(5 * time.Second):
t.Fatalf("Did not receive completion signal")
}
sub, err := js.PullSubscribe("foo", "d")
require_NoError(t, err)
// Consume 5 msgs
msgs, err := sub.Fetch(5)
require_NoError(t, err)
require_True(t, len(msgs) == 5)
// Now update to different filter subject.
_, err = js.UpdateConsumer("T", &nats.ConsumerConfig{
Durable: "d",
FilterSubject: "bar",
AckPolicy: nats.AckExplicitPolicy,
})
require_NoError(t, err)
sub, err = js.PullSubscribe("bar", "d")
require_NoError(t, err)
msgs, err = sub.Fetch(1)
require_NoError(t, err)
// Make sure meta and pending etc are all correct.
m := msgs[0]
meta, err := m.Metadata()
require_NoError(t, err)
if meta.Sequence.Consumer != 6 || meta.Sequence.Stream != 11 {
t.Fatalf("Sequence incorrect %+v", meta.Sequence)
}
if meta.NumDelivered != 1 {
t.Fatalf("Expected NumDelivered to be 1, got %d", meta.NumDelivered)
}
if meta.NumPending != 19 {
t.Fatalf("Expected NumPending to be 19, got %d", meta.NumPending)
}
}
// Originally pull consumers were FIFO with respect to the request, not delivery of messages.
// We have changed to have the behavior be FIFO but on an individual message basis.
// So after a message is delivered, the request, if still outstanding, effectively
// goes to the end of the queue of requests pending.
func TestJetStreamConsumerPullConsumerFIFO(t *testing.T) {
s := RunBasicJetStreamServer(t)
defer s.Shutdown()
nc, js := jsClientConnect(t, s)
defer nc.Close()
_, err := js.AddStream(&nats.StreamConfig{Name: "T"})
require_NoError(t, err)
// Create pull consumer.
_, err = js.AddConsumer("T", &nats.ConsumerConfig{Durable: "d", AckPolicy: nats.AckExplicitPolicy})
require_NoError(t, err)
// Simulate 10 pull requests each asking for 10 messages.
var subs []*nats.Subscription
for i := 0; i < 10; i++ {
inbox := nats.NewInbox()
sub := natsSubSync(t, nc, inbox)
subs = append(subs, sub)
req := &JSApiConsumerGetNextRequest{Batch: 10, Expires: 60 * time.Second}
jreq, err := json.Marshal(req)
require_NoError(t, err)
err = nc.PublishRequest(fmt.Sprintf(JSApiRequestNextT, "T", "d"), inbox, jreq)
require_NoError(t, err)
}
// Not all core publishes could have made it to the leader yet, wait for some time.
time.Sleep(100 * time.Millisecond)
// Now send 100 messages.
for i := 0; i < 100; i++ {
_, err = js.Publish("T", []byte("FIFO FTW!"))
require_NoError(t, err)
}
// Wait for messages
for _, sub := range subs {
checkSubsPending(t, sub, 10)
}
// Confirm FIFO
for index, sub := range subs {
for i := 0; i < 10; i++ {
m, err := sub.NextMsg(time.Second)
require_NoError(t, err)
meta, err := m.Metadata()
require_NoError(t, err)
// We expect these to be FIFO per message. E.g. sub #1 = [1, 11, 21, 31, ..]
if sseq := meta.Sequence.Stream; sseq != uint64(index+1+(10*i)) {
t.Fatalf("Expected message #%d for sub #%d to be %d, but got %d", i+1, index+1, index+1+(10*i), sseq)
}
}
}
}
// Make sure that when we reach an ack limit that we follow one shot semantics.
func TestJetStreamConsumerPullConsumerOneShotOnMaxAckLimit(t *testing.T) {
s := RunBasicJetStreamServer(t)
defer s.Shutdown()
nc, js := jsClientConnect(t, s)
defer nc.Close()
_, err := js.AddStream(&nats.StreamConfig{Name: "T"})
require_NoError(t, err)
for i := 0; i < 10; i++ {
js.Publish("T", []byte("OK"))
}
sub, err := js.PullSubscribe("T", "d", nats.MaxAckPending(5))
require_NoError(t, err)
start := time.Now()
msgs, err := sub.Fetch(10, nats.MaxWait(2*time.Second))
require_NoError(t, err)
if elapsed := time.Since(start); elapsed >= 2*time.Second {
t.Fatalf("Took too long, not one shot behavior: %v", elapsed)
}
if len(msgs) != 5 {
t.Fatalf("Expected 5 msgs, got %d", len(msgs))
}
}
func TestJetStreamConsumerDeliverNewMaxRedeliveriesAndServerRestart(t *testing.T) {
s := RunBasicJetStreamServer(t)
defer s.Shutdown()
nc, js := jsClientConnect(t, s)
defer nc.Close()
_, err := js.AddStream(&nats.StreamConfig{
Name: "TEST",
Subjects: []string{"foo.*"},
})
require_NoError(t, err)
inbox := nats.NewInbox()
_, err = js.AddConsumer("TEST", &nats.ConsumerConfig{
DeliverSubject: inbox,
Durable: "dur",
AckPolicy: nats.AckExplicitPolicy,
DeliverPolicy: nats.DeliverNewPolicy,
MaxDeliver: 3,
AckWait: 250 * time.Millisecond,
FilterSubject: "foo.bar",
})
require_NoError(t, err)
sendStreamMsg(t, nc, "foo.bar", "msg")
sub := natsSubSync(t, nc, inbox)
for i := 0; i < 3; i++ {
natsNexMsg(t, sub, time.Second)
}
// Now check that there is no more redeliveries
if msg, err := sub.NextMsg(300 * time.Millisecond); err != nats.ErrTimeout {
t.Fatalf("Expected timeout, got msg=%+v err=%v", msg, err)
}
// Give a chance to things to be persisted
time.Sleep(300 * time.Millisecond)
// Check server restart
nc.Close()
sd := s.JetStreamConfig().StoreDir
s.Shutdown()
s = RunJetStreamServerOnPort(-1, sd)
defer s.Shutdown()
nc, _ = jsClientConnect(t, s)
defer nc.Close()
sub = natsSubSync(t, nc, inbox)
// We should not have messages being redelivered.
if msg, err := sub.NextMsg(300 * time.Millisecond); err != nats.ErrTimeout {
t.Fatalf("Expected timeout, got msg=%+v err=%v", msg, err)
}
}
func TestJetStreamConsumerPendingLowerThanStreamFirstSeq(t *testing.T) {
s := RunBasicJetStreamServer(t)
defer s.Shutdown()
nc, js := jsClientConnect(t, s)
defer nc.Close()
_, err := js.AddStream(&nats.StreamConfig{
Name: "TEST",
Subjects: []string{"foo"},
})
require_NoError(t, err)
for i := 0; i < 10; i++ {
sendStreamMsg(t, nc, "foo", "msg")
}
inbox := nats.NewInbox()
_, err = js.AddConsumer("TEST", &nats.ConsumerConfig{
DeliverSubject: inbox,
Durable: "dur",
AckPolicy: nats.AckExplicitPolicy,
DeliverPolicy: nats.DeliverAllPolicy,
})
require_NoError(t, err)
sub := natsSubSync(t, nc, inbox)
for i := 0; i < 10; i++ {
msg := natsNexMsg(t, sub, time.Second)
require_NoError(t, msg.AckSync())
}
acc, err := s.lookupAccount(globalAccountName)
require_NoError(t, err)
mset, err := acc.lookupStream("TEST")
require_NoError(t, err)
o := mset.lookupConsumer("dur")
require_True(t, o != nil)
o.stop()
mset.store.Compact(1_000_000)
nc.Close()
sd := s.JetStreamConfig().StoreDir
s.Shutdown()
s = RunJetStreamServerOnPort(-1, sd)
defer s.Shutdown()
nc, js = jsClientConnect(t, s)
defer nc.Close()
si, err := js.StreamInfo("TEST")
require_NoError(t, err)
require_True(t, si.State.FirstSeq == 1_000_000)
require_True(t, si.State.LastSeq == 999_999)
acc, err = s.lookupAccount(globalAccountName)
require_NoError(t, err)
mset, err = acc.lookupStream("TEST")
require_NoError(t, err)
o = mset.lookupConsumer("dur")
require_True(t, o != nil)
natsSubSync(t, nc, inbox)
checkFor(t, 2*time.Second, 15*time.Millisecond, func() error {
ci, err := js.ConsumerInfo("TEST", "dur")
if err != nil {
return err
}
if ci.NumAckPending != 0 {
return fmt.Errorf("NumAckPending should be 0, got %v", ci.NumAckPending)
}
// Delivered stays the same because it reflects what was actually delivered.
// And must not be influenced by purges/compacts.
if ci.Delivered.Stream != 10 {
return fmt.Errorf("Delivered.Stream should be 10, got %v", ci.Delivered.Stream)
}
// Starting sequence should be skipped ahead, respecting the compact.
o.mu.RLock()
sseq := o.sseq
o.mu.RUnlock()
if sseq != 1_000_000 {
return fmt.Errorf("o.sseq should be 1,000,000, got %v", sseq)
}
return nil
})
}
// Bug when stream's consumer config does not force filestore to track per subject information.
func TestJetStreamConsumerEOFBugNewFileStore(t *testing.T) {
s := RunBasicJetStreamServer(t)
defer s.Shutdown()
nc, js := jsClientConnect(t, s)
defer nc.Close()
_, err := js.AddStream(&nats.StreamConfig{
Name: "TEST",
Subjects: []string{"foo.bar.*"},
})
require_NoError(t, err)
_, err = js.AddStream(&nats.StreamConfig{
Name: "M",
Mirror: &nats.StreamSource{Name: "TEST"},
})
require_NoError(t, err)
dsubj := nats.NewInbox()
sub, err := nc.SubscribeSync(dsubj)
require_NoError(t, err)
nc.Flush()
// Filter needs to be a wildcard. Need to bind to the
_, err = js.AddConsumer("M", &nats.ConsumerConfig{DeliverSubject: dsubj, FilterSubject: "foo.>"})
require_NoError(t, err)
for i := 0; i < 100; i++ {
_, err := js.PublishAsync("foo.bar.baz", []byte("OK"))
require_NoError(t, err)
}
for i := 0; i < 100; i++ {
m, err := sub.NextMsg(time.Second)
require_NoError(t, err)
m.Respond(nil)
}
// Now force an expiration.
mset, err := s.GlobalAccount().lookupStream("M")
require_NoError(t, err)
mset.mu.RLock()
store := mset.store.(*fileStore)
mset.mu.RUnlock()
store.mu.RLock()
mb := store.blks[0]
store.mu.RUnlock()
mb.mu.Lock()
mb.fss = nil
mb.mu.Unlock()
// Now send another message.
_, err = js.PublishAsync("foo.bar.baz", []byte("OK"))
require_NoError(t, err)
// This will fail with the bug.
_, err = sub.NextMsg(time.Second)
require_NoError(t, err)
}
func TestJetStreamConsumerMultipleSubjectsLast(t *testing.T) {
s := RunBasicJetStreamServer(t)
if config := s.JetStreamConfig(); config != nil {
defer removeDir(t, config.StoreDir)
}
defer s.Shutdown()
durable := "durable"
nc, js := jsClientConnect(t, s)
defer nc.Close()
acc := s.GlobalAccount()
mset, err := acc.addStream(&StreamConfig{
Subjects: []string{"events", "data", "other"},
Name: "name",
})
if err != nil {
t.Fatalf("error while creating stream")
}
sendStreamMsg(t, nc, "events", "1")
sendStreamMsg(t, nc, "data", "2")
sendStreamMsg(t, nc, "other", "3")
sendStreamMsg(t, nc, "events", "4")
sendStreamMsg(t, nc, "data", "5")
sendStreamMsg(t, nc, "data", "6")
sendStreamMsg(t, nc, "other", "7")
sendStreamMsg(t, nc, "other", "8")
// if they're not the same, expect error
_, err = mset.addConsumer(&ConsumerConfig{
DeliverPolicy: DeliverLast,
AckPolicy: AckExplicit,
DeliverSubject: "deliver",
FilterSubjects: []string{"events", "data"},
Durable: durable,
})
require_NoError(t, err)
sub, err := js.SubscribeSync(_EMPTY_, nats.Bind("name", durable))
require_NoError(t, err)
msg, err := sub.NextMsg(time.Millisecond * 500)
require_NoError(t, err)
j, err := strconv.Atoi(string(msg.Data))
require_NoError(t, err)
expectedStreamSeq := 6
if j != expectedStreamSeq {
t.Fatalf("wrong sequence, expected %v got %v", expectedStreamSeq, j)
}
require_NoError(t, msg.AckSync())
// check if we don't get more than we wanted
msg, err = sub.NextMsg(time.Millisecond * 500)
if msg != nil || err == nil {
t.Fatalf("should not get more messages")
}
info, err := js.ConsumerInfo("name", durable)
require_NoError(t, err)
require_Equal(t, info.NumAckPending, 0)
require_Equal(t, info.AckFloor.Stream, 6)
require_Equal(t, info.AckFloor.Consumer, 1)
require_Equal(t, info.NumPending, 0)
}
func TestJetStreamConsumerMultipleSubjectsLastPerSubject(t *testing.T) {
s := RunBasicJetStreamServer(t)
if config := s.JetStreamConfig(); config != nil {
defer removeDir(t, config.StoreDir)
}
defer s.Shutdown()
durable := "durable"
nc, js := jsClientConnect(t, s)
defer nc.Close()
acc := s.GlobalAccount()
mset, err := acc.addStream(&StreamConfig{
Subjects: []string{"events.*", "data.>", "other"},
Name: "name",
})
if err != nil {
t.Fatalf("error while creating stream")
}
sendStreamMsg(t, nc, "events.1", "bad")
sendStreamMsg(t, nc, "events.1", "events.1")
sendStreamMsg(t, nc, "data.1", "bad")
sendStreamMsg(t, nc, "data.1", "bad")
sendStreamMsg(t, nc, "data.1", "bad")
sendStreamMsg(t, nc, "data.1", "bad")
sendStreamMsg(t, nc, "data.1", "data.1")
sendStreamMsg(t, nc, "events.2", "bad")
sendStreamMsg(t, nc, "events.2", "bad")
// this is last proper sequence,
sendStreamMsg(t, nc, "events.2", "events.2")
sendStreamMsg(t, nc, "other", "bad")
sendStreamMsg(t, nc, "other", "bad")
// if they're not the same, expect error
_, err = mset.addConsumer(&ConsumerConfig{
DeliverPolicy: DeliverLastPerSubject,
AckPolicy: AckExplicit,
DeliverSubject: "deliver",
FilterSubjects: []string{"events.*", "data.>"},
Durable: durable,
})
require_NoError(t, err)
sub, err := js.SubscribeSync("", nats.Bind("name", durable))
require_NoError(t, err)
checkMessage := func(t *testing.T, subject string, payload string, ack bool) {
msg, err := sub.NextMsg(time.Millisecond * 500)
require_NoError(t, err)
if string(msg.Data) != payload {
t.Fatalf("expected %v paylaod, got %v", payload, string(msg.Data))
}
if subject != msg.Subject {
t.Fatalf("expected %v subject, got %v", subject, msg.Subject)
}
if ack {
msg.AckSync()
}
}
checkMessage(t, "events.1", "events.1", true)
checkMessage(t, "data.1", "data.1", true)
checkMessage(t, "events.2", "events.2", false)
info, err := js.ConsumerInfo("name", durable)
require_NoError(t, err)
require_Equal(t, info.AckFloor.Consumer, 2)
require_Equal(t, info.AckFloor.Stream, 9)
require_Equal(t, info.Delivered.Stream, 10)
require_Equal(t, info.Delivered.Consumer, 3)
require_NoError(t, err)
}
func TestJetStreamConsumerMultipleSubjects(t *testing.T) {
s := RunBasicJetStreamServer(t)
if config := s.JetStreamConfig(); config != nil {
defer removeDir(t, config.StoreDir)
}
defer s.Shutdown()
durable := "durable"
nc, js := jsClientConnect(t, s)
defer nc.Close()
mset, err := s.GlobalAccount().addStream(&StreamConfig{
Subjects: []string{"events.>", "data.>"},
Name: "name",
})
require_NoError(t, err)
for i := 0; i < 20; i += 2 {
sendStreamMsg(t, nc, "events.created", fmt.Sprintf("created %v", i))
sendStreamMsg(t, nc, "data.processed", fmt.Sprintf("processed %v", i+1))
}
_, err = mset.addConsumer(&ConsumerConfig{
Durable: durable,
DeliverSubject: "deliver",
FilterSubjects: []string{"events.created", "data.processed"},
AckPolicy: AckExplicit,
})
require_NoError(t, err)
sub, err := js.SubscribeSync("", nats.Bind("name", durable))
require_NoError(t, err)
for i := 0; i < 20; i++ {
msg, err := sub.NextMsg(time.Millisecond * 500)
require_NoError(t, err)
require_NoError(t, msg.AckSync())
}
info, err := js.ConsumerInfo("name", durable)
require_NoError(t, err)
require_True(t, info.NumAckPending == 0)
require_True(t, info.NumPending == 0)
require_True(t, info.AckFloor.Consumer == 20)
require_True(t, info.AckFloor.Stream == 20)
}
func TestJetStreamConsumerMultipleSubjectsWithEmpty(t *testing.T) {
s := RunBasicJetStreamServer(t)
if config := s.JetStreamConfig(); config != nil {
defer removeDir(t, config.StoreDir)
}
defer s.Shutdown()
durable := "durable"
nc, js := jsClientConnect(t, s)
defer nc.Close()
_, err := js.AddStream(&nats.StreamConfig{
Subjects: []string{"events.>"},
Name: "name",
})
require_NoError(t, err)
for i := 0; i < 10; i++ {
sendStreamMsg(t, nc, "events.created", fmt.Sprintf("%v", i))
}
// if they're not the same, expect error
_, err = js.AddConsumer("name", &nats.ConsumerConfig{
DeliverSubject: "deliver",
FilterSubject: "",
Durable: durable,
AckPolicy: nats.AckExplicitPolicy})
require_NoError(t, err)
sub, err := js.SubscribeSync("", nats.Bind("name", durable))
require_NoError(t, err)
for i := 0; i < 9; i++ {
msg, err := sub.NextMsg(time.Millisecond * 500)
require_NoError(t, err)
j, err := strconv.Atoi(string(msg.Data))
require_NoError(t, err)
if j != i {
t.Fatalf("wrong sequence, expected %v got %v", i, j)
}
require_NoError(t, msg.AckSync())
}
info, err := js.ConsumerInfo("name", durable)
require_NoError(t, err)
require_True(t, info.Delivered.Stream == 10)
require_True(t, info.Delivered.Consumer == 10)
require_True(t, info.AckFloor.Stream == 9)
require_True(t, info.AckFloor.Consumer == 9)
require_True(t, info.NumAckPending == 1)
resp := createConsumer(t, nc, "name", ConsumerConfig{
FilterSubjects: []string{""},
DeliverSubject: "multiple",
Durable: "multiple",
AckPolicy: AckExplicit,
})
require_True(t, resp.Error.ErrCode == 10139)
}
func TestJetStreamConsumerOverlappingSubjects(t *testing.T) {
s := RunBasicJetStreamServer(t)
if config := s.JetStreamConfig(); config != nil {
defer removeDir(t, config.StoreDir)
}
defer s.Shutdown()
nc, _ := jsClientConnect(t, s)
defer nc.Close()
acc := s.GlobalAccount()
_, err := acc.addStream(&StreamConfig{
Subjects: []string{"events.>"},
Name: "deliver",
})
require_NoError(t, err)
resp := createConsumer(t, nc, "deliver", ConsumerConfig{
FilterSubjects: []string{"events.one", "events.*"},
Durable: "name",
})
if resp.Error.ErrCode != 10138 {
t.Fatalf("this should error as we have overlapping subjects, got %+v", resp.Error)
}
}
func TestJetStreamConsumerMultipleSubjectsAck(t *testing.T) {
s := RunBasicJetStreamServer(t)
if config := s.JetStreamConfig(); config != nil {
defer removeDir(t, config.StoreDir)
}
defer s.Shutdown()
nc, js := jsClientConnect(t, s)
defer nc.Close()
acc := s.GlobalAccount()
mset, err := acc.addStream(&StreamConfig{
Subjects: []string{"events", "data", "other"},
Name: "deliver",
})
require_NoError(t, err)
_, err = mset.addConsumer(&ConsumerConfig{
FilterSubjects: []string{"events", "data"},
Durable: "name",
AckPolicy: AckExplicit,
Replicas: 1,
})
require_NoError(t, err)
sendStreamMsg(t, nc, "events", "1")
sendStreamMsg(t, nc, "data", "2")
sendStreamMsg(t, nc, "data", "3")
sendStreamMsg(t, nc, "data", "4")
sendStreamMsg(t, nc, "events", "5")
sendStreamMsg(t, nc, "data", "6")
sendStreamMsg(t, nc, "data", "7")
consumer, err := js.PullSubscribe("", "name", nats.Bind("deliver", "name"))
require_NoError(t, err)
msg, err := consumer.Fetch(3)
require_NoError(t, err)
require_Len(t, len(msg), 3)
require_NoError(t, msg[0].AckSync())
require_NoError(t, msg[1].AckSync())
// Due to consumer signaling it might not immediately be reflected.
checkFor(t, time.Second, 100*time.Millisecond, func() error {
info, err := js.ConsumerInfo("deliver", "name")
if err != nil {
return err
}
if info.AckFloor.Consumer != 2 {
return fmt.Errorf("bad consumer sequence. expected %v, got %v", 2, info.AckFloor.Consumer)
}
if info.AckFloor.Stream != 2 {
return fmt.Errorf("bad stream sequence. expected %v, got %v", 2, info.AckFloor.Stream)
}
if info.NumPending != 4 {
return fmt.Errorf("bad num pending. Expected %v, got %v", 4, info.NumPending)
}
return nil
})
}
func TestJetStreamConsumerMultipleSubjectAndNewAPI(t *testing.T) {
s := RunBasicJetStreamServer(t)
if config := s.JetStreamConfig(); config != nil {
defer removeDir(t, config.StoreDir)
}
defer s.Shutdown()
nc, _ := jsClientConnect(t, s)
defer nc.Close()
acc := s.GlobalAccount()
_, err := acc.addStream(&StreamConfig{
Subjects: []string{"data", "events"},
Name: "deliver",
})
if err != nil {
t.Fatalf("error while creating stream")
}
req, err := json.Marshal(&CreateConsumerRequest{Stream: "deliver", Config: ConsumerConfig{
FilterSubjects: []string{"events", "data"},
Name: "name",
Durable: "name",
}})
require_NoError(t, err)
resp, err := nc.Request(fmt.Sprintf("$JS.API.CONSUMER.CREATE.%s.%s.%s", "deliver", "name", "data.>"), req, time.Second*10)
var apiResp JSApiConsumerCreateResponse
json.Unmarshal(resp.Data, &apiResp)
require_NoError(t, err)
if apiResp.Error.ErrCode != 10137 {
t.Fatal("this should error as multiple subject filters is incompatible with new API and didn't")
}
}
func TestJetStreamConsumerMultipleSubjectsWithAddedMessages(t *testing.T) {
s := RunBasicJetStreamServer(t)
if config := s.JetStreamConfig(); config != nil {
defer removeDir(t, config.StoreDir)
}
defer s.Shutdown()
durable := "durable"
nc, js := jsClientConnect(t, s)
defer nc.Close()
acc := s.GlobalAccount()
mset, err := acc.addStream(&StreamConfig{
Subjects: []string{"events.>"},
Name: "deliver",
})
require_NoError(t, err)
// if they're not the same, expect error
_, err = mset.addConsumer(&ConsumerConfig{
DeliverSubject: "deliver",
FilterSubjects: []string{"events.created", "events.processed"},
Durable: durable,
AckPolicy: AckExplicit,
})
require_NoError(t, err)
sendStreamMsg(t, nc, "events.created", "0")
sendStreamMsg(t, nc, "events.created", "1")
sendStreamMsg(t, nc, "events.created", "2")
sendStreamMsg(t, nc, "events.created", "3")
sendStreamMsg(t, nc, "events.other", "BAD")
sendStreamMsg(t, nc, "events.processed", "4")
sendStreamMsg(t, nc, "events.processed", "5")
sendStreamMsg(t, nc, "events.processed", "6")
sendStreamMsg(t, nc, "events.other", "BAD")
sendStreamMsg(t, nc, "events.processed", "7")
sendStreamMsg(t, nc, "events.processed", "8")
sub, err := js.SubscribeSync("", nats.Bind("deliver", durable))
if err != nil {
t.Fatalf("error while subscribing to Consumer: %v", err)
}
for i := 0; i < 10; i++ {
if i == 5 {
sendStreamMsg(t, nc, "events.created", "9")
}
if i == 9 {
sendStreamMsg(t, nc, "events.other", "BAD")
sendStreamMsg(t, nc, "events.created", "11")
}
if i == 7 {
sendStreamMsg(t, nc, "events.processed", "10")
}
msg, err := sub.NextMsg(time.Second * 1)
require_NoError(t, err)
j, err := strconv.Atoi(string(msg.Data))
require_NoError(t, err)
if j != i {
t.Fatalf("wrong sequence, expected %v got %v", i, j)
}
if err := msg.AckSync(); err != nil {
t.Fatalf("error while acking the message :%v", err)
}
}
info, err := js.ConsumerInfo("deliver", durable)
require_NoError(t, err)
require_True(t, info.Delivered.Consumer == 12)
require_True(t, info.Delivered.Stream == 15)
require_True(t, info.AckFloor.Stream == 12)
require_True(t, info.AckFloor.Consumer == 10)
}
func TestJetStreamConsumerThreeFilters(t *testing.T) {
s := RunBasicJetStreamServer(t)
defer s.Shutdown()
nc, js := jsClientConnect(t, s)
defer nc.Close()
mset, err := s.GlobalAccount().addStream(&StreamConfig{
Name: "TEST",
Subjects: []string{"events", "data", "other", "ignored"},
})
require_NoError(t, err)
sendStreamMsg(t, nc, "ignored", "100")
sendStreamMsg(t, nc, "events", "0")
sendStreamMsg(t, nc, "events", "1")
sendStreamMsg(t, nc, "data", "2")
sendStreamMsg(t, nc, "ignored", "100")
sendStreamMsg(t, nc, "data", "3")
sendStreamMsg(t, nc, "other", "4")
sendStreamMsg(t, nc, "data", "5")
sendStreamMsg(t, nc, "other", "6")
sendStreamMsg(t, nc, "data", "7")
sendStreamMsg(t, nc, "ignored", "100")
mset.addConsumer(&ConsumerConfig{
FilterSubjects: []string{"events", "data", "other"},
Durable: "multi",
AckPolicy: AckExplicit,
})
consumer, err := js.PullSubscribe("", "multi", nats.Bind("TEST", "multi"))
require_NoError(t, err)
msgs, err := consumer.Fetch(6)
require_NoError(t, err)
for i, msg := range msgs {
require_Equal(t, string(msg.Data), fmt.Sprintf("%d", i))
require_NoError(t, msg.AckSync())
}
info, err := js.ConsumerInfo("TEST", "multi")
require_NoError(t, err)
require_True(t, info.Delivered.Stream == 8)
require_True(t, info.Delivered.Consumer == 6)
require_True(t, info.NumPending == 2)
require_True(t, info.NumAckPending == 0)
require_True(t, info.AckFloor.Consumer == 6)
require_True(t, info.AckFloor.Stream == 8)
}
func TestJetStreamConsumerUpdateFilterSubjects(t *testing.T) {
s := RunBasicJetStreamServer(t)
defer s.Shutdown()
nc, js := jsClientConnect(t, s)
defer nc.Close()
mset, err := s.GlobalAccount().addStream(&StreamConfig{
Name: "TEST",
Subjects: []string{"events", "data", "other"},
})
require_NoError(t, err)
sendStreamMsg(t, nc, "other", "100")
sendStreamMsg(t, nc, "events", "0")
sendStreamMsg(t, nc, "events", "1")
sendStreamMsg(t, nc, "data", "2")
sendStreamMsg(t, nc, "data", "3")
sendStreamMsg(t, nc, "other", "4")
sendStreamMsg(t, nc, "data", "5")
_, err = mset.addConsumer(&ConsumerConfig{
FilterSubjects: []string{"events", "data"},
Durable: "multi",
AckPolicy: AckExplicit,
})
require_NoError(t, err)
consumer, err := js.PullSubscribe("", "multi", nats.Bind("TEST", "multi"))
require_NoError(t, err)
msgs, err := consumer.Fetch(3)
require_NoError(t, err)
for i, msg := range msgs {
require_Equal(t, string(msg.Data), fmt.Sprintf("%d", i))
require_NoError(t, msg.AckSync())
}
_, err = mset.addConsumer(&ConsumerConfig{
FilterSubjects: []string{"events", "data", "other"},
Durable: "multi",
AckPolicy: AckExplicit,
})
require_NoError(t, err)
updatedConsumer, err := js.PullSubscribe("", "multi", nats.Bind("TEST", "multi"))
require_NoError(t, err)
msgs, err = updatedConsumer.Fetch(3)
require_NoError(t, err)
for i, msg := range msgs {
require_Equal(t, string(msg.Data), fmt.Sprintf("%d", i+3))
require_NoError(t, msg.AckSync())
}
}
func TestJetStreamConsumerAndStreamMetadata(t *testing.T) {
s := RunBasicJetStreamServer(t)
defer s.Shutdown()
metadata := map[string]string{"key": "value", "_nats_created_version": "2.9.11"}
acc := s.GlobalAccount()
// Check stream's first.
mset, err := acc.addStream(&StreamConfig{Name: "foo", Metadata: metadata})
if err != nil {
t.Fatalf("Unexpected error adding stream: %v", err)
}
if cfg := mset.config(); !reflect.DeepEqual(metadata, cfg.Metadata) {
t.Fatalf("Expected a metadata of %q, got %q", metadata, cfg.Metadata)
}
// Now consumer
o, err := mset.addConsumer(&ConsumerConfig{
Metadata: metadata,
DeliverSubject: "to",
AckPolicy: AckNone})
if err != nil {
t.Fatalf("Unexpected error adding consumer: %v", err)
}
if cfg := o.config(); !reflect.DeepEqual(metadata, cfg.Metadata) {
t.Fatalf("Expected a metadata of %q, got %q", metadata, cfg.Metadata)
}
// Test max.
data := make([]byte, JSMaxMetadataLen/100)
crand.Read(data)
bigValue := base64.StdEncoding.EncodeToString(data)
bigMetadata := make(map[string]string, 101)
for i := 0; i < 101; i++ {
bigMetadata[fmt.Sprintf("key%d", i)] = bigValue
}
_, err = acc.addStream(&StreamConfig{Name: "bar", Metadata: bigMetadata})
if err == nil || !strings.Contains(err.Error(), "stream metadata exceeds") {
t.Fatalf("Expected an error but got none")
}
_, err = mset.addConsumer(&ConsumerConfig{
Metadata: bigMetadata,
DeliverSubject: "to",
AckPolicy: AckNone})
if err == nil || !strings.Contains(err.Error(), "consumer metadata exceeds") {
t.Fatalf("Expected an error but got none")
}
}
func TestJetStreamConsumerPurge(t *testing.T) {
s := RunBasicJetStreamServer(t)
defer s.Shutdown()
nc, js := jsClientConnect(t, s)
defer nc.Close()
_, err := js.AddStream(&nats.StreamConfig{
Name: "TEST",
Subjects: []string{"test.>"},
})
require_NoError(t, err)
sendStreamMsg(t, nc, "test.1", "hello")
sendStreamMsg(t, nc, "test.2", "hello")
sub, err := js.PullSubscribe("test.>", "consumer")
require_NoError(t, err)
// Purge one of the subjects.
err = js.PurgeStream("TEST", &nats.StreamPurgeRequest{Subject: "test.2"})
require_NoError(t, err)
info, err := js.ConsumerInfo("TEST", "consumer")
require_NoError(t, err)
require_True(t, info.NumPending == 1)
// Expect to get message from not purged subject.
_, err = sub.Fetch(1)
require_NoError(t, err)
_, err = js.AddStream(&nats.StreamConfig{
Name: "OTHER",
Subjects: []string{"other.>"},
})
require_NoError(t, err)
// Publish two items to two subjects.
sendStreamMsg(t, nc, "other.1", "hello")
sendStreamMsg(t, nc, "other.2", "hello")
sub, err = js.PullSubscribe("other.>", "other_consumer")
require_NoError(t, err)
// Purge whole stream.
err = js.PurgeStream("OTHER", &nats.StreamPurgeRequest{})
require_NoError(t, err)
info, err = js.ConsumerInfo("OTHER", "other_consumer")
require_NoError(t, err)
require_True(t, info.NumPending == 0)
// This time expect error, as we purged whole stream,
_, err = sub.Fetch(1)
require_Error(t, err)
}
func TestJetStreamConsumerFilterUpdate(t *testing.T) {
s := RunBasicJetStreamServer(t)
defer s.Shutdown()
nc, js := jsClientConnect(t, s)
defer nc.Close()
_, err := js.AddStream(&nats.StreamConfig{
Name: "TEST",
Subjects: []string{"foo.>", "bar.>"},
})
require_NoError(t, err)
for i := 0; i < 3; i++ {
sendStreamMsg(t, nc, "foo.data", "OK")
}
sub, err := nc.SubscribeSync("deliver")
require_NoError(t, err)
js.AddConsumer("TEST", &nats.ConsumerConfig{
Durable: "consumer",
DeliverSubject: "deliver",
FilterSubject: "foo.data",
})
_, err = sub.NextMsg(time.Second * 1)
require_NoError(t, err)
_, err = sub.NextMsg(time.Second * 1)
require_NoError(t, err)
_, err = sub.NextMsg(time.Second * 1)
require_NoError(t, err)
_, err = js.UpdateConsumer("TEST", &nats.ConsumerConfig{
Durable: "consumer",
DeliverSubject: "deliver",
FilterSubject: "foo.>",
})
require_NoError(t, err)
sendStreamMsg(t, nc, "foo.other", "data")
// This will timeout if filters were not properly updated.
_, err = sub.NextMsg(time.Second * 1)
require_NoError(t, err)
mset, err := s.GlobalAccount().lookupStream("TEST")
require_NoError(t, err)
checkNumFilter := func(expected int) {
t.Helper()
mset.mu.RLock()
nf := mset.numFilter
mset.mu.RUnlock()
if nf != expected {
t.Fatalf("Expected stream's numFilter to be %d, got %d", expected, nf)
}
}
checkNumFilter(1)
// Update consumer once again, now not having any filters
_, err = js.UpdateConsumer("TEST", &nats.ConsumerConfig{
Durable: "consumer",
DeliverSubject: "deliver",
FilterSubject: _EMPTY_,
})
require_NoError(t, err)
// and expect that numFilter reports correctly.
checkNumFilter(0)
}
func TestJetStreamConsumerAckFloorWithExpired(t *testing.T) {
s := RunBasicJetStreamServer(t)
defer s.Shutdown()
nc, js := jsClientConnect(t, s)
defer nc.Close()
_, err := js.AddStream(&nats.StreamConfig{
Name: "TEST",
Subjects: []string{"foo"},
MaxAge: 2 * time.Second,
})
require_NoError(t, err)
nmsgs := 100
for i := 0; i < nmsgs; i++ {
sendStreamMsg(t, nc, "foo", "OK")
}
sub, err := js.PullSubscribe("foo", "dlc", nats.AckWait(time.Second))
require_NoError(t, err)
// Queue up all for ack pending.
msgs, err := sub.Fetch(nmsgs)
require_NoError(t, err)
require_True(t, len(msgs) == nmsgs)
// Let all messages expire.
time.Sleep(3 * time.Second)
si, err := js.StreamInfo("TEST")
require_NoError(t, err)
require_True(t, si.State.Msgs == 0)
ci, err := js.ConsumerInfo("TEST", "dlc")
require_NoError(t, err)
require_True(t, ci.Delivered.Consumer == uint64(nmsgs))
require_True(t, ci.Delivered.Stream == uint64(nmsgs))
require_True(t, ci.AckFloor.Consumer == uint64(nmsgs))
require_True(t, ci.AckFloor.Stream == uint64(nmsgs))
require_True(t, ci.NumAckPending == 0)
require_True(t, ci.NumPending == 0)
require_True(t, ci.NumRedelivered == 0)
}
func TestJetStreamConsumerIsFiltered(t *testing.T) {
s := RunBasicJetStreamServer(t)
defer s.Shutdown()
acc := s.GlobalAccount()
tests := []struct {
name string
streamSubjects []string
filters []string
isFiltered bool
}{
{
name: "single_subject",
streamSubjects: []string{"one"},
filters: []string{"one"},
isFiltered: false,
},
{
name: "single_subject_filtered",
streamSubjects: []string{"one.>"},
filters: []string{"one.filter"},
isFiltered: true,
},
{
name: "multi_subject_non_filtered",
streamSubjects: []string{"multi", "foo", "bar.>"},
filters: []string{"multi", "bar.>", "foo"},
isFiltered: false,
},
{
name: "multi_subject_filtered_wc",
streamSubjects: []string{"events", "data"},
filters: []string{"data"},
isFiltered: true,
},
{
name: "multi_subject_filtered",
streamSubjects: []string{"machines", "floors"},
filters: []string{"machines"},
isFiltered: true,
},
}
for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
mset, err := acc.addStream(&StreamConfig{
Name: test.name,
Subjects: test.streamSubjects,
})
require_NoError(t, err)
o, err := mset.addConsumer(&ConsumerConfig{
FilterSubjects: test.filters,
Durable: test.name,
})
require_NoError(t, err)
require_True(t, o.isFiltered() == test.isFiltered)
})
}
}
func TestJetStreamConsumerWithFormattingSymbol(t *testing.T) {
s := RunBasicJetStreamServer(t)
defer s.Shutdown()
nc, js := jsClientConnect(t, s)
defer nc.Close()
_, err := js.AddStream(&nats.StreamConfig{
Name: "Test%123",
Subjects: []string{"foo"},
})
require_NoError(t, err)
for i := 0; i < 10; i++ {
sendStreamMsg(t, nc, "foo", "OK")
}
_, err = js.AddConsumer("Test%123", &nats.ConsumerConfig{
Durable: "Test%123",
FilterSubject: "foo",
DeliverSubject: "bar",
})
require_NoError(t, err)
sub, err := js.SubscribeSync("foo", nats.Bind("Test%123", "Test%123"))
require_NoError(t, err)
_, err = sub.NextMsg(time.Second * 5)
require_NoError(t, err)
}
func TestJetStreamConsumerDefaultsFromStream(t *testing.T) {
s := RunBasicJetStreamServer(t)
defer s.Shutdown()
acc := s.GlobalAccount()
if _, err := acc.addStream(&StreamConfig{
Name: "test",
Subjects: []string{"test.*"},
ConsumerLimits: StreamConsumerLimits{
MaxAckPending: 15,
InactiveThreshold: time.Second,
},
}); err != nil {
t.Fatalf("Failed to add stream: %v", err)
}
nc := clientConnectToServer(t, s)
defer nc.Close()
js, err := nc.JetStream()
if err != nil {
t.Fatalf("Failed to connect to JetStream: %v", err)
}
t.Run("InheritDefaultsFromStream", func(t *testing.T) {
ci, err := js.AddConsumer("test", &nats.ConsumerConfig{
Name: "InheritDefaultsFromStream",
AckPolicy: nats.AckExplicitPolicy,
})
require_NoError(t, err)
switch {
case ci.Config.InactiveThreshold != time.Second:
t.Fatalf("InactiveThreshold should be 1s, got %s", ci.Config.InactiveThreshold)
case ci.Config.MaxAckPending != 15:
t.Fatalf("MaxAckPending should be 15, got %d", ci.Config.MaxAckPending)
}
})
t.Run("CreateConsumerErrorOnExceedMaxAckPending", func(t *testing.T) {
_, err := js.AddConsumer("test", &nats.ConsumerConfig{
Name: "CreateConsumerErrorOnExceedMaxAckPending",
MaxAckPending: 30,
})
switch e := err.(type) {
case *nats.APIError:
if ErrorIdentifier(e.ErrorCode) != JSConsumerMaxPendingAckExcessErrF {
t.Fatalf("invalid error code, got %d, wanted %d", e.ErrorCode, JSConsumerMaxPendingAckExcessErrF)
}
default:
t.Fatalf("should have returned API error, got %T", e)
}
})
t.Run("CreateConsumerErrorOnExceedInactiveThreshold", func(t *testing.T) {
_, err := js.AddConsumer("test", &nats.ConsumerConfig{
Name: "CreateConsumerErrorOnExceedInactiveThreshold",
InactiveThreshold: time.Second * 2,
})
switch e := err.(type) {
case *nats.APIError:
if ErrorIdentifier(e.ErrorCode) != JSConsumerInactiveThresholdExcess {
t.Fatalf("invalid error code, got %d, wanted %d", e.ErrorCode, JSConsumerInactiveThresholdExcess)
}
default:
t.Fatalf("should have returned API error, got %T", e)
}
})
t.Run("UpdateStreamErrorOnViolateConsumerMaxAckPending", func(t *testing.T) {
_, err := js.AddConsumer("test", &nats.ConsumerConfig{
Name: "UpdateStreamErrorOnViolateConsumerMaxAckPending",
MaxAckPending: 15,
})
require_NoError(t, err)
stream, err := acc.lookupStream("test")
require_NoError(t, err)
err = stream.update(&StreamConfig{
Name: "test",
Subjects: []string{"test.*"},
ConsumerLimits: StreamConsumerLimits{
MaxAckPending: 10,
},
})
if err == nil {
t.Fatalf("stream update should have errored but didn't")
}
})
t.Run("UpdateStreamErrorOnViolateConsumerInactiveThreshold", func(t *testing.T) {
_, err := js.AddConsumer("test", &nats.ConsumerConfig{
Name: "UpdateStreamErrorOnViolateConsumerInactiveThreshold",
InactiveThreshold: time.Second,
})
require_NoError(t, err)
stream, err := acc.lookupStream("test")
require_NoError(t, err)
err = stream.update(&StreamConfig{
Name: "test",
Subjects: []string{"test.*"},
ConsumerLimits: StreamConsumerLimits{
InactiveThreshold: time.Second / 2,
},
})
if err == nil {
t.Fatalf("stream update should have errored but didn't")
}
})
}
// Server issue 4685
func TestJetStreamConsumerPendingForKV(t *testing.T) {
for _, st := range []nats.StorageType{nats.FileStorage, nats.MemoryStorage} {
t.Run(st.String(), func(t *testing.T) {
s := RunBasicJetStreamServer(t)
defer s.Shutdown()
nc, js := jsClientConnect(t, s)
defer nc.Close()
_, err := js.AddStream(&nats.StreamConfig{
Name: "TEST",
Subjects: []string{"test.>"},
Storage: st,
MaxMsgsPerSubject: 10,
Discard: nats.DiscardNew,
})
require_NoError(t, err)
_, err = js.Publish("test.1", []byte("x"))
require_NoError(t, err)
var msg *nats.Msg
// this is the detail that triggers the off by one, remove this code and all tests pass
msg = nats.NewMsg("test.1")
msg.Data = []byte("y")
msg.Header.Add(nats.ExpectedLastSeqHdr, "1")
_, err = js.PublishMsg(msg)
require_NoError(t, err)
_, err = js.Publish("test.2", []byte("x"))
require_NoError(t, err)
_, err = js.Publish("test.3", []byte("x"))
require_NoError(t, err)
_, err = js.Publish("test.4", []byte("x"))
require_NoError(t, err)
_, err = js.Publish("test.5", []byte("x"))
require_NoError(t, err)
nfo, err := js.StreamInfo("TEST", &nats.StreamInfoRequest{SubjectsFilter: ">"})
require_NoError(t, err)
require_Equal(t, len(nfo.State.Subjects), 5)
sub, err := js.SubscribeSync("test.>", nats.DeliverLastPerSubject())
require_NoError(t, err)
msg, err = sub.NextMsg(time.Second)
require_NoError(t, err)
meta, err := msg.Metadata()
require_NoError(t, err)
require_Equal(t, meta.NumPending, 4)
})
}
}
func TestJetStreamConsumerNakThenAckFloorMove(t *testing.T) {
s := RunBasicJetStreamServer(t)
defer s.Shutdown()
nc, js := jsClientConnect(t, s)
defer nc.Close()
_, err := js.AddStream(&nats.StreamConfig{
Name: "TEST",
Subjects: []string{"foo"},
})
require_NoError(t, err)
for i := 0; i < 11; i++ {
js.Publish("foo", []byte("OK"))
}
sub, err := js.PullSubscribe("foo", "dlc", nats.AckWait(100*time.Millisecond))
require_NoError(t, err)
msgs, err := sub.Fetch(11)
require_NoError(t, err)
// Nak first one
msgs[0].Nak()
// Ack 2-10
for i := 1; i < 10; i++ {
msgs[i].AckSync()
}
// Hold onto last.
lastMsg := msgs[10]
ci, err := sub.ConsumerInfo()
require_NoError(t, err)
require_Equal(t, ci.AckFloor.Consumer, 0)
require_Equal(t, ci.AckFloor.Stream, 0)
require_Equal(t, ci.NumAckPending, 2)
// Grab first messsage again and ack this time.
msgs, err = sub.Fetch(1)
require_NoError(t, err)
msgs[0].AckSync()
ci, err = sub.ConsumerInfo()
require_NoError(t, err)
require_Equal(t, ci.Delivered.Consumer, 12)
require_Equal(t, ci.Delivered.Stream, 11)
require_Equal(t, ci.AckFloor.Consumer, 10)
require_Equal(t, ci.AckFloor.Stream, 10)
require_Equal(t, ci.NumAckPending, 1)
// Make sure when we ack last one we collapse the AckFloor.Consumer
// with the higher delivered due to re-deliveries.
lastMsg.AckSync()
ci, err = sub.ConsumerInfo()
require_NoError(t, err)
require_Equal(t, ci.Delivered.Consumer, 12)
require_Equal(t, ci.Delivered.Stream, 11)
require_Equal(t, ci.AckFloor.Consumer, 12)
require_Equal(t, ci.AckFloor.Stream, 11)
require_Equal(t, ci.NumAckPending, 0)
}
func TestJetStreamConsumerPauseViaConfig(t *testing.T) {
s := RunBasicJetStreamServer(t)
defer s.Shutdown()
nc, js := jsClientConnect(t, s)
defer nc.Close()
_, err := js.AddStream(&nats.StreamConfig{
Name: "TEST",
Subjects: []string{"foo"},
})
require_NoError(t, err)
t.Run("CreateShouldSucceed", func(t *testing.T) {
deadline := time.Now().Add(time.Hour)
ci := jsTestPause_CreateOrUpdateConsumer(t, nc, ActionCreate, "TEST", ConsumerConfig{
Name: "my_consumer_1",
PauseUntil: &deadline,
})
require_True(t, ci != nil)
require_True(t, ci.Config != nil)
require_True(t, ci.Config.PauseUntil != nil)
require_True(t, ci.Config.PauseUntil.Equal(deadline))
})
t.Run("UpdateShouldFail", func(t *testing.T) {
deadline := time.Now().Add(time.Hour)
ci := jsTestPause_CreateOrUpdateConsumer(t, nc, ActionCreate, "TEST", ConsumerConfig{
Name: "my_consumer_2",
})
require_True(t, ci != nil)
require_True(t, ci.Config != nil)
require_True(t, ci.Config.PauseUntil == nil || ci.Config.PauseUntil.IsZero())
var cc ConsumerConfig
j, err := json.Marshal(ci.Config)
require_NoError(t, err)
require_NoError(t, json.Unmarshal(j, &cc))
pauseUntil := time.Now().Add(time.Hour)
cc.PauseUntil = &pauseUntil
ci2 := jsTestPause_CreateOrUpdateConsumer(t, nc, ActionUpdate, "TEST", cc)
require_False(t, ci2.Config.PauseUntil != nil && ci2.Config.PauseUntil.Equal(deadline))
require_True(t, ci2.Config.PauseUntil == nil || ci2.Config.PauseUntil.Equal(time.Time{}))
})
}
func TestJetStreamConsumerPauseViaEndpoint(t *testing.T) {
s := RunBasicJetStreamServer(t)
defer s.Shutdown()
nc, js := jsClientConnect(t, s)
defer nc.Close()
_, err := js.AddStream(&nats.StreamConfig{
Name: "TEST",
Subjects: []string{"push", "pull"},
})
require_NoError(t, err)
t.Run("PullConsumer", func(t *testing.T) {
_, err := js.AddConsumer("TEST", &nats.ConsumerConfig{
Name: "pull_consumer",
})
require_NoError(t, err)
sub, err := js.PullSubscribe("pull", "", nats.Bind("TEST", "pull_consumer"))
require_NoError(t, err)
// This should succeed as there's no pause, so it definitely
// shouldn't take more than a second.
for i := 0; i < 10; i++ {
_, err = js.Publish("pull", []byte("OK"))
require_NoError(t, err)
}
msgs, err := sub.Fetch(10, nats.MaxWait(time.Second))
require_NoError(t, err)
require_Equal(t, len(msgs), 10)
// Now we'll pause the consumer for 3 seconds.
deadline := time.Now().Add(time.Second * 3)
require_True(t, jsTestPause_PauseConsumer(t, nc, "TEST", "pull_consumer", deadline).Equal(deadline))
// This should fail as we'll wait for only half of the deadline.
for i := 0; i < 10; i++ {
_, err = js.Publish("pull", []byte("OK"))
require_NoError(t, err)
}
_, err = sub.Fetch(10, nats.MaxWait(time.Until(deadline)/2))
require_Error(t, err, nats.ErrTimeout)
// This should succeed after a short wait, and when we're done,
// we should be after the deadline.
msgs, err = sub.Fetch(10)
require_NoError(t, err)
require_Equal(t, len(msgs), 10)
require_True(t, time.Now().After(deadline))
// This should succeed as there's no pause, so it definitely
// shouldn't take more than a second.
for i := 0; i < 10; i++ {
_, err = js.Publish("pull", []byte("OK"))
require_NoError(t, err)
}
msgs, err = sub.Fetch(10, nats.MaxWait(time.Second))
require_NoError(t, err)
require_Equal(t, len(msgs), 10)
require_True(t, jsTestPause_PauseConsumer(t, nc, "TEST", "pull_consumer", time.Time{}).Equal(time.Time{}))
// This should succeed as there's no pause, so it definitely
// shouldn't take more than a second.
for i := 0; i < 10; i++ {
_, err = js.Publish("pull", []byte("OK"))
require_NoError(t, err)
}
msgs, err = sub.Fetch(10, nats.MaxWait(time.Second))
require_NoError(t, err)
require_Equal(t, len(msgs), 10)
})
t.Run("PushConsumer", func(t *testing.T) {
ch := make(chan *nats.Msg, 100)
_, err = js.ChanSubscribe("push", ch, nats.BindStream("TEST"), nats.ConsumerName("push_consumer"))
require_NoError(t, err)
// This should succeed as there's no pause, so it definitely
// shouldn't take more than a second.
for i := 0; i < 10; i++ {
_, err = js.Publish("push", []byte("OK"))
require_NoError(t, err)
}
for i := 0; i < 10; i++ {
msg := require_ChanRead(t, ch, time.Second)
require_NotEqual(t, msg, nil)
}
// Now we'll pause the consumer for 3 seconds.
deadline := time.Now().Add(time.Second * 3)
require_True(t, jsTestPause_PauseConsumer(t, nc, "TEST", "push_consumer", deadline).Equal(deadline))
// This should succeed after a short wait, and when we're done,
// we should be after the deadline.
for i := 0; i < 10; i++ {
_, err = js.Publish("push", []byte("OK"))
require_NoError(t, err)
}
for i := 0; i < 10; i++ {
msg := require_ChanRead(t, ch, time.Second*5)
require_NotEqual(t, msg, nil)
require_True(t, time.Now().After(deadline))
}
// This should succeed as there's no pause, so it definitely
// shouldn't take more than a second.
for i := 0; i < 10; i++ {
_, err = js.Publish("push", []byte("OK"))
require_NoError(t, err)
}
for i := 0; i < 10; i++ {
msg := require_ChanRead(t, ch, time.Second)
require_NotEqual(t, msg, nil)
}
require_True(t, jsTestPause_PauseConsumer(t, nc, "TEST", "push_consumer", time.Time{}).Equal(time.Time{}))
// This should succeed as there's no pause, so it definitely
// shouldn't take more than a second.
for i := 0; i < 10; i++ {
_, err = js.Publish("push", []byte("OK"))
require_NoError(t, err)
}
for i := 0; i < 10; i++ {
msg := require_ChanRead(t, ch, time.Second)
require_NotEqual(t, msg, nil)
}
})
}
func TestJetStreamConsumerPauseResumeViaEndpoint(t *testing.T) {
s := RunBasicJetStreamServer(t)
defer s.Shutdown()
nc, js := jsClientConnect(t, s)
defer nc.Close()
_, err := js.AddStream(&nats.StreamConfig{
Name: "TEST",
Subjects: []string{"TEST"},
})
require_NoError(t, err)
_, err = js.AddConsumer("TEST", &nats.ConsumerConfig{
Name: "CONSUMER",
})
require_NoError(t, err)
getConsumerInfo := func() ConsumerInfo {
var ci ConsumerInfo
infoResp, err := nc.Request("$JS.API.CONSUMER.INFO.TEST.CONSUMER", nil, time.Second)
require_NoError(t, err)
err = json.Unmarshal(infoResp.Data, &ci)
require_NoError(t, err)
return ci
}
// Ensure we are not paused
require_False(t, getConsumerInfo().Paused)
// Now we'll pause the consumer for 30 seconds.
deadline := time.Now().Add(time.Second * 30)
require_True(t, jsTestPause_PauseConsumer(t, nc, "TEST", "CONSUMER", deadline).Equal(deadline))
// Ensure the consumer reflects being paused
require_True(t, getConsumerInfo().Paused)
subj := fmt.Sprintf("$JS.API.CONSUMER.PAUSE.%s.%s", "TEST", "CONSUMER")
_, err = nc.Request(subj, nil, time.Second)
require_NoError(t, err)
// Ensure the consumer reflects being resumed
require_False(t, getConsumerInfo().Paused)
}
func TestJetStreamConsumerPauseHeartbeats(t *testing.T) {
s := RunBasicJetStreamServer(t)
defer s.Shutdown()
nc, js := jsClientConnect(t, s)
defer nc.Close()
_, err := js.AddStream(&nats.StreamConfig{
Name: "TEST",
Subjects: []string{"foo"},
})
require_NoError(t, err)
deadline := time.Now().Add(time.Hour)
dsubj := "deliver_subj"
ci := jsTestPause_CreateOrUpdateConsumer(t, nc, ActionCreate, "TEST", ConsumerConfig{
Name: "my_consumer",
PauseUntil: &deadline,
Heartbeat: time.Millisecond * 100,
DeliverSubject: dsubj,
})
require_True(t, ci.Config.PauseUntil.Equal(deadline))
ch := make(chan *nats.Msg, 10)
_, err = nc.ChanSubscribe(dsubj, ch)
require_NoError(t, err)
for i := 0; i < 20; i++ {
msg := require_ChanRead(t, ch, time.Millisecond*200)
require_Equal(t, msg.Header.Get("Status"), "100")
require_Equal(t, msg.Header.Get("Description"), "Idle Heartbeat")
}
}
func TestJetStreamConsumerPauseAdvisories(t *testing.T) {
s := RunBasicJetStreamServer(t)
defer s.Shutdown()
nc, js := jsClientConnect(t, s)
defer nc.Close()
checkAdvisory := func(msg *nats.Msg, shouldBePaused bool, deadline time.Time) {
t.Helper()
var advisory JSConsumerPauseAdvisory
require_NoError(t, json.Unmarshal(msg.Data, &advisory))
require_Equal(t, advisory.Stream, "TEST")
require_Equal(t, advisory.Consumer, "my_consumer")
require_Equal(t, advisory.Paused, shouldBePaused)
require_True(t, advisory.PauseUntil.Equal(deadline))
}
_, err := js.AddStream(&nats.StreamConfig{
Name: "TEST",
Subjects: []string{"foo"},
})
require_NoError(t, err)
ch := make(chan *nats.Msg, 10)
_, err = nc.ChanSubscribe(JSAdvisoryConsumerPausePre+".TEST.my_consumer", ch)
require_NoError(t, err)
deadline := time.Now().Add(time.Second)
jsTestPause_CreateOrUpdateConsumer(t, nc, ActionCreate, "TEST", ConsumerConfig{
Name: "my_consumer",
PauseUntil: &deadline,
})
// First advisory should tell us that the consumer was paused
// on creation.
msg := require_ChanRead(t, ch, time.Second*2)
checkAdvisory(msg, true, deadline)
// The second one for the unpause.
msg = require_ChanRead(t, ch, time.Second*2)
checkAdvisory(msg, false, deadline)
// Now we'll pause the consumer using the API.
deadline = time.Now().Add(time.Second)
require_True(t, jsTestPause_PauseConsumer(t, nc, "TEST", "my_consumer", deadline).Equal(deadline))
// Third advisory should tell us about the pause via the API.
msg = require_ChanRead(t, ch, time.Second*2)
checkAdvisory(msg, true, deadline)
// Finally that should unpause.
msg = require_ChanRead(t, ch, time.Second*2)
checkAdvisory(msg, false, deadline)
}
func TestJetStreamConsumerSurvivesRestart(t *testing.T) {
s := RunBasicJetStreamServer(t)
defer s.Shutdown()
nc, js := jsClientConnect(t, s)
defer nc.Close()
_, err := js.AddStream(&nats.StreamConfig{
Name: "TEST",
Subjects: []string{"foo"},
})
require_NoError(t, err)
deadline := time.Now().Add(time.Hour)
jsTestPause_CreateOrUpdateConsumer(t, nc, ActionCreate, "TEST", ConsumerConfig{
Name: "my_consumer",
PauseUntil: &deadline,
})
sd := s.JetStreamConfig().StoreDir
s.Shutdown()
s = RunJetStreamServerOnPort(-1, sd)
defer s.Shutdown()
stream, err := s.gacc.lookupStream("TEST")
require_NoError(t, err)
consumer := stream.lookupConsumer("my_consumer")
require_NotEqual(t, consumer, nil)
consumer.mu.RLock()
timer := consumer.uptmr
consumer.mu.RUnlock()
require_True(t, timer != nil)
}
func TestJetStreamConsumerInfoNumPending(t *testing.T) {
s := RunBasicJetStreamServer(t)
defer s.Shutdown()
// Client for API requests.
nc, js := jsClientConnect(t, s)
defer nc.Close()
_, err := js.AddStream(&nats.StreamConfig{
Name: "LIMITS",
Subjects: []string{"js.in.limits"},
MaxMsgs: 100,
})
require_NoError(t, err)
_, err = js.AddConsumer("LIMITS", &nats.ConsumerConfig{
Name: "PULL",
AckPolicy: nats.AckExplicitPolicy,
})
require_NoError(t, err)
for i := 0; i < 1000; i++ {
js.Publish("js.in.limits", []byte("x"))
}
ci, err := js.ConsumerInfo("LIMITS", "PULL")
require_NoError(t, err)
require_Equal(t, ci.NumPending, 100)
// Now restart the server.
sd := s.JetStreamConfig().StoreDir
s.Shutdown()
// Restart.
s = RunJetStreamServerOnPort(-1, sd)
defer s.Shutdown()
nc, js = jsClientConnect(t, s)
defer nc.Close()
ci, err = js.ConsumerInfo("LIMITS", "PULL")
require_NoError(t, err)
require_Equal(t, ci.NumPending, 100)
}
func TestJetStreamConsumerDontDecrementPendingCountOnSkippedMsg(t *testing.T) {
s := RunBasicJetStreamServer(t)
defer s.Shutdown()
nc, js := jsClientConnect(t, s)
defer nc.Close()
_, err := js.AddStream(&nats.StreamConfig{Name: "TEST", Subjects: []string{"foo"}})
require_NoError(t, err)
_, err = js.AddConsumer("TEST", &nats.ConsumerConfig{Durable: "CONSUMER"})
require_NoError(t, err)
acc, err := s.lookupAccount(globalAccountName)
require_NoError(t, err)
mset, err := acc.lookupStream("TEST")
require_NoError(t, err)
o := mset.lookupConsumer("CONSUMER")
requireExpected := func(expected int64) {
t.Helper()
checkFor(t, time.Second, 10*time.Millisecond, func() error {
o.mu.RLock()
npc := o.npc
o.mu.RUnlock()
if npc != expected {
return fmt.Errorf("expected npc=%d, got %d", expected, npc)
}
return nil
})
}
// Should initially report no messages available.
requireExpected(0)
// A new message is available, should report in pending.
_, err = js.Publish("foo", nil)
require_NoError(t, err)
requireExpected(1)
// Pending count should decrease when the message is deleted.
err = js.DeleteMsg("TEST", 1)
require_NoError(t, err)
requireExpected(0)
// Make more messages available, should report in pending.
_, err = js.Publish("foo", nil)
require_NoError(t, err)
_, err = js.Publish("foo", nil)
require_NoError(t, err)
requireExpected(2)
// Simulate getNextMsg being called and the starting sequence to skip over a deleted message.
// Also simulate one pending message.
o.mu.Lock()
o.sseq = 100
o.npc--
o.pending = make(map[uint64]*Pending)
o.pending[2] = &Pending{}
o.mu.Unlock()
// Since this message is pending we should not decrement pending count as we've done so already.
o.decStreamPending(2, "foo")
requireExpected(1)
// This is the deleted message that was skipped, we've hit the race condition and are not able to
// fix it at this point. If we decrement then we could have decremented it twice if the message
// was removed as a result of an Ack with Interest or WorkQueue retention, instead of due to contention.
o.decStreamPending(3, "foo")
requireExpected(1)
}
func TestJetStreamConsumerPendingCountAfterMsgAckAboveFloor(t *testing.T) {
s := RunBasicJetStreamServer(t)
defer s.Shutdown()
nc, js := jsClientConnect(t, s)
defer nc.Close()
_, err := js.AddStream(&nats.StreamConfig{
Name: "TEST",
Subjects: []string{"foo"},
Retention: nats.WorkQueuePolicy,
})
require_NoError(t, err)
for i := 0; i < 2; i++ {
_, err = js.Publish("foo", nil)
require_NoError(t, err)
}
sub, err := js.PullSubscribe("foo", "CONSUMER")
require_NoError(t, err)
acc, err := s.lookupAccount(globalAccountName)
require_NoError(t, err)
mset, err := acc.lookupStream("TEST")
require_NoError(t, err)
o := mset.lookupConsumer("CONSUMER")
requireExpected := func(expected int64) {
t.Helper()
checkFor(t, time.Second, 10*time.Millisecond, func() error {
o.mu.RLock()
npc := o.npc
o.mu.RUnlock()
if npc != expected {
return fmt.Errorf("expected npc=%d, got %d", expected, npc)
}
return nil
})
}
// Expect 2 messages pending.
requireExpected(2)
// Fetch 2 messages and ack the last.
msgs, err := sub.Fetch(2)
require_NoError(t, err)
require_Len(t, len(msgs), 2)
msg := msgs[1]
err = msg.AckSync()
require_NoError(t, err)
// We've fetched 2 message so should report 0 pending.
requireExpected(0)
}
func TestJetStreamConsumerPullRemoveInterest(t *testing.T) {
s := RunBasicJetStreamServer(t)
defer s.Shutdown()
mname := "MYS-PULL"
mset, err := s.GlobalAccount().addStream(&StreamConfig{Name: mname, Storage: MemoryStorage})
if err != nil {
t.Fatalf("Unexpected error adding stream: %v", err)
}
defer mset.delete()
wcfg := &ConsumerConfig{Durable: "worker", AckPolicy: AckExplicit}
o, err := mset.addConsumer(wcfg)
if err != nil {
t.Fatalf("Expected no error with registered interest, got %v", err)
}
rqn := o.requestNextMsgSubject()
defer o.delete()
nc := clientConnectToServer(t, s)
defer nc.Close()
// Ask for a message even though one is not there. This will queue us up for waiting.
if _, err := nc.Request(rqn, nil, 10*time.Millisecond); err == nil {
t.Fatalf("Expected an error, got none")
}
// This is using new style request mechanism. so drop the connection itself to get rid of interest.
nc.Close()
// Wait for client cleanup
checkFor(t, 200*time.Millisecond, 10*time.Millisecond, func() error {
if n := s.NumClients(); err != nil || n != 0 {
return fmt.Errorf("Still have %d clients", n)
}
return nil
})
nc = clientConnectToServer(t, s)
defer nc.Close()
// Send a message
sendStreamMsg(t, nc, mname, "Hello World!")
msg, err := nc.Request(rqn, nil, time.Second)
require_NoError(t, err)
_, dseq, dc, _, _ := replyInfo(msg.Reply)
if dseq != 1 {
t.Fatalf("Expected consumer sequence of 1, got %d", dseq)
}
if dc != 1 {
t.Fatalf("Expected delivery count of 1, got %d", dc)
}
// Now do old school request style and more than one waiting.
nc = clientConnectWithOldRequest(t, s)
defer nc.Close()
// Now queue up 10 waiting via failed requests.
for i := 0; i < 10; i++ {
if _, err := nc.Request(rqn, nil, 1*time.Millisecond); err == nil {
t.Fatalf("Expected an error, got none")
}
}
// Send a second message
sendStreamMsg(t, nc, mname, "Hello World!")
msg, err = nc.Request(rqn, nil, time.Second)
require_NoError(t, err)
_, dseq, dc, _, _ = replyInfo(msg.Reply)
if dseq != 2 {
t.Fatalf("Expected consumer sequence of 2, got %d", dseq)
}
if dc != 1 {
t.Fatalf("Expected delivery count of 1, got %d", dc)
}
}
func TestJetStreamConsumerPullMaxWaitingOfOne(t *testing.T) {
s := RunBasicJetStreamServer(t)
defer s.Shutdown()
nc, js := jsClientConnect(t, s)
defer nc.Close()
_, err := js.AddStream(&nats.StreamConfig{Name: "TEST", Subjects: []string{"TEST.A"}})
require_NoError(t, err)
_, err = js.AddConsumer("TEST", &nats.ConsumerConfig{
Durable: "dur",
MaxWaiting: 1,
AckPolicy: nats.AckExplicitPolicy,
})
require_NoError(t, err)
// First check that a request can timeout (we had an issue where this was
// not the case for MaxWaiting of 1).
req := JSApiConsumerGetNextRequest{Batch: 1, Expires: 250 * time.Millisecond}
reqb, _ := json.Marshal(req)
msg, err := nc.Request("$JS.API.CONSUMER.MSG.NEXT.TEST.dur", reqb, 13000*time.Millisecond)
require_NoError(t, err)
if v := msg.Header.Get("Status"); v != "408" {
t.Fatalf("Expected 408, got: %s", v)
}
// Now have a request waiting...
req = JSApiConsumerGetNextRequest{Batch: 1}
reqb, _ = json.Marshal(req)
// Send the request, but do not block since we want then to send an extra
// request that should be rejected.
sub := natsSubSync(t, nc, nats.NewInbox())
err = nc.PublishRequest("$JS.API.CONSUMER.MSG.NEXT.TEST.dur", sub.Subject, reqb)
require_NoError(t, err)
// Send a new request, this should be rejected as a 409.
req = JSApiConsumerGetNextRequest{Batch: 1, Expires: 250 * time.Millisecond}
reqb, _ = json.Marshal(req)
msg, err = nc.Request("$JS.API.CONSUMER.MSG.NEXT.TEST.dur", reqb, 300*time.Millisecond)
require_NoError(t, err)
if v := msg.Header.Get("Status"); v != "409" {
t.Fatalf("Expected 409, got: %s", v)
}
if v := msg.Header.Get("Description"); v != "Exceeded MaxWaiting" {
t.Fatalf("Expected error about exceeded max waiting, got: %s", v)
}
}
func TestJetStreamConsumerPullMaxWaitingOfOneWithHeartbeatInterval(t *testing.T) {
s := RunBasicJetStreamServer(t)
defer s.Shutdown()
nc, js := jsClientConnect(t, s)
defer nc.Close()
_, err := js.AddStream(&nats.StreamConfig{Name: "TEST", Subjects: []string{"TEST.A"}})
require_NoError(t, err)
_, err = js.AddConsumer("TEST", &nats.ConsumerConfig{
Durable: "dur",
MaxWaiting: 1,
AckPolicy: nats.AckExplicitPolicy,
})
require_NoError(t, err)
// First check that a request can timeout (we had an issue where this was
// not the case for MaxWaiting of 1).
req := JSApiConsumerGetNextRequest{Batch: 1, Expires: 250 * time.Millisecond}
reqb, _ := json.Marshal(req)
msg, err := nc.Request("$JS.API.CONSUMER.MSG.NEXT.TEST.dur", reqb, 13000*time.Millisecond)
require_NoError(t, err)
if v := msg.Header.Get("Status"); v != "408" {
t.Fatalf("Expected 408, got: %s", v)
}
// Now have a request waiting...
req = JSApiConsumerGetNextRequest{Batch: 1}
reqb, _ = json.Marshal(req)
// Send the request, but do not block since we want then to send an extra
// request that should be rejected.
sub := natsSubSync(t, nc, nats.NewInbox())
err = nc.PublishRequest("$JS.API.CONSUMER.MSG.NEXT.TEST.dur", sub.Subject, reqb)
require_NoError(t, err)
// Send a new request, this should not respond since we specified an idle heartbeat,
// therefore the client will just expect to miss those instead.
req = JSApiConsumerGetNextRequest{Batch: 1, Expires: 500 * time.Millisecond, Heartbeat: 250 * time.Millisecond}
reqb, _ = json.Marshal(req)
_, err = nc.Request("$JS.API.CONSUMER.MSG.NEXT.TEST.dur", reqb, 300*time.Millisecond)
require_Error(t, err)
}
func TestJetStreamConsumerPullMaxWaiting(t *testing.T) {
s := RunBasicJetStreamServer(t)
defer s.Shutdown()
nc, js := jsClientConnect(t, s)
defer nc.Close()
_, err := js.AddStream(&nats.StreamConfig{Name: "TEST", Subjects: []string{"test.*"}})
require_NoError(t, err)
_, err = js.AddConsumer("TEST", &nats.ConsumerConfig{
Durable: "dur",
AckPolicy: nats.AckExplicitPolicy,
MaxWaiting: 10,
})
require_NoError(t, err)
// Cannot be updated.
_, err = js.UpdateConsumer("TEST", &nats.ConsumerConfig{
Durable: "dur",
AckPolicy: nats.AckExplicitPolicy,
MaxWaiting: 1,
})
if !strings.Contains(err.Error(), "can not be updated") {
t.Fatalf(`expected "cannot be updated" error, got %s`, err)
}
}
func TestJetStreamConsumerPullRequestCleanup(t *testing.T) {
s := RunBasicJetStreamServer(t)
defer s.Shutdown()
nc, js := jsClientConnect(t, s)
defer nc.Close()
_, err := js.AddStream(&nats.StreamConfig{Name: "T", Storage: nats.MemoryStorage})
require_NoError(t, err)
_, err = js.AddConsumer("T", &nats.ConsumerConfig{Durable: "dlc", AckPolicy: nats.AckExplicitPolicy})
require_NoError(t, err)
req := &JSApiConsumerGetNextRequest{Batch: 10, Expires: 100 * time.Millisecond}
jreq, err := json.Marshal(req)
require_NoError(t, err)
// Need interest otherwise the requests will be recycled based on that.
_, err = nc.SubscribeSync("xx")
require_NoError(t, err)
// Queue up 100 requests.
rsubj := fmt.Sprintf(JSApiRequestNextT, "T", "dlc")
for i := 0; i < 100; i++ {
err = nc.PublishRequest(rsubj, "xx", jreq)
require_NoError(t, err)
}
// Wait to expire
time.Sleep(200 * time.Millisecond)
ci, err := js.ConsumerInfo("T", "dlc")
require_NoError(t, err)
if ci.NumWaiting != 0 {
t.Fatalf("Expected to see no waiting requests, got %d", ci.NumWaiting)
}
}
func TestJetStreamConsumerPullRequestMaximums(t *testing.T) {
s := RunBasicJetStreamServer(t)
defer s.Shutdown()
nc, _ := jsClientConnect(t, s)
defer nc.Close()
// Need to do this via server for now.
acc := s.GlobalAccount()
mset, err := acc.addStream(&StreamConfig{Name: "TEST", Storage: MemoryStorage})
require_NoError(t, err)
_, err = mset.addConsumer(&ConsumerConfig{
Durable: "dlc",
MaxRequestBatch: 10,
MaxRequestMaxBytes: 10_000,
MaxRequestExpires: time.Second,
AckPolicy: AckExplicit,
})
require_NoError(t, err)
genReq := func(b, mb int, e time.Duration) []byte {
req := &JSApiConsumerGetNextRequest{Batch: b, Expires: e, MaxBytes: mb}
jreq, err := json.Marshal(req)
require_NoError(t, err)
return jreq
}
rsubj := fmt.Sprintf(JSApiRequestNextT, "TEST", "dlc")
// Exceeds max batch size.
resp, err := nc.Request(rsubj, genReq(11, 0, 100*time.Millisecond), time.Second)
require_NoError(t, err)
if status := resp.Header.Get("Status"); status != "409" {
t.Fatalf("Expected a 409 status code, got %q", status)
}
// Exceeds max expires.
resp, err = nc.Request(rsubj, genReq(1, 0, 10*time.Minute), time.Second)
require_NoError(t, err)
if status := resp.Header.Get("Status"); status != "409" {
t.Fatalf("Expected a 409 status code, got %q", status)
}
// Exceeds max bytes.
resp, err = nc.Request(rsubj, genReq(10, 10_000*2, 10*time.Minute), time.Second)
require_NoError(t, err)
if status := resp.Header.Get("Status"); status != "409" {
t.Fatalf("Expected a 409 status code, got %q", status)
}
}
func TestJetStreamConsumerPullCrossAccountExpires(t *testing.T) {
conf := createConfFile(t, []byte(fmt.Sprintf(`
listen: 127.0.0.1:-1
jetstream: {max_mem_store: 4GB, max_file_store: 1TB, store_dir: %q}
accounts: {
JS: {
jetstream: enabled
users: [ {user: dlc, password: foo} ]
exports [ { service: "$JS.API.CONSUMER.MSG.NEXT.>", response: stream } ]
},
IU: {
users: [ {user: mh, password: bar} ]
imports [ { service: { subject: "$JS.API.CONSUMER.MSG.NEXT.*.*", account: JS } }]
# Re-export for dasiy chain test.
exports [ { service: "$JS.API.CONSUMER.MSG.NEXT.>", response: stream } ]
},
IU2: {
users: [ {user: ik, password: bar} ]
imports [ { service: { subject: "$JS.API.CONSUMER.MSG.NEXT.*.*", account: IU } } ]
},
}
`, t.TempDir())))
s, _ := RunServerWithConfig(conf)
defer s.Shutdown()
// Connect to JS account and create stream, put some messages into it.
nc, js := jsClientConnect(t, s, nats.UserInfo("dlc", "foo"))
defer nc.Close()
_, err := js.AddStream(&nats.StreamConfig{Name: "PC", Subjects: []string{"foo"}})
require_NoError(t, err)
toSend := 50
for i := 0; i < toSend; i++ {
_, err := js.Publish("foo", []byte("OK"))
require_NoError(t, err)
}
// Now create pull consumer.
_, err = js.AddConsumer("PC", &nats.ConsumerConfig{Durable: "PC", AckPolicy: nats.AckExplicitPolicy})
require_NoError(t, err)
// Now access from the importing account.
nc2, _ := jsClientConnect(t, s, nats.UserInfo("mh", "bar"))
defer nc2.Close()
// Make sure batch request works properly with stream response.
req := &JSApiConsumerGetNextRequest{Batch: 10}
jreq, err := json.Marshal(req)
require_NoError(t, err)
rsubj := fmt.Sprintf(JSApiRequestNextT, "PC", "PC")
// Make sure we can get a batch correctly etc.
// This requires response stream above in the export definition.
sub, err := nc2.SubscribeSync("xx")
require_NoError(t, err)
err = nc2.PublishRequest(rsubj, "xx", jreq)
require_NoError(t, err)
checkSubsPending(t, sub, 10)
// Now let's queue up a bunch of requests and then delete interest to make sure the system
// removes those requests.
// Purge stream
err = js.PurgeStream("PC")
require_NoError(t, err)
// Queue up 10 requests
for i := 0; i < 10; i++ {
err = nc2.PublishRequest(rsubj, "xx", jreq)
require_NoError(t, err)
}
// Since using different connection, flush to make sure processed.
nc2.Flush()
ci, err := js.ConsumerInfo("PC", "PC")
require_NoError(t, err)
if ci.NumWaiting != 10 {
t.Fatalf("Expected to see 10 waiting requests, got %d", ci.NumWaiting)
}
// Now remove interest and make sure requests are removed.
sub.Unsubscribe()
checkFor(t, 5*time.Second, 10*time.Millisecond, func() error {
ci, err := js.ConsumerInfo("PC", "PC")
require_NoError(t, err)
if ci.NumWaiting != 0 {
return fmt.Errorf("Requests still present")
}
return nil
})
// Now let's test that ephemerals will go away as well when interest etc is no longer around.
ci, err = js.AddConsumer("PC", &nats.ConsumerConfig{AckPolicy: nats.AckExplicitPolicy})
require_NoError(t, err)
// Set the inactivity threshold by hand for now.
jsacc, err := s.LookupAccount("JS")
require_NoError(t, err)
mset, err := jsacc.lookupStream("PC")
require_NoError(t, err)
o := mset.lookupConsumer(ci.Name)
if o == nil {
t.Fatalf("Error looking up consumer %q", ci.Name)
}
err = o.setInActiveDeleteThreshold(50 * time.Millisecond)
require_NoError(t, err)
rsubj = fmt.Sprintf(JSApiRequestNextT, "PC", ci.Name)
sub, err = nc2.SubscribeSync("zz")
require_NoError(t, err)
err = nc2.PublishRequest(rsubj, "zz", jreq)
require_NoError(t, err)
// Wait past inactive threshold.
time.Sleep(100 * time.Millisecond)
// Make sure it is still there..
ci, err = js.ConsumerInfo("PC", ci.Name)
require_NoError(t, err)
if ci.NumWaiting != 1 {
t.Fatalf("Expected to see 1 waiting request, got %d", ci.NumWaiting)
}
// Now release interest.
sub.Unsubscribe()
checkFor(t, 5*time.Second, 10*time.Millisecond, func() error {
_, err := js.ConsumerInfo("PC", ci.Name)
if err == nil {
return fmt.Errorf("Consumer still present")
}
return nil
})
// Now test daisy chained.
toSend = 10
for i := 0; i < toSend; i++ {
_, err := js.Publish("foo", []byte("OK"))
require_NoError(t, err)
}
ci, err = js.AddConsumer("PC", &nats.ConsumerConfig{AckPolicy: nats.AckExplicitPolicy})
require_NoError(t, err)
// Set the inactivity threshold by hand for now.
o = mset.lookupConsumer(ci.Name)
if o == nil {
t.Fatalf("Error looking up consumer %q", ci.Name)
}
// Make this one longer so we test request purge and ephemerals in same test.
err = o.setInActiveDeleteThreshold(500 * time.Millisecond)
require_NoError(t, err)
// Now access from the importing account.
nc3, _ := jsClientConnect(t, s, nats.UserInfo("ik", "bar"))
defer nc3.Close()
sub, err = nc3.SubscribeSync("yy")
require_NoError(t, err)
rsubj = fmt.Sprintf(JSApiRequestNextT, "PC", ci.Name)
err = nc3.PublishRequest(rsubj, "yy", jreq)
require_NoError(t, err)
checkSubsPending(t, sub, 10)
// Purge stream
err = js.PurgeStream("PC")
require_NoError(t, err)
// Queue up 10 requests
for i := 0; i < 10; i++ {
err = nc3.PublishRequest(rsubj, "yy", jreq)
require_NoError(t, err)
}
// Since using different connection, flush to make sure processed.
nc3.Flush()
ci, err = js.ConsumerInfo("PC", ci.Name)
require_NoError(t, err)
if ci.NumWaiting != 10 {
t.Fatalf("Expected to see 10 waiting requests, got %d", ci.NumWaiting)
}
// Now remove interest and make sure requests are removed.
sub.Unsubscribe()
checkFor(t, 5*time.Second, 10*time.Millisecond, func() error {
ci, err := js.ConsumerInfo("PC", ci.Name)
require_NoError(t, err)
if ci.NumWaiting != 0 {
return fmt.Errorf("Requests still present")
}
return nil
})
// Now make sure the ephemeral goes away too.
// Ephemerals have jitter by default of up to 1s.
checkFor(t, 6*time.Second, 10*time.Millisecond, func() error {
_, err := js.ConsumerInfo("PC", ci.Name)
if err == nil {
return fmt.Errorf("Consumer still present")
}
return nil
})
}
func TestJetStreamConsumerPullCrossAccountExpiresNoDataRace(t *testing.T) {
conf := createConfFile(t, []byte(fmt.Sprintf(`
listen: 127.0.0.1:-1
jetstream: {max_mem_store: 4GB, max_file_store: 1TB, store_dir: %q}
accounts: {
JS: {
jetstream: enabled
users: [ {user: dlc, password: foo} ]
exports [ { service: "$JS.API.CONSUMER.MSG.NEXT.>", response: stream } ]
},
IU: {
jetstream: enabled
users: [ {user: ik, password: bar} ]
imports [ { service: { subject: "$JS.API.CONSUMER.MSG.NEXT.*.*", account: JS } }]
},
}
`, t.TempDir())))
test := func() {
s, _ := RunServerWithConfig(conf)
defer s.Shutdown()
// Connect to JS account and create stream, put some messages into it.
nc, js := jsClientConnect(t, s, nats.UserInfo("dlc", "foo"))
defer nc.Close()
_, err := js.AddStream(&nats.StreamConfig{Name: "PC", Subjects: []string{"foo"}})
require_NoError(t, err)
toSend := 100
for i := 0; i < toSend; i++ {
_, err := js.Publish("foo", []byte("OK"))
require_NoError(t, err)
}
// Create pull consumer.
_, err = js.AddConsumer("PC", &nats.ConsumerConfig{Durable: "PC", AckPolicy: nats.AckExplicitPolicy})
require_NoError(t, err)
// Now access from the importing account.
nc2, _ := jsClientConnect(t, s, nats.UserInfo("ik", "bar"))
defer nc2.Close()
req := &JSApiConsumerGetNextRequest{Batch: 1}
jreq, err := json.Marshal(req)
require_NoError(t, err)
rsubj := fmt.Sprintf(JSApiRequestNextT, "PC", "PC")
sub, err := nc2.SubscribeSync("xx")
require_NoError(t, err)
wg := sync.WaitGroup{}
wg.Add(1)
go func() {
time.Sleep(5 * time.Millisecond)
sub.Unsubscribe()
wg.Done()
}()
for i := 0; i < toSend; i++ {
nc2.PublishRequest(rsubj, "xx", jreq)
}
wg.Wait()
}
// Need to rerun this test several times to get the race (which then would possible be panic
// such as: "fatal error: concurrent map read and map write"
for iter := 0; iter < 10; iter++ {
test()
}
}
// This tests account export/import replies across a LN connection with account import/export
// on both sides of the LN.
func TestJetStreamConsumerPullCrossAccountsAndLeafNodes(t *testing.T) {
conf := createConfFile(t, []byte(fmt.Sprintf(`
server_name: SJS
listen: 127.0.0.1:-1
jetstream: {max_mem_store: 4GB, max_file_store: 1TB, domain: JSD, store_dir: %q }
accounts: {
JS: {
jetstream: enabled
users: [ {user: dlc, password: foo} ]
exports [ { service: "$JS.API.CONSUMER.MSG.NEXT.>", response: stream } ]
},
IU: {
users: [ {user: mh, password: bar} ]
imports [ { service: { subject: "$JS.API.CONSUMER.MSG.NEXT.*.*", account: JS } }]
},
}
leaf { listen: "127.0.0.1:-1" }
`, t.TempDir())))
s, o := RunServerWithConfig(conf)
defer s.Shutdown()
conf2 := createConfFile(t, []byte(fmt.Sprintf(`
server_name: SLN
listen: 127.0.0.1:-1
accounts: {
A: {
users: [ {user: l, password: p} ]
exports [ { service: "$JS.JSD.API.CONSUMER.MSG.NEXT.>", response: stream } ]
},
B: {
users: [ {user: m, password: p} ]
imports [ { service: { subject: "$JS.JSD.API.CONSUMER.MSG.NEXT.*.*", account: A } }]
},
}
# bind local A to IU account on other side of LN.
leaf { remotes [ { url: nats://mh:[email protected]:%d; account: A } ] }
`, o.LeafNode.Port)))
s2, _ := RunServerWithConfig(conf2)
defer s2.Shutdown()
checkLeafNodeConnectedCount(t, s, 1)
// Connect to JS account, create stream and consumer and put in some messages.
nc, js := jsClientConnect(t, s, nats.UserInfo("dlc", "foo"))
defer nc.Close()
_, err := js.AddStream(&nats.StreamConfig{Name: "PC", Subjects: []string{"foo"}})
require_NoError(t, err)
toSend := 10
for i := 0; i < toSend; i++ {
_, err := js.Publish("foo", []byte("OK"))
require_NoError(t, err)
}
// Now create durable pull consumer.
_, err = js.AddConsumer("PC", &nats.ConsumerConfig{Durable: "PC", AckPolicy: nats.AckExplicitPolicy})
require_NoError(t, err)
// Now access from the account on the leafnode, so importing on both sides and crossing a leafnode connection.
nc2, _ := jsClientConnect(t, s2, nats.UserInfo("m", "p"))
defer nc2.Close()
req := &JSApiConsumerGetNextRequest{Batch: toSend, Expires: 500 * time.Millisecond}
jreq, err := json.Marshal(req)
require_NoError(t, err)
// Make sure we can get a batch correctly etc.
// This requires response stream above in the export definition.
sub, err := nc2.SubscribeSync("xx")
require_NoError(t, err)
rsubj := "$JS.JSD.API.CONSUMER.MSG.NEXT.PC.PC"
err = nc2.PublishRequest(rsubj, "xx", jreq)
require_NoError(t, err)
checkSubsPending(t, sub, 10)
// Queue up a bunch of requests.
for i := 0; i < 10; i++ {
err = nc2.PublishRequest(rsubj, "xx", jreq)
require_NoError(t, err)
}
checkFor(t, 5*time.Second, 10*time.Millisecond, func() error {
ci, err := js.ConsumerInfo("PC", "PC")
require_NoError(t, err)
if ci.NumWaiting != 10 {
return fmt.Errorf("Expected to see 10 waiting requests, got %d", ci.NumWaiting)
}
return nil
})
// Remove interest.
sub.Unsubscribe()
// Make sure requests go away eventually after they expire.
checkFor(t, 5*time.Second, 10*time.Millisecond, func() error {
ci, err := js.ConsumerInfo("PC", "PC")
require_NoError(t, err)
if ci.NumWaiting != 0 {
return fmt.Errorf("Expected to see no waiting requests, got %d", ci.NumWaiting)
}
return nil
})
}
// This test is to explicitly test for all combinations of pull consumer behavior.
// 1. Long poll, will be used to emulate push. A request is only invalidated when batch is filled, it expires, or we lose interest.
// 2. Batch 1, will return no messages or a message. Works today.
// 3. Conditional wait, or one shot. This is what the clients do when the do a fetch().
// They expect to wait up to a given time for any messages but will return once they have any to deliver, so parital fills.
// 4. Try, which never waits at all ever.
func TestJetStreamConsumerPullOneShotBehavior(t *testing.T) {
s := RunBasicJetStreamServer(t)
defer s.Shutdown()
// Client for API requests.
nc, js := jsClientConnect(t, s)
defer nc.Close()
_, err := js.AddStream(&nats.StreamConfig{
Name: "TEST",
Subjects: []string{"foo"},
})
require_NoError(t, err)
_, err = js.AddConsumer("TEST", &nats.ConsumerConfig{
Durable: "dlc",
AckPolicy: nats.AckExplicitPolicy,
FilterSubject: "foo",
})
require_NoError(t, err)
// We will do low level requests by hand for this test as to not depend on any client impl.
rsubj := fmt.Sprintf(JSApiRequestNextT, "TEST", "dlc")
getNext := func(batch int, expires time.Duration, noWait bool) (numMsgs int, elapsed time.Duration, hdr *nats.Header) {
t.Helper()
req := &JSApiConsumerGetNextRequest{Batch: batch, Expires: expires, NoWait: noWait}
jreq, err := json.Marshal(req)
require_NoError(t, err)
// Create listener.
reply, msgs := nats.NewInbox(), make(chan *nats.Msg, batch)
sub, err := nc.ChanSubscribe(reply, msgs)
require_NoError(t, err)
defer sub.Unsubscribe()
// Send request.
start := time.Now()
err = nc.PublishRequest(rsubj, reply, jreq)
require_NoError(t, err)
for {
select {
case m := <-msgs:
if len(m.Data) == 0 && m.Header != nil {
return numMsgs, time.Since(start), &m.Header
}
numMsgs++
if numMsgs >= batch {
return numMsgs, time.Since(start), nil
}
case <-time.After(expires + 250*time.Millisecond):
t.Fatalf("Did not receive all the msgs in time")
}
}
}
expect := func(batch int, expires time.Duration, noWait bool, ne int, he *nats.Header, lt time.Duration, gt time.Duration) {
t.Helper()
n, e, h := getNext(batch, expires, noWait)
if n != ne {
t.Fatalf("Expected %d msgs, got %d", ne, n)
}
if !reflect.DeepEqual(h, he) {
t.Fatalf("Expected %+v hdr, got %+v", he, h)
}
if lt > 0 && e > lt {
t.Fatalf("Expected elapsed of %v to be less than %v", e, lt)
}
if gt > 0 && e < gt {
t.Fatalf("Expected elapsed of %v to be greater than %v", e, gt)
}
}
expectAfter := func(batch int, expires time.Duration, noWait bool, ne int, he *nats.Header, gt time.Duration) {
t.Helper()
expect(batch, expires, noWait, ne, he, 0, gt)
}
expectInstant := func(batch int, expires time.Duration, noWait bool, ne int, he *nats.Header) {
t.Helper()
expect(batch, expires, noWait, ne, he, 5*time.Millisecond, 0)
}
expectOK := func(batch int, expires time.Duration, noWait bool, ne int) {
t.Helper()
expectInstant(batch, expires, noWait, ne, nil)
}
noMsgs := &nats.Header{"Status": []string{"404"}, "Description": []string{"No Messages"}}
reqTimeout := &nats.Header{"Status": []string{"408"}, "Description": []string{"Request Timeout"}, "Nats-Pending-Bytes": []string{"0"}, "Nats-Pending-Messages": []string{"1"}}
// We are empty here, meaning no messages available.
// Do not wait, should get noMsgs.
expectInstant(1, 0, true, 0, noMsgs)
// We should wait here for the full second.
expectAfter(1, 250*time.Millisecond, false, 0, reqTimeout, 250*time.Millisecond)
// This should also wait since no messages are available. This is the one shot scenario, or wait for at least a message if none are there.
expectAfter(1, 500*time.Millisecond, true, 0, reqTimeout, 500*time.Millisecond)
// Now let's put some messages into the system.
for i := 0; i < 20; i++ {
_, err := js.Publish("foo", []byte("HELLO"))
require_NoError(t, err)
}
// Now run same 3 scenarios.
expectOK(1, 0, true, 1)
expectOK(5, 500*time.Millisecond, false, 5)
expectOK(5, 500*time.Millisecond, true, 5)
}
func TestJetStreamConsumerPullMultipleRequestsExpireOutOfOrder(t *testing.T) {
s := RunBasicJetStreamServer(t)
defer s.Shutdown()
// Client for API requests.
nc, js := jsClientConnect(t, s)
defer nc.Close()
_, err := js.AddStream(&nats.StreamConfig{
Name: "TEST",
Subjects: []string{"foo"},
})
require_NoError(t, err)
_, err = js.AddConsumer("TEST", &nats.ConsumerConfig{
Durable: "dlc",
AckPolicy: nats.AckExplicitPolicy,
FilterSubject: "foo",
})
require_NoError(t, err)
// We will now queue up 4 requests. All should expire but they will do so out of order.
// We want to make sure we get them in correct order.
rsubj := fmt.Sprintf(JSApiRequestNextT, "TEST", "dlc")
sub, err := nc.SubscribeSync("i.*")
require_NoError(t, err)
defer sub.Unsubscribe()
for _, expires := range []time.Duration{200, 100, 25, 75} {
reply := fmt.Sprintf("i.%d", expires)
req := &JSApiConsumerGetNextRequest{Expires: expires * time.Millisecond}
jreq, err := json.Marshal(req)
require_NoError(t, err)
err = nc.PublishRequest(rsubj, reply, jreq)
require_NoError(t, err)
}
start := time.Now()
checkSubsPending(t, sub, 4)
elapsed := time.Since(start)
if elapsed < 200*time.Millisecond || elapsed > 500*time.Millisecond {
t.Fatalf("Expected elapsed to be close to %v, but got %v", 200*time.Millisecond, elapsed)
}
var rs []string
for i := 0; i < 4; i++ {
m, err := sub.NextMsg(0)
require_NoError(t, err)
rs = append(rs, m.Subject)
}
if expected := []string{"i.25", "i.75", "i.100", "i.200"}; !reflect.DeepEqual(rs, expected) {
t.Fatalf("Received in wrong order, wanted %+v, got %+v", expected, rs)
}
}
func TestJetStreamConsumerPullNoAck(t *testing.T) {
s := RunBasicJetStreamServer(t)
defer s.Shutdown()
nc, js := jsClientConnect(t, s)
defer nc.Close()
_, err := js.AddStream(&nats.StreamConfig{
Name: "TEST",
Subjects: []string{"ORDERS.*"},
})
require_NoError(t, err)
_, err = js.AddConsumer("TEST", &nats.ConsumerConfig{
Durable: "dlc",
AckPolicy: nats.AckNonePolicy,
})
require_NoError(t, err)
}
func TestJetStreamConsumerPullLastPerSubjectRedeliveries(t *testing.T) {
s := RunBasicJetStreamServer(t)
defer s.Shutdown()
nc, js := jsClientConnect(t, s)
defer nc.Close()
_, err := js.AddStream(&nats.StreamConfig{
Name: "TEST",
Subjects: []string{"foo.>"},
})
require_NoError(t, err)
for i := 0; i < 20; i++ {
sendStreamMsg(t, nc, fmt.Sprintf("foo.%v", i), "msg")
}
// Create a pull sub with a maxackpending that is <= of the number of
// messages in the stream and as much as we are going to Fetch() below.
sub, err := js.PullSubscribe(">", "dur",
nats.AckExplicit(),
nats.BindStream("TEST"),
nats.DeliverLastPerSubject(),
nats.MaxAckPending(10),
nats.MaxRequestBatch(10),
nats.AckWait(250*time.Millisecond))
require_NoError(t, err)
// Fetch the max number of message we can get, and don't ack them.
_, err = sub.Fetch(10, nats.MaxWait(time.Second))
require_NoError(t, err)
// Wait for more than redelivery time.
time.Sleep(500 * time.Millisecond)
// Fetch again, make sure we can get those 10 messages.
msgs, err := sub.Fetch(10, nats.MaxWait(time.Second))
require_NoError(t, err)
require_True(t, len(msgs) == 10)
// Make sure those were the first 10 messages
for i, m := range msgs {
if m.Subject != fmt.Sprintf("foo.%v", i) {
t.Fatalf("Expected message for subject foo.%v, got %v", i, m.Subject)
}
m.Ack()
}
}
func TestJetStreamConsumerPullTimeoutHeaders(t *testing.T) {
s := RunBasicJetStreamServer(t)
defer s.Shutdown()
// Client for API requests.
nc, js := jsClientConnect(t, s)
defer nc.Close()
_, err := js.AddStream(&nats.StreamConfig{
Name: "TEST",
Subjects: []string{"foo.>"},
})
require_NoError(t, err)
_, err = js.AddConsumer("TEST", &nats.ConsumerConfig{
Durable: "dlc",
AckPolicy: nats.AckExplicitPolicy,
})
require_NoError(t, err)
nc.Publish("foo.foo", []byte("foo"))
nc.Publish("foo.bar", []byte("bar"))
nc.Publish("foo.else", []byte("baz"))
nc.Flush()
// We will do low level requests by hand for this test as to not depend on any client impl.
rsubj := fmt.Sprintf(JSApiRequestNextT, "TEST", "dlc")
maxBytes := 1024
batch := 50
req := &JSApiConsumerGetNextRequest{Batch: batch, Expires: 100 * time.Millisecond, NoWait: false, MaxBytes: maxBytes}
jreq, err := json.Marshal(req)
require_NoError(t, err)
// Create listener.
reply, msgs := nats.NewInbox(), make(chan *nats.Msg, batch)
sub, err := nc.ChanSubscribe(reply, msgs)
require_NoError(t, err)
defer sub.Unsubscribe()
// Send request.
err = nc.PublishRequest(rsubj, reply, jreq)
require_NoError(t, err)
bytesReceived := 0
messagesReceived := 0
checkHeaders := func(expectedStatus, expectedDesc string, m *nats.Msg) {
t.Helper()
if value := m.Header.Get("Status"); value != expectedStatus {
t.Fatalf("Expected status %q, got %q", expectedStatus, value)
}
if value := m.Header.Get("Description"); value != expectedDesc {
t.Fatalf("Expected description %q, got %q", expectedDesc, value)
}
if value := m.Header.Get(JSPullRequestPendingMsgs); value != fmt.Sprint(batch-messagesReceived) {
t.Fatalf("Expected %d messages, got %s", batch-messagesReceived, value)
}
if value := m.Header.Get(JSPullRequestPendingBytes); value != fmt.Sprint(maxBytes-bytesReceived) {
t.Fatalf("Expected %d bytes, got %s", maxBytes-bytesReceived, value)
}
}
for done := false; !done; {
select {
case m := <-msgs:
if len(m.Data) == 0 && m.Header != nil {
checkHeaders("408", "Request Timeout", m)
done = true
} else {
messagesReceived += 1
bytesReceived += (len(m.Data) + len(m.Header) + len(m.Reply) + len(m.Subject))
}
case <-time.After(100 + 250*time.Millisecond):
t.Fatalf("Did not receive all the msgs in time")
}
}
// Now resend the request but then shutdown the server and
// make sure we have the same info.
err = nc.PublishRequest(rsubj, reply, jreq)
require_NoError(t, err)
natsFlush(t, nc)
s.Shutdown()
// It is possible that the client did not receive, so let's not fail
// on that. But if the 409 indicating the server is shutdown
// is received, then it should have the new headers.
messagesReceived, bytesReceived = 0, 0
select {
case m := <-msgs:
checkHeaders("409", "Server Shutdown", m)
case <-time.After(500 * time.Millisecond):
// we can't fail for that.
t.Logf("Subscription did not receive the pull request response on server shutdown")
}
}
func TestJetStreamConsumerPullBatchCompleted(t *testing.T) {
s := RunBasicJetStreamServer(t)
defer s.Shutdown()
nc, js := jsClientConnect(t, s)
defer nc.Close()
_, err := js.AddStream(&nats.StreamConfig{
Name: "TEST",
Subjects: []string{"foo"},
})
require_NoError(t, err)
msgSize := 128
msg := make([]byte, msgSize)
crand.Read(msg)
for i := 0; i < 10; i++ {
_, err := js.Publish("foo", msg)
require_NoError(t, err)
}
_, err = js.AddConsumer("TEST", &nats.ConsumerConfig{
Durable: "dur",
AckPolicy: nats.AckExplicitPolicy,
})
require_NoError(t, err)
req := JSApiConsumerGetNextRequest{Batch: 0, MaxBytes: 1024, Expires: 250 * time.Millisecond}
reqb, _ := json.Marshal(req)
sub := natsSubSync(t, nc, nats.NewInbox())
err = nc.PublishRequest("$JS.API.CONSUMER.MSG.NEXT.TEST.dur", sub.Subject, reqb)
require_NoError(t, err)
// Expect first message to arrive normally.
_, err = sub.NextMsg(time.Second * 1)
require_NoError(t, err)
// Second message should be info that batch is complete, but there were pending bytes.
pullMsg, err := sub.NextMsg(time.Second * 1)
require_NoError(t, err)
if v := pullMsg.Header.Get("Status"); v != "409" {
t.Fatalf("Expected 409, got: %s", v)
}
if v := pullMsg.Header.Get("Description"); v != "Batch Completed" {
t.Fatalf("Expected Batch Completed, got: %s", v)
}
}
func TestJetStreamConsumerPullLargeBatchExpired(t *testing.T) {
s := RunBasicJetStreamServer(t)
defer s.Shutdown()
nc, js := jsClientConnect(t, s)
defer nc.Close()
_, err := js.AddStream(&nats.StreamConfig{
Name: "TEST",
Subjects: []string{"foo"},
})
if err != nil {
t.Fatalf("add stream failed: %s", err)
}
sub, err := js.PullSubscribe("foo", "dlc", nats.PullMaxWaiting(10), nats.MaxAckPending(10*50_000_000))
if err != nil {
t.Fatalf("Error creating pull subscriber: %v", err)
}
// Queue up 10 batch requests with timeout.
rsubj := fmt.Sprintf(JSApiRequestNextT, "TEST", "dlc")
req := &JSApiConsumerGetNextRequest{Batch: 50_000_000, Expires: 100 * time.Millisecond}
jreq, _ := json.Marshal(req)
for i := 0; i < 10; i++ {
nc.PublishRequest(rsubj, "bar", jreq)
}
nc.Flush()
// Let them all expire.
time.Sleep(150 * time.Millisecond)
// Now do another and measure how long to timeout and shutdown the server.
start := time.Now()
sub.Fetch(1, nats.MaxWait(100*time.Millisecond))
s.Shutdown()
if delta := time.Since(start); delta > 200*time.Millisecond {
t.Fatalf("Took too long to expire: %v", delta)
}
}
func TestJetStreamConsumerPullTimeout(t *testing.T) {
s := RunBasicJetStreamServer(t)
defer s.Shutdown()
nc, js := jsClientConnect(t, s)
defer nc.Close()
_, err := js.AddStream(&nats.StreamConfig{
Name: "TEST",
})
require_NoError(t, err)
_, err = js.AddConsumer("TEST", &nats.ConsumerConfig{
Durable: "pr",
AckPolicy: nats.AckExplicitPolicy,
})
require_NoError(t, err)
const numMessages = 1000
// Send messages in small intervals.
go func() {
for i := 0; i < numMessages; i++ {
time.Sleep(time.Millisecond * 10)
sendStreamMsg(t, nc, "TEST", "data")
}
}()
// Prepare manual Pull Request.
req := &JSApiConsumerGetNextRequest{Batch: 200, NoWait: false, Expires: time.Millisecond * 100}
jreq, _ := json.Marshal(req)
subj := fmt.Sprintf(JSApiRequestNextT, "TEST", "pr")
reply := "_pr_"
var got atomic.Int32
nc.PublishRequest(subj, reply, jreq)
// Manually subscribe to inbox subject and send new request only if we get `408 Request Timeout`.
sub, _ := nc.Subscribe(reply, func(msg *nats.Msg) {
if msg.Header.Get("Status") == "408" && msg.Header.Get("Description") == "Request Timeout" {
nc.PublishRequest(subj, reply, jreq)
nc.Flush()
} else {
got.Add(1)
msg.Ack()
}
})
defer sub.Unsubscribe()
// Check if we're not stuck.
checkFor(t, time.Second*30, time.Second*1, func() error {
if got.Load() < int32(numMessages) {
return fmt.Errorf("expected %d messages", numMessages)
}
return nil
})
}
func TestJetStreamConsumerPullMaxBytes(t *testing.T) {
s := RunBasicJetStreamServer(t)
defer s.Shutdown()
nc, js := jsClientConnect(t, s)
defer nc.Close()
_, err := js.AddStream(&nats.StreamConfig{
Name: "TEST",
})
require_NoError(t, err)
// Put in ~2MB, each ~100k
msz, dsz := 100_000, 99_950
total, msg := 20, []byte(strings.Repeat("Z", dsz))
for i := 0; i < total; i++ {
if _, err := js.Publish("TEST", msg); err != nil {
t.Fatalf("Unexpected publish error: %v", err)
}
}
_, err = js.AddConsumer("TEST", &nats.ConsumerConfig{
Durable: "pr",
AckPolicy: nats.AckExplicitPolicy,
})
require_NoError(t, err)
req := &JSApiConsumerGetNextRequest{MaxBytes: 100, NoWait: true}
jreq, _ := json.Marshal(req)
subj := fmt.Sprintf(JSApiRequestNextT, "TEST", "pr")
reply := "_pr_"
sub, _ := nc.SubscribeSync(reply)
defer sub.Unsubscribe()
checkHeader := func(m *nats.Msg, expected *nats.Header) {
t.Helper()
if len(m.Data) != 0 {
t.Fatalf("Did not expect data, got %d bytes", len(m.Data))
}
expectedStatus, givenStatus := expected.Get("Status"), m.Header.Get("Status")
expectedDesc, givenDesc := expected.Get("Description"), m.Header.Get("Description")
if expectedStatus != givenStatus || expectedDesc != givenDesc {
t.Fatalf("expected %s %s, got %s %s", expectedStatus, expectedDesc, givenStatus, givenDesc)
}
}
// If we ask for less MaxBytes then a single message make sure we get an error.
badReq := &nats.Header{"Status": []string{"409"}, "Description": []string{"Message Size Exceeds MaxBytes"}}
nc.PublishRequest(subj, reply, jreq)
m, err := sub.NextMsg(time.Second)
require_NoError(t, err)
checkSubsPending(t, sub, 0)
checkHeader(m, badReq)
// If we request a ton of max bytes make sure batch size overrides.
req = &JSApiConsumerGetNextRequest{Batch: 1, MaxBytes: 10_000_000, NoWait: true}
jreq, _ = json.Marshal(req)
nc.PublishRequest(subj, reply, jreq)
// we expect two messages, as the second one should be `Batch Completed` status.
checkSubsPending(t, sub, 2)
// first one is message from the stream.
m, err = sub.NextMsg(time.Second)
require_NoError(t, err)
require_True(t, len(m.Data) == dsz)
require_True(t, len(m.Header) == 0)
// second one is the status.
m, err = sub.NextMsg(time.Second)
require_NoError(t, err)
if v := m.Header.Get("Description"); v != "Batch Completed" {
t.Fatalf("Expected Batch Completed, got: %s", v)
}
checkSubsPending(t, sub, 0)
// Same but with batch > 1
req = &JSApiConsumerGetNextRequest{Batch: 5, MaxBytes: 10_000_000, NoWait: true}
jreq, _ = json.Marshal(req)
nc.PublishRequest(subj, reply, jreq)
// 6, not 5, as 6th is the status.
checkSubsPending(t, sub, 6)
for i := 0; i < 5; i++ {
m, err = sub.NextMsg(time.Second)
require_NoError(t, err)
require_True(t, len(m.Data) == dsz)
require_True(t, len(m.Header) == 0)
}
m, err = sub.NextMsg(time.Second)
require_NoError(t, err)
if v := m.Header.Get("Description"); v != "Batch Completed" {
t.Fatalf("Expected Batch Completed, got: %s", v)
}
checkSubsPending(t, sub, 0)
// Now ask for large batch but make sure we are limited by batch size.
req = &JSApiConsumerGetNextRequest{Batch: 1_000, MaxBytes: msz * 4, NoWait: true}
jreq, _ = json.Marshal(req)
nc.PublishRequest(subj, reply, jreq)
// Receive 4 messages + the 409
checkSubsPending(t, sub, 5)
for i := 0; i < 4; i++ {
m, err = sub.NextMsg(time.Second)
require_NoError(t, err)
require_True(t, len(m.Data) == dsz)
require_True(t, len(m.Header) == 0)
}
m, err = sub.NextMsg(time.Second)
require_NoError(t, err)
checkHeader(m, badReq)
checkSubsPending(t, sub, 0)
req = &JSApiConsumerGetNextRequest{Batch: 1_000, MaxBytes: msz, NoWait: true}
jreq, _ = json.Marshal(req)
nc.PublishRequest(subj, reply, jreq)
// Receive 1 message + 409
checkSubsPending(t, sub, 2)
m, err = sub.NextMsg(time.Second)
require_NoError(t, err)
require_True(t, len(m.Data) == dsz)
require_True(t, len(m.Header) == 0)
m, err = sub.NextMsg(time.Second)
require_NoError(t, err)
checkHeader(m, badReq)
checkSubsPending(t, sub, 0)
}
// https://github.com/nats-io/nats-server/issues/6824
func TestJetStreamConsumerDeliverAllOverlappingFilterSubjects(t *testing.T) {
s := RunBasicJetStreamServer(t)
defer s.Shutdown()
nc, js := jsClientConnectNewAPI(t, s)
defer nc.Close()
ctx := context.Background()
_, err := js.CreateOrUpdateStream(ctx, jetstream.StreamConfig{
Name: "TEST",
Subjects: []string{"stream.>"},
})
require_NoError(t, err)
publishMessageCount := 10
for i := 0; i < publishMessageCount; i++ {
_, err = js.Publish(ctx, "stream.A", nil)
require_NoError(t, err)
}
// Create consumer
consumer, err := js.CreateOrUpdateConsumer(ctx, "TEST", jetstream.ConsumerConfig{
DeliverPolicy: jetstream.DeliverAllPolicy,
FilterSubjects: []string{
"stream.A",
"stream.A.>",
},
})
require_NoError(t, err)
messages := make(chan jetstream.Msg)
cc, err := consumer.Consume(func(msg jetstream.Msg) {
messages <- msg
msg.Ack()
})
require_NoError(t, err)
defer cc.Drain()
var count = 0
for {
if count == publishMessageCount {
// All messages received.
return
}
select {
case <-messages:
count++
case <-time.After(2 * time.Second):
t.Errorf("Timeout reached, %d messages received. Exiting.", count)
return
}
}
}
// https://github.com/nats-io/nats-server/issues/6844
func TestJetStreamConsumerDeliverAllNonOverlappingFilterSubjects(t *testing.T) {
s := RunBasicJetStreamServer(t)
defer s.Shutdown()
nc, js := jsClientConnectNewAPI(t, s)
defer nc.Close()
ctx := context.Background()
_, err := js.CreateOrUpdateStream(ctx, jetstream.StreamConfig{
Name: "TEST",
Subjects: []string{"stream.>"},
})
require_NoError(t, err)
publishMessageCount := 10
for i := 0; i < publishMessageCount; i++ {
_, err = js.Publish(ctx, "stream.subject", nil)
require_NoError(t, err)
}
// Create consumer
consumer, err := js.CreateOrUpdateConsumer(ctx, "TEST", jetstream.ConsumerConfig{
DeliverPolicy: jetstream.DeliverAllPolicy,
FilterSubjects: []string{
"stream.subject.A",
"stream.subject.A.>",
},
})
require_NoError(t, err)
i, err := consumer.Info(ctx)
require_NoError(t, err)
require_Equal(t, i.NumPending, 0)
}
// https://github.com/nats-io/nats-server/issues/7336
func TestJetStreamConsumerDeliverPartialOverlappingFilterSubjects(t *testing.T) {
s := RunBasicJetStreamServer(t)
defer s.Shutdown()
nc, js := jsClientConnectNewAPI(t, s)
defer nc.Close()
ctx := context.Background()
_, err := js.CreateOrUpdateStream(ctx, jetstream.StreamConfig{
Name: "TEST",
Subjects: []string{"stream.>"},
})
require_NoError(t, err)
publishMessageCount := 10
for i := 0; i < publishMessageCount; i++ {
_, err = js.Publish(ctx, "stream.A", nil)
require_NoError(t, err)
}
// Create consumer
consumer, err := js.CreateOrUpdateConsumer(ctx, "TEST", jetstream.ConsumerConfig{
DeliverPolicy: jetstream.DeliverAllPolicy,
FilterSubjects: []string{
"stream.A",
"stream.*.A",
},
})
require_NoError(t, err)
messages := make(chan jetstream.Msg)
cc, err := consumer.Consume(func(msg jetstream.Msg) {
messages <- msg
msg.Ack()
})
require_NoError(t, err)
defer cc.Drain()
var count = 0
for {
if count == publishMessageCount {
// All messages received.
return
}
select {
case <-messages:
count++
case <-time.After(2 * time.Second):
t.Errorf("Timeout reached, %d messages received. Exiting.", count)
return
}
}
}
func TestJetStreamConsumerStateAlwaysFromStore(t *testing.T) {
s := RunBasicJetStreamServer(t)
defer s.Shutdown()
nc, js := jsClientConnect(t, s)
defer nc.Close()
_, err := js.AddStream(&nats.StreamConfig{
Name: "TEST",
Subjects: []string{"foo.>"},
})
require_NoError(t, err)
_, err = js.AddConsumer("TEST", &nats.ConsumerConfig{
Durable: "CONSUMER",
AckWait: 2 * time.Second,
AckPolicy: nats.AckExplicitPolicy,
FilterSubject: "foo.bar",
})
require_NoError(t, err)
// Publish two messages, one the consumer is interested in.
_, err = js.Publish("foo.bar", nil)
require_NoError(t, err)
_, err = js.Publish("foo.other", nil)
require_NoError(t, err)
sub, err := js.PullSubscribe("foo.bar", "CONSUMER")
require_NoError(t, err)
defer sub.Drain()
// Consumer info should start empty.
ci, err := js.ConsumerInfo("TEST", "CONSUMER")
require_NoError(t, err)
require_Equal(t, ci.Delivered.Stream, 0)
require_Equal(t, ci.AckFloor.Stream, 0)
// Fetch more messages than match our filter.
msgs, err := sub.Fetch(2, nats.MaxWait(time.Second))
require_NoError(t, err)
require_Len(t, len(msgs), 1)
// We have received, but not acknowledged, consumer info must reflect that.
ci, err = js.ConsumerInfo("TEST", "CONSUMER")
require_NoError(t, err)
require_Equal(t, ci.Delivered.Stream, 1)
require_Equal(t, ci.AckFloor.Stream, 0)
// Now we acknowledge the message and expect our delivered/ackfloor to be correct.
require_NoError(t, msgs[0].AckSync())
ci, err = js.ConsumerInfo("TEST", "CONSUMER")
require_NoError(t, err)
require_Equal(t, ci.Delivered.Stream, 1)
require_Equal(t, ci.AckFloor.Stream, 1)
}
func TestJetStreamConsumerPullNoWaitBatchLargerThanPending(t *testing.T) {
test := func(t *testing.T, replicas int) {
c := createJetStreamClusterExplicit(t, "R3S", 3)
defer c.shutdown()
nc, js := jsClientConnect(t, c.randomServer())
defer nc.Close()
_, err := js.AddStream(&nats.StreamConfig{
Name: "TEST",
Subjects: []string{"foo"},
Replicas: replicas,
})
require_NoError(t, err)
_, err = js.AddConsumer("TEST", &nats.ConsumerConfig{
Durable: "C",
AckPolicy: nats.AckExplicitPolicy,
FilterSubject: "foo",
Replicas: replicas,
})
require_NoError(t, err)
req := JSApiConsumerGetNextRequest{Batch: 10, NoWait: true}
for range 5 {
_, err := js.Publish("foo", []byte("OK"))
require_NoError(t, err)
}
sub := sendRequest(t, nc, "rply", req)
defer sub.Unsubscribe()
// Should get all 5 messages.
for range 5 {
msg, err := sub.NextMsg(time.Second)
require_NoError(t, err)
if len(msg.Data) == 0 && msg.Header != nil {
t.Fatalf("Expected data, got: %s", msg.Header.Get("Description"))
}
}
}
t.Run("R1", func(t *testing.T) { test(t, 1) })
t.Run("R3", func(t *testing.T) { test(t, 3) })
}
func TestJetStreamConsumerNotInactiveDuringAckWait(t *testing.T) {
test := func(t *testing.T, replicas int) {
c := createJetStreamClusterExplicit(t, "R3S", 3)
defer c.shutdown()
nc, js := jsClientConnect(t, c.randomServer())
defer nc.Close()
_, err := js.AddStream(&nats.StreamConfig{
Name: "TEST",
Subjects: []string{"foo"},
Replicas: replicas,
})
require_NoError(t, err)
_, err = js.Publish("foo", nil)
require_NoError(t, err)
_, err = js.AddConsumer("TEST", &nats.ConsumerConfig{
Durable: "CONSUMER",
AckPolicy: nats.AckExplicitPolicy,
Replicas: replicas,
InactiveThreshold: 500 * time.Millisecond, // Pull mode adds up to 1 second randomly.
AckWait: time.Minute,
})
require_NoError(t, err)
_, err = js.ConsumerInfo("TEST", "CONSUMER")
require_NoError(t, err)
sub, err := js.PullSubscribe(_EMPTY_, "CONSUMER", nats.BindStream("TEST"))
require_NoError(t, err)
defer sub.Drain()
msgs, err := sub.Fetch(1)
require_NoError(t, err)
require_Len(t, len(msgs), 1)
_, err = js.ConsumerInfo("TEST", "CONSUMER")
require_NoError(t, err)
// AckWait is still active, so must not delete the consumer while waiting for an ack.
time.Sleep(1750 * time.Millisecond)
_, err = js.ConsumerInfo("TEST", "CONSUMER")
require_NoError(t, err)
require_NoError(t, msgs[0].AckSync())
// Not waiting on AckWait anymore, consumer is deleted after the inactivity threshold.
time.Sleep(1750 * time.Millisecond)
_, err = js.ConsumerInfo("TEST", "CONSUMER")
require_Error(t, err, nats.ErrConsumerNotFound)
}
t.Run("R1", func(t *testing.T) { test(t, 1) })
t.Run("R3", func(t *testing.T) { test(t, 3) })
}
func TestJetStreamConsumerNotInactiveDuringAckWaitBackoff(t *testing.T) {
test := func(t *testing.T, replicas int) {
c := createJetStreamClusterExplicit(t, "R3S", 3)
defer c.shutdown()
nc, js := jsClientConnect(t, c.randomServer())
defer nc.Close()
_, err := js.AddStream(&nats.StreamConfig{
Name: "TEST",
Subjects: []string{"foo"},
Replicas: replicas,
})
require_NoError(t, err)
_, err = js.Publish("foo", nil)
require_NoError(t, err)
_, err = js.AddConsumer("TEST", &nats.ConsumerConfig{
Durable: "CONSUMER",
AckPolicy: nats.AckExplicitPolicy,
Replicas: replicas,
InactiveThreshold: 500 * time.Millisecond, // Pull mode adds up to 1 second randomly.
BackOff: []time.Duration{
2 * time.Second,
4 * time.Second,
},
})
require_NoError(t, err)
_, err = js.ConsumerInfo("TEST", "CONSUMER")
require_NoError(t, err)
sub, err := js.PullSubscribe(_EMPTY_, "CONSUMER", nats.BindStream("TEST"))
require_NoError(t, err)
defer sub.Drain()
msgs, err := sub.Fetch(1)
require_NoError(t, err)
require_Len(t, len(msgs), 1)
_, err = js.ConsumerInfo("TEST", "CONSUMER")
require_NoError(t, err)
// AckWait is still active, so must not delete the consumer while waiting for an ack.
time.Sleep(1750 * time.Millisecond)
_, err = js.ConsumerInfo("TEST", "CONSUMER")
require_NoError(t, err)
require_NoError(t, msgs[0].Nak())
msgs, err = sub.Fetch(1)
require_NoError(t, err)
require_Len(t, len(msgs), 1)
// AckWait is still active, now based on backoff, so must not delete the consumer while waiting for an ack.
// We've confirmed can wait 2s AckWait + InactiveThreshold, now check we can also wait for the backoff.
time.Sleep(3750 * time.Millisecond)
_, err = js.ConsumerInfo("TEST", "CONSUMER")
require_NoError(t, err)
require_NoError(t, msgs[0].AckSync())
// Not waiting on AckWait anymore, consumer is deleted after the inactivity threshold.
time.Sleep(1750 * time.Millisecond)
_, err = js.ConsumerInfo("TEST", "CONSUMER")
require_Error(t, err, nats.ErrConsumerNotFound)
}
t.Run("R1", func(t *testing.T) { test(t, 1) })
t.Run("R3", func(t *testing.T) { test(t, 3) })
}
func TestSortingConsumerPullRequests(t *testing.T) {
for _, test := range []struct {
name string
requests []waitingRequest
expected []waitingRequest
}{
{
name: "sort",
requests: []waitingRequest{
{priorityGroup: &PriorityGroup{Priority: 1}},
{priorityGroup: &PriorityGroup{Priority: 2}},
{priorityGroup: &PriorityGroup{Priority: 1}},
{priorityGroup: &PriorityGroup{Priority: 3}},
},
expected: []waitingRequest{
{priorityGroup: &PriorityGroup{Priority: 1}},
{priorityGroup: &PriorityGroup{Priority: 1}},
{priorityGroup: &PriorityGroup{Priority: 2}},
{priorityGroup: &PriorityGroup{Priority: 3}},
},
},
{
name: "test if sort is stable",
requests: []waitingRequest{
{priorityGroup: &PriorityGroup{Priority: 1}, reply: "1a"},
{priorityGroup: &PriorityGroup{Priority: 2}, reply: "2a"},
{priorityGroup: &PriorityGroup{Priority: 1}, reply: "1b"},
{priorityGroup: &PriorityGroup{Priority: 2}, reply: "2b"},
{priorityGroup: &PriorityGroup{Priority: 1}, reply: "1c"},
{priorityGroup: &PriorityGroup{Priority: 3}, reply: "3a"},
{priorityGroup: &PriorityGroup{Priority: 2}, reply: "2c"},
},
expected: []waitingRequest{
{priorityGroup: &PriorityGroup{Priority: 1}, reply: "1a"},
{priorityGroup: &PriorityGroup{Priority: 1}, reply: "1b"},
{priorityGroup: &PriorityGroup{Priority: 1}, reply: "1c"},
{priorityGroup: &PriorityGroup{Priority: 2}, reply: "2a"},
{priorityGroup: &PriorityGroup{Priority: 2}, reply: "2b"},
{priorityGroup: &PriorityGroup{Priority: 2}, reply: "2c"},
{priorityGroup: &PriorityGroup{Priority: 3}, reply: "3a"},
},
},
} {
t.Run(test.name, func(t *testing.T) {
t.Helper()
wq := newWaitQueue(100)
for _, r := range test.requests {
err := wq.addPrioritized(&r)
require_NoError(t, err)
}
if wq.n != len(test.expected) {
t.Fatalf("Expected %d requests, got %d", len(test.expected), wq.n)
}
// Verify order
for i, expected := range test.expected {
current := wq.peek()
if current == nil {
t.Fatalf("Expected request at position %d, but queue was empty", i)
}
if current.priorityGroup.Priority != expected.priorityGroup.Priority {
t.Fatalf("At position %d: expected priority %d, got %d",
i, expected.priorityGroup.Priority, current.priorityGroup.Priority)
}
if current.reply != expected.reply {
t.Fatalf("At position %d: expected reply %q, got %q", i, expected.reply, current.reply)
}
wq.removeCurrent()
}
})
}
}
func TestWaitQueuePopAndRequeue(t *testing.T) {
t.Run("basic requeue with batches", func(t *testing.T) {
wq := newWaitQueue(100)
// Add elements with different priorities
reqs := []waitingRequest{
{priorityGroup: &PriorityGroup{Priority: 1}, reply: "1a", n: 3},
{priorityGroup: &PriorityGroup{Priority: 1}, reply: "1b", n: 3},
{priorityGroup: &PriorityGroup{Priority: 1}, reply: "1c", n: 3},
{priorityGroup: &PriorityGroup{Priority: 2}, reply: "2a", n: 3},
{priorityGroup: &PriorityGroup{Priority: 2}, reply: "2b", n: 3},
{priorityGroup: &PriorityGroup{Priority: 2}, reply: "2c", n: 3},
{priorityGroup: &PriorityGroup{Priority: 3}, reply: "3a", n: 3},
{priorityGroup: &PriorityGroup{Priority: 3}, reply: "3b", n: 3},
{priorityGroup: &PriorityGroup{Priority: 3}, reply: "3c", n: 3},
}
for i := range reqs {
err := wq.addPrioritized(&reqs[i])
require_NoError(t, err)
}
i := 0
j := 0
for {
wr := wq.popAndRequeue()
require_Equal(t, wr.reply, fmt.Sprintf("%da", j+1))
wr = wq.popAndRequeue()
require_Equal(t, wr.reply, fmt.Sprintf("%db", j+1))
wr = wq.popAndRequeue()
require_Equal(t, wr.reply, fmt.Sprintf("%dc", j+1))
i++
if i%3 == 0 {
j++
}
require_Equal(t, wq.n, 9-(j*3))
if j == 2 {
break
}
}
})
t.Run("request removal when fully served", func(t *testing.T) {
wq := newWaitQueue(100)
reqs := []waitingRequest{
{priorityGroup: &PriorityGroup{Priority: 1}, reply: "1a", n: 2}, // Will be removed after 2 pops
{priorityGroup: &PriorityGroup{Priority: 1}, reply: "1b", n: 1}, // Will be removed after 1 pop
{priorityGroup: &PriorityGroup{Priority: 2}, reply: "2a", n: 3}, // Will remain
}
for i := range reqs {
err := wq.addPrioritized(&reqs[i])
require_NoError(t, err)
}
initialCount := wq.n
// Pop 1a first time (n=2 -> n=1, should requeue)
wr := wq.popAndRequeue()
if wr == nil || wr.reply != "1a" || wr.n != 1 {
t.Fatalf("Expected 1a with n=1, got %v with n=%d", wr, wr.n)
}
if wq.n != initialCount {
t.Fatalf("Queue size should remain %d, got %d", initialCount, wq.n)
}
// Pop 1b (n=1 -> n=0, should be removed)
wr = wq.popAndRequeue()
if wr == nil || wr.reply != "1b" || wr.n != 0 {
t.Fatalf("Expected 1b with n=0, got %v with n=%d", wr, wr.n)
}
if wq.n != initialCount-1 {
t.Fatalf("Queue size should be %d after 1b removal, got %d", initialCount-1, wq.n)
}
// Pop 1a second time (n=1 -> n=0, should be removed)
wr = wq.popAndRequeue()
if wr == nil || wr.reply != "1a" || wr.n != 0 {
t.Fatalf("Expected 1a with n=0, got %v with n=%d", wr, wr.n)
}
if wq.n != initialCount-2 {
t.Fatalf("Queue size should be %d after 1a removal, got %d", initialCount-2, wq.n)
}
// Only 2a should remain
next := wq.peek()
if next == nil || next.reply != "2a" || next.n != 3 {
t.Fatalf("Expected only 2a with n=3 to remain, got %v with n=%d", next, next.n)
}
})
t.Run("single element lifecycle", func(t *testing.T) {
wq := newWaitQueue(10)
req := waitingRequest{priorityGroup: &PriorityGroup{Priority: 1}, reply: "single", n: 2}
wq.add(&req)
// First pop (n=2 -> n=1, should stay)
result := wq.popAndRequeue()
if result == nil || result.reply != "single" || result.n != 1 {
t.Fatalf("Expected single with n=1, got %v with n=%d", result, result.n)
}
if wq.n != 1 {
t.Fatalf("Queue should still have 1 element, got %d", wq.n)
}
// Second pop (n=1 -> n=0, should be removed)
result = wq.popAndRequeue()
if result == nil || result.reply != "single" || result.n != 0 {
t.Fatalf("Expected single with n=0, got %v with n=%d", result, result.n)
}
if wq.n != 0 {
t.Fatalf("Queue should be empty after removal, got %d elements", wq.n)
}
if wq.head != nil {
t.Fatalf("Queue head should be nil after removal, got %v", wq.head)
}
})
}
func TestJetStreamConsumerPrioritized(t *testing.T) {
s := RunBasicJetStreamServer(t)
defer s.Shutdown()
nc, js := jsClientConnect(t, s)
defer nc.Close()
_, err := js.AddStream(&nats.StreamConfig{
Name: "TEST",
Subjects: []string{"foo"},
})
require_NoError(t, err)
acc, err := s.lookupAccount(globalAccountName)
require_NoError(t, err)
mset, err := acc.lookupStream("TEST")
require_NoError(t, err)
_, err = mset.addConsumer(&ConsumerConfig{
Durable: "CONSUMER",
AckPolicy: AckNone,
FilterSubject: "foo",
PriorityGroups: []string{"A"},
PriorityPolicy: PriorityPrioritized,
})
require_NoError(t, err)
o := mset.lookupConsumer("CONSUMER")
require_NotNil(t, o)
// Helper function to send pull request and get response
sendPullRequest := func(t *testing.T, inbox string, priority int, batch int) *nats.Subscription {
sub, err := nc.SubscribeSync(inbox)
require_NoError(t, err)
req := JSApiConsumerGetNextRequest{
Batch: batch,
Expires: 10 * time.Second,
PriorityGroup: PriorityGroup{
Group: "A",
Priority: priority,
},
}
reqb, _ := json.Marshal(req)
err = nc.PublishRequest("$JS.API.CONSUMER.MSG.NEXT.TEST.CONSUMER", inbox, reqb)
require_NoError(t, err)
return sub
}
t.Run("invalid priority number", func(t *testing.T) {
sub := sendPullRequest(t, "invalid_priority", 10, 1)
msg, err := sub.NextMsg(2 * time.Second)
require_NoError(t, err)
require_Equal(t, msg.Header.Get("Status"), "400")
sub2 := sendPullRequest(t, "invalid_priority", -5, 1)
require_Equal(t, msg.Header.Get("Status"), "400")
msg, err = sub2.NextMsg(2 * time.Second)
require_NoError(t, err)
require_Equal(t, msg.Header.Get("Status"), "400")
})
t.Run("priority ordering", func(t *testing.T) {
priority3 := sendPullRequest(t, "priority3", 3, 1) // Priority 3
priority1 := sendPullRequest(t, "priority1", 1, 1) // Priority 1 (should be served first)
priority2 := sendPullRequest(t, "priority2", 2, 2) // Priority 2
// Small delay to ensure requests are processed
time.Sleep(50 * time.Millisecond)
_, err = js.Publish("foo", fmt.Appendf(nil, "message"))
require_NoError(t, err)
// Priority 3 should time out
_, err := priority3.NextMsg(200 * time.Millisecond)
require_Error(t, err, nats.ErrTimeout)
// Priority 2 should time out
_, err = priority2.NextMsg(200 * time.Millisecond)
require_Error(t, err, nats.ErrTimeout)
// Priority 1 should get message first
msg, err := priority1.NextMsg(2 * time.Second)
require_NoError(t, err)
require_NotNil(t, msg)
_, err = js.Publish("foo", fmt.Appendf(nil, "message"))
require_NoError(t, err)
// Priority 3 should time out.
_, err = priority3.NextMsg(200 * time.Millisecond)
require_Error(t, err, nats.ErrTimeout)
// Priority 2 should get message next
msg, err = priority2.NextMsg(2 * time.Second)
require_NoError(t, err)
require_NotNil(t, msg)
_, err = js.Publish("foo", fmt.Appendf(nil, "message"))
require_NoError(t, err)
// Priority 2 should get message next
msg, err = priority2.NextMsg(2 * time.Second)
require_NoError(t, err)
require_NotNil(t, msg)
// Priority 3 should time out
_, err = priority3.NextMsg(2 * time.Second)
require_Error(t, err, nats.ErrTimeout)
_, err = js.Publish("foo", fmt.Appendf(nil, "message"))
require_NoError(t, err)
// Priority 3 should get message next
msg, err = priority3.NextMsg(2 * time.Second)
require_NoError(t, err)
require_NotNil(t, msg)
})
t.Run("dynamic priority interruption", func(t *testing.T) {
inbox3 := nats.NewInbox()
sub3 := sendPullRequest(t, inbox3, 3, 3)
_, err = js.Publish("foo", fmt.Appendf(nil, "msg"))
require_NoError(t, err)
msg, err := sub3.NextMsg(2 * time.Second)
require_NoError(t, err)
require_NotNil(t, msg)
// Now, despite priority 3 still having pending messages, a new pull request
// with a lower priority should be able to take over the delivery.
inbox2 := nats.NewInbox()
sub2 := sendPullRequest(t, inbox2, 2, 2)
_, err = js.Publish("foo", fmt.Appendf(nil, "msg"))
require_NoError(t, err)
msg, err = sub2.NextMsg(2 * time.Second)
require_NoError(t, err)
require_NotNil(t, msg)
// The same should happen with priority 1.
inbox1 := nats.NewInbox()
sub1 := sendPullRequest(t, inbox1, 1, 1) // Priority 1, batch 3
_, err = js.Publish("foo", fmt.Appendf(nil, "msg"))
require_NoError(t, err)
_, err = js.Publish("foo", fmt.Appendf(nil, "msg"))
require_NoError(t, err)
_, err = js.Publish("foo", fmt.Appendf(nil, "msg"))
require_NoError(t, err)
msg, err = sub1.NextMsg(2 * time.Second)
require_NoError(t, err)
require_NotNil(t, msg)
// Now priority 2 should take over for last message.
msg, err = sub2.NextMsg(2 * time.Second)
require_NoError(t, err)
require_NotNil(t, msg)
// Finally, priority 3 should get its last message.
msg, err = sub3.NextMsg(2 * time.Second)
require_NoError(t, err)
require_NotNil(t, msg)
})
}
func TestJetStreamConsumerMaxDeliverUnderflow(t *testing.T) {
s := RunBasicJetStreamServer(t)
defer s.Shutdown()
nc, js := jsClientConnect(t, s)
defer nc.Close()
_, err := js.AddStream(&nats.StreamConfig{Name: "TEST", Subjects: []string{"foo"}})
require_NoError(t, err)
cfg := &nats.ConsumerConfig{Durable: "CONSUMER", MaxDeliver: -1}
_, err = js.AddConsumer("TEST", cfg)
require_NoError(t, err)
mset, err := s.globalAccount().lookupStream("TEST")
require_NoError(t, err)
o := mset.lookupConsumer("CONSUMER")
require_NotNil(t, o)
// Infinite MaxDeliver should be zero.
o.mu.RLock()
maxdc := o.maxdc
o.mu.RUnlock()
require_Equal(t, maxdc, 0)
// Finite MaxDeliver should be reported the same.
cfg.MaxDeliver = 1
_, err = js.UpdateConsumer("TEST", cfg)
require_NoError(t, err)
o.mu.RLock()
maxdc = o.maxdc
o.mu.RUnlock()
require_Equal(t, maxdc, 1)
// Infinite MaxDeliver should be zero.
cfg.MaxDeliver = -1
_, err = js.UpdateConsumer("TEST", cfg)
require_NoError(t, err)
o.mu.RLock()
maxdc = o.maxdc
o.mu.RUnlock()
require_Equal(t, maxdc, 0)
}
// https://github.com/nats-io/nats-server/issues/7457
func TestJetStreamConsumerNoWaitNoMessagesOnEos(t *testing.T) {
s := RunBasicJetStreamServer(t)
defer s.Shutdown()
nc, js := jsClientConnect(t, s)
defer nc.Close()
_, err := js.AddStream(&nats.StreamConfig{Name: "TEST", Subjects: []string{"foo"}})
require_NoError(t, err)
_, err = js.AddConsumer("TEST", &nats.ConsumerConfig{Durable: "CONSUMER"})
require_NoError(t, err)
sub, err := nc.SubscribeSync("reply")
require_NoError(t, err)
defer sub.Drain()
require_NoError(t, nc.Flush())
mset, err := s.globalAccount().lookupStream("TEST")
require_NoError(t, err)
o := mset.lookupConsumer("CONSUMER")
require_NotNil(t, o)
// Fiddle with the pending count such that the NoWait request will go through,
// and the "404 No Messages" will be sent when hitting the end of the stream.
o.mu.Lock()
o.npc++
o.mu.Unlock()
req := &JSApiConsumerGetNextRequest{NoWait: true}
jreq, err := json.Marshal(req)
require_NoError(t, err)
o.processNextMsgRequest("reply", jreq)
msg, err := sub.NextMsg(time.Second)
require_NoError(t, err)
require_Equal(t, msg.Header.Get("Status"), "404")
require_Equal(t, msg.Header.Get("Description"), "No Messages")
}
// https://github.com/nats-io/nats-server/issues/5373
func TestJetStreamConsumerNoWaitNoMessagesOnEosWithDeliveredMsgs(t *testing.T) {
s := RunBasicJetStreamServer(t)
defer s.Shutdown()
nc, js := jsClientConnect(t, s)
defer nc.Close()
_, err := js.AddStream(&nats.StreamConfig{Name: "TEST", Subjects: []string{"foo"}})
require_NoError(t, err)
_, err = js.AddConsumer("TEST", &nats.ConsumerConfig{Durable: "CONSUMER"})
require_NoError(t, err)
_, err = js.Publish("foo", []byte("msg"))
require_NoError(t, err)
sub, err := nc.SubscribeSync("reply")
require_NoError(t, err)
defer sub.Drain()
require_NoError(t, nc.Flush())
mset, err := s.globalAccount().lookupStream("TEST")
require_NoError(t, err)
o := mset.lookupConsumer("CONSUMER")
require_NotNil(t, o)
req := &JSApiConsumerGetNextRequest{NoWait: true, Batch: 2}
jreq, err := json.Marshal(req)
require_NoError(t, err)
o.processNextMsgRequest("reply", jreq)
msg, err := sub.NextMsg(time.Second)
require_NoError(t, err)
require_Equal(t, msg.Subject, "foo")
require_Equal(t, string(msg.Data), "msg")
// We requested two messages but the stream only contained 1.
msg, err = sub.NextMsg(time.Second)
require_NoError(t, err)
require_Equal(t, msg.Header.Get("Status"), "404")
require_Equal(t, msg.Header.Get("Description"), "No Messages")
}
func TestJetStreamConsumerEfficientInterestStateCheck(t *testing.T) {
s := RunBasicJetStreamServer(t)
defer s.Shutdown()
nc, js := jsClientConnect(t, s)
defer nc.Close()
acc := s.GlobalAccount()
mset, err := acc.addStream(&StreamConfig{
Name: "TEST",
Retention: InterestPolicy,
Subjects: []string{"foo"},
})
require_NoError(t, err)
sub, err := js.PullSubscribe(_EMPTY_, "CONSUMER", nats.BindStream("TEST"))
require_NoError(t, err)
defer sub.Drain()
_, _, err = mset.store.StoreMsg("foo", nil, nil, 0)
require_NoError(t, err)
_, _, err = mset.store.StoreMsg("foo", nil, nil, 0)
require_NoError(t, err)
require_NoError(t, mset.store.SkipMsgs(3, 10_000_000))
_, _, err = mset.store.StoreMsg("foo", nil, nil, 0)
require_NoError(t, err)
// Only acknowledge the first message, but the last two will have been marked as delivered.
// This will create a very large gap between the ack floor and the delivered sequence.
msgs, err := sub.Fetch(3)
require_NoError(t, err)
require_Len(t, len(msgs), 3)
require_NoError(t, msgs[0].AckSync())
o := mset.lookupConsumer("CONSUMER")
require_NotNil(t, o)
// We used to do a linear scan, which was terribly slow for this case. Instead,
// we should efficiently skip over deletes or messages that don't match our filter.
var ss StreamState
mset.store.FastState(&ss)
start := time.Now()
require_NoError(t, o.checkStateForInterestStream(&ss))
elapsed := time.Since(start)
require_LessThan(t, elapsed, 50*time.Millisecond)
}
func TestJetStreamConsumerWithCorruptStateIsDeleted(t *testing.T) {
storeDir := t.TempDir()
conf := createConfFile(t, []byte(fmt.Sprintf(`
listen: 127.0.0.1:-1
jetstream: {store_dir: %q}
`, storeDir)))
s, _ := RunServerWithConfig(conf)
defer s.Shutdown()
nc, js := jsClientConnect(t, s)
defer nc.Close()
_, err := js.AddStream(&nats.StreamConfig{
Name: "TEST",
Subjects: []string{"foo"},
})
require_NoError(t, err)
_, err = js.AddConsumer("TEST", &nats.ConsumerConfig{Durable: "DURABLE"})
require_NoError(t, err)
// Consumer should be returned in names and info requests.
var names []string
for name := range js.ConsumerNames("TEST") {
names = append(names, name)
}
require_Len(t, len(names), 1)
require_Equal(t, names[0], "DURABLE")
_, err = js.ConsumerInfo("TEST", "DURABLE")
require_NoError(t, err)
mset, err := s.globalAccount().lookupStream("TEST")
require_NoError(t, err)
o := mset.lookupConsumer("DURABLE")
require_NotNil(t, o)
fs := o.store.(*consumerFileStore)
fs.mu.Lock()
ifn := fs.ifn
fs.mu.Unlock()
// Stop the server and zero the consumer's data file.
s.Shutdown()
stat, err := os.Stat(ifn)
require_NoError(t, err)
require_LessThan(t, 0, stat.Size())
buf := bytes.Repeat([]byte{0}, int(stat.Size()))
require_NoError(t, os.WriteFile(ifn, buf, defaultFilePerms))
s, _ = RunServerWithConfig(conf)
defer s.Shutdown()
// Reconnect.
nc.Close()
nc, js = jsClientConnect(t, s)
defer nc.Close()
// The consumer should be recognized to have corrupt state and be deleted automatically,
// so it doesn't show up in requests.
names = make([]string, 0)
for name := range js.ConsumerNames("TEST") {
names = append(names, name)
}
require_Len(t, len(names), 0)
_, err = js.ConsumerInfo("TEST", "DURABLE")
require_Error(t, err, nats.ErrConsumerNotFound)
}
func TestJetStreamConsumerNoDeleteAfterConcurrentShutdownAndLeaderChange(t *testing.T) {
storeDir := t.TempDir()
conf := createConfFile(t, []byte(fmt.Sprintf(`
listen: 127.0.0.1:-1
jetstream: {store_dir: %q}
`, storeDir)))
s, _ := RunServerWithConfig(conf)
defer s.Shutdown()
mset, err := s.globalAccount().addStream(&StreamConfig{Name: "TEST", Subjects: []string{"foo"}})
require_NoError(t, err)
o, err := mset.addConsumer(&ConsumerConfig{Durable: "CONSUMER", AckPolicy: AckExplicit})
require_NoError(t, err)
fs := o.store.(*consumerFileStore)
fs.mu.Lock()
odir := fs.odir
fs.mu.Unlock()
// Simulate a server shutting down at the same time that a replicated consumer becomes leader.
// Previously the consumer would be deleted due to not being able to create the required subscriptions.
s.Shutdown()
// We'll revert required settings to ensure we can set it as leader.
o.mu.Lock()
o.mset = mset
o.closed = false
o.leader.Store(false)
fs.mu.Lock()
fs.closed = false
fs.odir = odir
fs.mu.Unlock()
o.mu.Unlock()
o.setLeader(true)
// Check the consumer was not deleted.
s, _ = RunServerWithConfig(conf)
defer s.Shutdown()
mset, err = s.globalAccount().lookupStream("TEST")
require_NoError(t, err)
o = mset.lookupConsumer("CONSUMER")
require_NotNil(t, o)
}
func TestJetStreamConsumerOnlyRecalculatePendingIfFilterSubjectUpdated(t *testing.T) {
s := RunBasicJetStreamServer(t)
defer s.Shutdown()
nc, js := jsClientConnect(t, s)
defer nc.Close()
_, err := js.AddStream(&nats.StreamConfig{Name: "TEST", Subjects: []string{"foo"}})
require_NoError(t, err)
cfg := &nats.ConsumerConfig{Durable: "DURABLE"}
_, err = js.AddConsumer("TEST", cfg)
require_NoError(t, err)
// Publishing a message will adjust the pending count for the consumer.
_, err = js.Publish("foo", nil)
require_NoError(t, err)
mset, err := s.globalAccount().lookupStream("TEST")
require_NoError(t, err)
o := mset.lookupConsumer("DURABLE")
require_NotNil(t, o)
o.mu.RLock()
npc, npf := o.npc, o.npf
o.mu.RUnlock()
require_Equal(t, npc, 1)
require_Equal(t, npf, 0)
// Updating a consumer with the same config should NOT recalculate pending.
_, err = js.UpdateConsumer("TEST", cfg)
require_NoError(t, err)
o.mu.RLock()
npc, npf = o.npc, o.npf
o.mu.RUnlock()
require_Equal(t, npc, 1)
require_Equal(t, npf, 0)
// Updating the filter subject should recalculate pending.
cfg.FilterSubject = "foo"
_, err = js.UpdateConsumer("TEST", cfg)
require_NoError(t, err)
o.mu.RLock()
npc, npf = o.npc, o.npf
o.mu.RUnlock()
require_Equal(t, npc, 1)
require_Equal(t, npf, 1)
}
func TestJetStreamConsumerCheckNumPending(t *testing.T) {
s := RunBasicJetStreamServer(t)
defer s.Shutdown()
nc, js := jsClientConnect(t, s)
defer nc.Close()
_, err := js.AddStream(&nats.StreamConfig{Name: "TEST", Subjects: []string{"foo"}})
require_NoError(t, err)
cfg := &nats.ConsumerConfig{Durable: "DURABLE"}
_, err = js.AddConsumer("TEST", cfg)
require_NoError(t, err)
// Publish 5 messages.
for range 5 {
_, err = js.Publish("foo", nil)
require_NoError(t, err)
}
mset, err := s.globalAccount().lookupStream("TEST")
require_NoError(t, err)
o := mset.lookupConsumer("DURABLE")
require_NotNil(t, o)
// Initial state should point to the first message, with all 5 pending.
o.mu.Lock()
sseq, np := o.sseq, o.numPending()
o.mu.Unlock()
require_Equal(t, sseq, 1)
require_Equal(t, np, 5)
// We've sent 2 messages, the next message is seq 3, should return 3 pending.
o.mu.Lock()
o.sseq = 3
np, _ = o.checkNumPending()
o.mu.Unlock()
require_Equal(t, np, 3)
// An excessively large sequence should not panic the num pending correction to 0.
o.mu.Lock()
o.sseq = 100
np, _ = o.checkNumPending()
o.mu.Unlock()
require_Equal(t, np, 0)
// Delete the tail of the stream.
for i := range 4 {
require_NoError(t, js.DeleteMsg("TEST", uint64(i+2)))
}
// We've sent a single message, but that is the only message in the stream.
// We don't fully recalculate the pending count, but should at least return something that's reasonable.
// There's only a single message in the stream, so our pending can't be larger than that.
o.mu.Lock()
o.sseq = 2
o.npc = 4
np, _ = o.checkNumPending()
o.mu.Unlock()
require_Equal(t, np, 1)
}
// https://github.com/nats-io/nats-server/issues/7779
func TestJetStreamConsumerAllowOverlappingSubjectsIfNotSubset(t *testing.T) {
s := RunBasicJetStreamServer(t)
defer s.Shutdown()
nc, js := jsClientConnect(t, s)
defer nc.Close()
_, err := js.AddStream(&nats.StreamConfig{
Name: "TEST",
Subjects: []string{"event.>"},
})
require_NoError(t, err)
parts := []string{"foo", "bar", "baz", "oth"}
for _, start := range parts {
for _, end := range parts {
subj := fmt.Sprintf("event.%s.%s", start, end)
_, err = js.Publish(subj, nil)
require_NoError(t, err)
}
}
// First consumer isn't allowed, since 'event.*.foo' is a subset of 'event.>'.
_, err = js.AddConsumer("TEST", &nats.ConsumerConfig{
Durable: "OVERLAP",
FilterSubjects: []string{
"event.>",
"event.*.foo",
},
})
require_Error(t, err, NewJSConsumerOverlappingSubjectFiltersError())
// The second consumer's subjects do overlap, but they are separate sets (that partially overlap).
_, err = js.AddConsumer("TEST", &nats.ConsumerConfig{
Durable: "DURABLE",
FilterSubjects: []string{
"event.foo.*",
"event.*.foo",
},
})
require_NoError(t, err)
sub, err := js.PullSubscribe(_EMPTY_, "DURABLE", nats.BindStream("TEST"))
require_NoError(t, err)
defer sub.Drain()
// Confirm the consumer gets all messages in the expected order without duplicates.
batch := len(parts) * len(parts)
msgs, err := sub.Fetch(batch, nats.MaxWait(time.Second))
require_NoError(t, err)
var count int
for _, start := range parts {
for _, end := range parts {
if start != "foo" && end != "foo" {
continue
}
count++
require_True(t, len(msgs) >= count)
msg := msgs[count-1]
subj := fmt.Sprintf("event.%s.%s", start, end)
require_Equal(t, msg.Subject, subj)
}
}
require_Equal(t, count, 7)
require_Len(t, len(msgs), count)
}
func TestJetStreamConsumerResetToSequence(t *testing.T) {
test := func(replicas int) {
c := createJetStreamClusterExplicit(t, "R3S", 3)
defer c.shutdown()
nc, js := jsClientConnect(t, c.randomServer())
defer nc.Close()
cfg := &nats.StreamConfig{
Name: "TEST",
Subjects: []string{"foo"},
Replicas: replicas,
}
_, err := js.AddStream(cfg)
require_NoError(t, err)
sub, err := js.PullSubscribe(_EMPTY_, "CONSUMER",
nats.BindStream("TEST"),
nats.MaxAckPending(1),
nats.AckWait(time.Second),
nats.ConsumerReplicas(replicas),
)
require_NoError(t, err)
defer sub.Drain()
for i := range 4 {
_, err = js.Publish("foo", []byte(fmt.Sprintf("msg%d", i+1)))
require_NoError(t, err)
}
// Fetch and ack the first message.
msgs, err := sub.Fetch(1, nats.MaxWait(time.Second))
require_NoError(t, err)
require_Len(t, len(msgs), 1)
require_Equal(t, string(msgs[0].Data), "msg1")
require_NoError(t, msgs[0].AckSync())
// Now switch the stream to interest now that the messages and consumer are all available.
cfg.Retention = nats.InterestPolicy
_, err = js.UpdateStream(cfg)
require_NoError(t, err)
sl := c.streamLeader(globalAccountName, "TEST")
checkFor(t, 2*time.Second, 200*time.Millisecond, func() error {
mset, err := sl.globalAccount().lookupStream("TEST")
if err != nil {
return err
}
state := mset.state()
if state.FirstSeq != 2 {
return fmt.Errorf("expected first seq to be 2, got %v", state)
}
return checkState(t, c, globalAccountName, "TEST")
})
s := c.consumerLeader(globalAccountName, "TEST", "CONSUMER")
mset, err := s.globalAccount().lookupStream("TEST")
require_NoError(t, err)
o := mset.lookupConsumer("CONSUMER")
require_NotNil(t, o)
type Expected struct{ numPending, numAckPending, dseq, adflr, sseq, asflr uint64 }
checkConsumerInfo := func(e Expected) {
t.Helper()
ci, err := js.ConsumerInfo("TEST", "CONSUMER")
require_NoError(t, err)
o.mu.RLock()
sseq, dseq, adflr, asflr, pending := o.sseq, o.dseq, o.adflr, o.asflr, len(o.pending)
o.mu.RUnlock()
require_Equal(t, ci.NumPending, e.numPending)
// NumAckPending needs to match both in the store and running state.
require_Equal(t, ci.NumAckPending, int(e.numAckPending))
require_Equal(t, pending, int(e.numAckPending))
// Delivered.Consumer needs to match both in the store and running state.
require_Equal(t, ci.Delivered.Consumer, e.dseq)
require_Equal(t, dseq-1, e.dseq)
// AckFloor.Consumer needs to match both in the store and running state.
require_Equal(t, ci.AckFloor.Consumer, e.adflr)
require_Equal(t, adflr, e.adflr)
// Delivered.Stream needs to match both in the store and running state.
require_Equal(t, ci.Delivered.Stream, e.sseq)
require_Equal(t, sseq-1, e.sseq)
// AckFloor.Stream needs to match both in the store and running state.
require_Equal(t, ci.AckFloor.Stream, e.asflr)
require_Equal(t, asflr, e.asflr)
}
// A single message was acked, 3 are ready to be delivered.
checkConsumerInfo(Expected{
numPending: 3, numAckPending: 0,
dseq: 1, adflr: 1,
sseq: 1, asflr: 1,
})
msgs, err = sub.Fetch(1, nats.MaxWait(time.Second))
require_NoError(t, err)
require_Len(t, len(msgs), 1)
require_Equal(t, string(msgs[0].Data), "msg2")
// Don't ack.
// A message has been delivered and needs to be acked still.
checkConsumerInfo(Expected{
numPending: 2, numAckPending: 1,
dseq: 2, adflr: 1,
sseq: 2, asflr: 1,
})
// Resetting the consumer with an empty request results in a reset back to the ack floor.
var resp JSApiConsumerResetResponse
msg, err := nc.Request("$JS.API.CONSUMER.RESET.TEST.CONSUMER", nil, time.Second)
require_NoError(t, err)
require_NoError(t, json.Unmarshal(msg.Data, &resp))
require_Equal(t, resp.Type, JSApiConsumerResetResponseType)
require_Equal(t, resp.ResetSeq, 2)
require_Equal(t, resp.Delivered.Stream, 1)
require_Equal(t, resp.Delivered.Last, nil)
require_Equal(t, resp.AckFloor.Stream, 1)
require_Equal(t, resp.AckFloor.Last, nil)
// Should be back.
require_Equal(t, resp.NumPending, 3)
checkConsumerInfo(Expected{
numPending: 3, numAckPending: 0,
dseq: 0, adflr: 0,
sseq: 1, asflr: 1,
})
// Trying to reset to zero also resets back to the ack floor.
req := JSApiConsumerResetRequest{Seq: 0}
data, err := json.Marshal(req)
require_NoError(t, err)
resp = JSApiConsumerResetResponse{}
msg, err = nc.Request("$JS.API.CONSUMER.RESET.TEST.CONSUMER", data, time.Second)
require_NoError(t, err)
require_NoError(t, json.Unmarshal(msg.Data, &resp))
require_Equal(t, resp.Type, JSApiConsumerResetResponseType)
require_Equal(t, resp.ResetSeq, 2)
require_Equal(t, resp.Delivered.Stream, 1)
require_Equal(t, resp.Delivered.Last, nil)
require_Equal(t, resp.AckFloor.Stream, 1)
require_Equal(t, resp.AckFloor.Last, nil)
require_Equal(t, resp.NumPending, 3)
checkConsumerInfo(Expected{
numPending: 3, numAckPending: 0,
dseq: 0, adflr: 0,
sseq: 1, asflr: 1,
})
// Resetting the consumer to the last message's sequence so it can be delivered still.
req = JSApiConsumerResetRequest{Seq: 4}
data, err = json.Marshal(req)
require_NoError(t, err)
resp = JSApiConsumerResetResponse{}
msg, err = nc.Request("$JS.API.CONSUMER.RESET.TEST.CONSUMER", data, time.Second)
require_NoError(t, err)
require_NoError(t, json.Unmarshal(msg.Data, &resp))
require_Equal(t, resp.Type, JSApiConsumerResetResponseType)
require_Equal(t, resp.ResetSeq, 4)
require_Equal(t, resp.Delivered.Stream, 3)
require_Equal(t, resp.Delivered.Last, nil)
require_Equal(t, resp.AckFloor.Stream, 3)
require_Equal(t, resp.AckFloor.Last, nil)
// One message left as pending, the delivered counts are now zero,
// but the sequence and AckFloor are moved up.
checkConsumerInfo(Expected{
numPending: 1, numAckPending: 0,
dseq: 0, adflr: 0,
sseq: 3, asflr: 3,
})
// As a result of moving the starting sequence up, some messages
// have now lost interest and need to be removed.
checkFor(t, 2*time.Second, 200*time.Millisecond, func() error {
mset, err := sl.globalAccount().lookupStream("TEST")
if err != nil {
return err
}
state := mset.state()
if state.FirstSeq != 4 {
return fmt.Errorf("expected first seq to be 4, got %v", state)
}
return checkState(t, c, globalAccountName, "TEST")
})
// We fetch the last message.
msgs, err = sub.Fetch(1, nats.MaxWait(2*time.Second))
require_NoError(t, err)
require_Len(t, len(msgs), 1)
require_Equal(t, string(msgs[0].Data), "msg4")
checkConsumerInfo(Expected{
numPending: 0, numAckPending: 1,
dseq: 1, adflr: 0,
sseq: 4, asflr: 3,
})
// After acking, the delivered count should only show 1, but the AckFloor is higher.
require_NoError(t, msgs[0].AckSync())
checkConsumerInfo(Expected{
numPending: 0, numAckPending: 0,
dseq: 1, adflr: 1,
sseq: 4, asflr: 4,
})
// The stream should now be empty.
checkFor(t, 2*time.Second, 200*time.Millisecond, func() error {
mset, err := sl.globalAccount().lookupStream("TEST")
if err != nil {
return err
}
state := mset.state()
if state.Msgs != 0 || state.FirstSeq != 5 {
return fmt.Errorf("stream is not empty: %v", state)
}
return checkState(t, c, globalAccountName, "TEST")
})
}
for _, replicas := range []int{1, 3} {
t.Run(fmt.Sprintf("R%d", replicas), func(t *testing.T) {
test(replicas)
})
}
}
func TestJetStreamConsumerResetToSequenceConstraintOnStartSeq(t *testing.T) {
s := RunBasicJetStreamServer(t)
defer s.Shutdown()
nc, js := jsClientConnect(t, s)
defer nc.Close()
cfg := &nats.StreamConfig{
Name: "TEST",
Subjects: []string{"foo"},
}
_, err := js.AddStream(cfg)
require_NoError(t, err)
sub, err := js.PullSubscribe(_EMPTY_, "CONSUMER",
nats.BindStream("TEST"),
nats.StartSequence(3),
)
require_NoError(t, err)
defer sub.Drain()
for i := range 4 {
_, err = js.Publish("foo", []byte(fmt.Sprintf("msg%d", i+1)))
require_NoError(t, err)
}
// Fetch and ack the first message.
msgs, err := sub.Fetch(1, nats.MaxWait(time.Second))
require_NoError(t, err)
require_Len(t, len(msgs), 1)
require_Equal(t, string(msgs[0].Data), "msg3")
require_NoError(t, msgs[0].AckSync())
// Trying to reset below the configured starting sequence fails.
req := JSApiConsumerResetRequest{Seq: 2}
data, err := json.Marshal(req)
require_NoError(t, err)
var resp JSApiConsumerResetResponse
msg, err := nc.Request("$JS.API.CONSUMER.RESET.TEST.CONSUMER", data, time.Second)
require_NoError(t, err)
require_NoError(t, json.Unmarshal(msg.Data, &resp))
require_Equal(t, resp.Type, JSApiConsumerResetResponseType)
require_NotNil(t, resp.Error)
require_Error(t, resp.Error, NewJSConsumerInvalidResetError(errors.New("below start seq")))
// Resetting above or equal to the configured starting sequence succeeds.
req = JSApiConsumerResetRequest{Seq: 3}
data, err = json.Marshal(req)
require_NoError(t, err)
resp = JSApiConsumerResetResponse{}
msg, err = nc.Request("$JS.API.CONSUMER.RESET.TEST.CONSUMER", data, time.Second)
require_NoError(t, err)
require_NoError(t, json.Unmarshal(msg.Data, &resp))
require_Equal(t, resp.Type, JSApiConsumerResetResponseType)
require_Equal(t, resp.ResetSeq, 3)
require_Equal(t, resp.Delivered.Stream, 2)
require_Equal(t, resp.Delivered.Last, nil)
require_Equal(t, resp.AckFloor.Stream, 2)
require_Equal(t, resp.AckFloor.Last, nil)
// Fetching should now get the same message.
msgs, err = sub.Fetch(1, nats.MaxWait(time.Second))
require_NoError(t, err)
require_Len(t, len(msgs), 1)
require_Equal(t, string(msgs[0].Data), "msg3")
require_NoError(t, msgs[0].AckSync())
}
func TestJetStreamConsumerResetToSequenceConstraintOnStartTime(t *testing.T) {
s := RunBasicJetStreamServer(t)
defer s.Shutdown()
nc, js := jsClientConnect(t, s)
defer nc.Close()
cfg := &nats.StreamConfig{
Name: "TEST",
Subjects: []string{"foo"},
}
_, err := js.AddStream(cfg)
require_NoError(t, err)
for i := range 2 {
_, err = js.Publish("foo", []byte(fmt.Sprintf("msg%d", i+1)))
require_NoError(t, err)
}
sub, err := js.PullSubscribe(_EMPTY_, "CONSUMER",
nats.BindStream("TEST"),
nats.StartTime(time.Now()),
)
require_NoError(t, err)
defer sub.Drain()
for i := range 2 {
_, err = js.Publish("foo", []byte(fmt.Sprintf("msg%d", i+3)))
require_NoError(t, err)
}
// Fetch and ack the first message.
msgs, err := sub.Fetch(1, nats.MaxWait(time.Second))
require_NoError(t, err)
require_Len(t, len(msgs), 1)
require_Equal(t, string(msgs[0].Data), "msg3")
require_NoError(t, msgs[0].AckSync())
// Trying to reset below the configured starting time fails.
req := JSApiConsumerResetRequest{Seq: 2}
data, err := json.Marshal(req)
require_NoError(t, err)
var resp JSApiConsumerResetResponse
msg, err := nc.Request("$JS.API.CONSUMER.RESET.TEST.CONSUMER", data, time.Second)
require_NoError(t, err)
require_NoError(t, json.Unmarshal(msg.Data, &resp))
require_Equal(t, resp.Type, JSApiConsumerResetResponseType)
require_NotNil(t, resp.Error)
require_Error(t, resp.Error, NewJSConsumerInvalidResetError(errors.New("below start time")))
// Resetting to a sequence that has a starting time above or equal to the configured starting time succeeds.
req = JSApiConsumerResetRequest{Seq: 3}
data, err = json.Marshal(req)
require_NoError(t, err)
resp = JSApiConsumerResetResponse{}
msg, err = nc.Request("$JS.API.CONSUMER.RESET.TEST.CONSUMER", data, time.Second)
require_NoError(t, err)
require_NoError(t, json.Unmarshal(msg.Data, &resp))
require_Equal(t, resp.Type, JSApiConsumerResetResponseType)
require_Equal(t, resp.ResetSeq, 3)
require_Equal(t, resp.Delivered.Stream, 2)
require_Equal(t, resp.Delivered.Last, nil)
require_Equal(t, resp.AckFloor.Stream, 2)
require_Equal(t, resp.AckFloor.Last, nil)
// Fetching should now get the same message.
msgs, err = sub.Fetch(1, nats.MaxWait(time.Second))
require_NoError(t, err)
require_Len(t, len(msgs), 1)
require_Equal(t, string(msgs[0].Data), "msg3")
require_NoError(t, msgs[0].AckSync())
}
// https://github.com/nats-io/nats-server/issues/7847
func TestJetStreamConsumerLegacyDurableCreateSetsConsumerName(t *testing.T) {
s := RunBasicJetStreamServer(t)
defer s.Shutdown()
nc, js := jsClientConnect(t, s)
defer nc.Close()
cfg := &nats.StreamConfig{
Name: "TEST",
Subjects: []string{"foo"},
}
_, err := js.AddStream(cfg)
require_NoError(t, err)
cc := CreateConsumerRequest{
Stream: "TEST",
Config: ConsumerConfig{
Durable: "CONSUMER",
AckPolicy: AckExplicit,
Replicas: 1,
},
Action: ActionCreate,
}
req, err := json.Marshal(cc)
require_NoError(t, err)
msg, err := nc.Request("$JS.API.CONSUMER.CREATE.TEST.CONSUMER", req, time.Second)
require_NoError(t, err)
var resp JSApiConsumerCreateResponse
require_NoError(t, json.Unmarshal(msg.Data, &resp))
require_True(t, resp.Error == nil)
require_Equal(t, resp.Config.Durable, "CONSUMER")
require_Equal(t, resp.Config.Name, "CONSUMER")
msg, err = nc.Request("$JS.API.CONSUMER.DURABLE.CREATE.TEST.CONSUMER", req, time.Second)
require_NoError(t, err)
resp = JSApiConsumerCreateResponse{}
require_NoError(t, json.Unmarshal(msg.Data, &resp))
require_True(t, resp.Error == nil)
require_Equal(t, resp.Config.Durable, "CONSUMER")
require_Equal(t, resp.Config.Name, "CONSUMER")
}
// https://github.com/nats-io/nats-server/issues/7852
func TestJetStreamConsumerSingleFilterSubjectInFilterSubjects(t *testing.T) {
s := RunBasicJetStreamServer(t)
defer s.Shutdown()
nc, js := jsClientConnect(t, s)
defer nc.Close()
_, err := js.AddStream(&nats.StreamConfig{
Name: "TEST",
Subjects: []string{"foo"},
})
require_NoError(t, err)
_, err = js.AddConsumer("TEST", &nats.ConsumerConfig{
Durable: "CONSUMER",
FilterSubjects: []string{"foo"},
})
require_NoError(t, err)
mset, err := s.globalAccount().lookupStream("TEST")
require_NoError(t, err)
o := mset.lookupConsumer("CONSUMER")
require_NotNil(t, o)
o.mu.RLock()
defer o.mu.RUnlock()
// Should not initialize the sublist, as that will make us use LoadNextMsgMulti versus LoadNextMsg.
require_Len(t, len(o.subjf), 1)
require_True(t, o.filters == nil)
}