Merge branch 'main' of https://github.com/nats-io/nats.go into jsv2

Improve readme

Signed-off-by: Piotr Piotrowski <piotr@synadia.com>

Fix msg size calculation

Signed-off-by: Piotr Piotrowski <piotr@synadia.com>

Add connection event listeners

Signed-off-by: Piotr Piotrowski <piotr@synadia.com>

Fix leaky goroutines in tests

Signed-off-by: Piotr Piotrowski <piotr@synadia.com>
This commit is contained in:
Piotr Piotrowski
2023-05-16 12:50:12 +02:00
12 changed files with 174 additions and 65 deletions

1
go.mod
View File

@@ -8,6 +8,7 @@ require (
)
require (
github.com/klauspost/compress v1.16.5 // indirect
golang.org/x/crypto v0.6.0 // indirect
golang.org/x/sys v0.5.0 // indirect
)

2
go.sum
View File

@@ -1,3 +1,5 @@
github.com/klauspost/compress v1.16.5 h1:IFV2oUNUzZaz+XyusxpLzpzS8Pt5rh0Z16For/djlyI=
github.com/klauspost/compress v1.16.5/go.mod h1:ntbaceVETuRiXiv4DpjP66DpAtAGkEQskQzEyD//IeE=
github.com/nats-io/nkeys v0.4.4 h1:xvBJ8d69TznjcQl9t6//Q5xXuVhyYiSos6RPtvQNTwA=
github.com/nats-io/nkeys v0.4.4/go.mod h1:XUkxdLPTufzlihbamfzQ7mw/VGx6ObUs+0bN5sNvt64=
github.com/nats-io/nuid v1.0.1 h1:5iA8DT8V7q8WK2EScv2padNa/rTESc1KdnPw4TC2paw=

View File

@@ -4,7 +4,8 @@ go 1.19
require (
github.com/golang/protobuf v1.4.2
github.com/nats-io/nats-server/v2 v2.9.6
github.com/klauspost/compress v1.16.5
github.com/nats-io/nats-server/v2 v2.9.16
github.com/nats-io/nkeys v0.4.4
github.com/nats-io/nuid v1.0.1
golang.org/x/text v0.3.6
@@ -12,10 +13,9 @@ require (
)
require (
github.com/klauspost/compress v1.15.11 // indirect
github.com/minio/highwayhash v1.0.2 // indirect
github.com/nats-io/jwt/v2 v2.3.0 // indirect
golang.org/x/crypto v0.6.0 // indirect
golang.org/x/sys v0.5.0 // indirect
golang.org/x/time v0.0.0-20220922220347-f3bd1da661af // indirect
github.com/nats-io/jwt/v2 v2.4.1 // indirect
golang.org/x/crypto v0.8.0 // indirect
golang.org/x/sys v0.7.0 // indirect
golang.org/x/time v0.3.0 // indirect
)

View File

@@ -9,34 +9,25 @@ github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMyw
github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
github.com/google/go-cmp v0.4.0 h1:xsAVV57WRhGj6kEIi8ReJzQlHHqcBYCElAvkovg3B/4=
github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/klauspost/compress v1.15.11 h1:Lcadnb3RKGin4FYM/orgq0qde+nc15E5Cbqg4B9Sx9c=
github.com/klauspost/compress v1.15.11/go.mod h1:QPwzmACJjUTFsnSHH934V6woptycfrDDJnH7hvFVbGM=
github.com/klauspost/compress v1.16.5 h1:IFV2oUNUzZaz+XyusxpLzpzS8Pt5rh0Z16For/djlyI=
github.com/klauspost/compress v1.16.5/go.mod h1:ntbaceVETuRiXiv4DpjP66DpAtAGkEQskQzEyD//IeE=
github.com/minio/highwayhash v1.0.2 h1:Aak5U0nElisjDCfPSG79Tgzkn2gl66NxOMspRrKnA/g=
github.com/minio/highwayhash v1.0.2/go.mod h1:BQskDq+xkJ12lmlUUi7U0M5Swg3EWR+dLTk+kldvVxY=
github.com/nats-io/jwt/v2 v2.3.0 h1:z2mA1a7tIf5ShggOFlR1oBPgd6hGqcDYsISxZByUzdI=
github.com/nats-io/jwt/v2 v2.3.0/go.mod h1:0tqz9Hlu6bCBFLWAASKhE5vUA4c24L9KPUUgvwumE/k=
github.com/nats-io/nats-server/v2 v2.9.6 h1:RTtK+rv/4CcliOuqGsy58g7MuWkBaWmF5TUNwuUo9Uw=
github.com/nats-io/nats-server/v2 v2.9.6/go.mod h1:AB6hAnGZDlYfqb7CTAm66ZKMZy9DpfierY1/PbpvI2g=
github.com/nats-io/nkeys v0.3.0/go.mod h1:gvUNGjVcM2IPr5rCsRsC6Wb3Hr2CQAm08dsxtV6A5y4=
github.com/nats-io/jwt/v2 v2.4.1 h1:Y35W1dgbbz2SQUYDPCaclXcuqleVmpbRa7646Jf2EX4=
github.com/nats-io/jwt/v2 v2.4.1/go.mod h1:24BeQtRwxRV8ruvC4CojXlx/WQ/VjuwlYiH+vu/+ibI=
github.com/nats-io/nats-server/v2 v2.9.16 h1:SuNe6AyCcVy0g5326wtyU8TdqYmcPqzTjhkHojAjprc=
github.com/nats-io/nats-server/v2 v2.9.16/go.mod h1:z1cc5Q+kqJkz9mLUdlcSsdYnId4pyImHjNgoh6zxSC0=
github.com/nats-io/nkeys v0.4.4 h1:xvBJ8d69TznjcQl9t6//Q5xXuVhyYiSos6RPtvQNTwA=
github.com/nats-io/nkeys v0.4.4/go.mod h1:XUkxdLPTufzlihbamfzQ7mw/VGx6ObUs+0bN5sNvt64=
github.com/nats-io/nuid v1.0.1 h1:5iA8DT8V7q8WK2EScv2padNa/rTESc1KdnPw4TC2paw=
github.com/nats-io/nuid v1.0.1/go.mod h1:19wcPz3Ph3q0Jbyiqsd0kePYG7A95tJPxeL+1OSON2c=
golang.org/x/crypto v0.0.0-20210314154223-e6e6c4f2bb5b/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4=
golang.org/x/crypto v0.6.0 h1:qfktjS5LUO+fFKeJXZ+ikTRijMmljikvG68fpMMruSc=
golang.org/x/crypto v0.6.0/go.mod h1:OFC/31mSvZgRz0V1QTNCzfAI1aIRzbiufJtkMIlEp58=
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
golang.org/x/crypto v0.8.0 h1:pd9TJtTueMTVQXzk8E2XESSMQDj/U7OUu0PqJqPXQjQ=
golang.org/x/crypto v0.8.0/go.mod h1:mRqEX+O9/h5TFCrQhkgjo2yKi0yYA+9ecGkdQoHrywE=
golang.org/x/sys v0.0.0-20190130150945-aca44879d564/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.5.0 h1:MUK/U/4lj1t1oPg0HfuXDN/Z1wv31ZJ/YcPiGccS4DU=
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.6 h1:aRYxNxv6iGQlyVaZmk6ZgYEDa+Jg18DxebPSrd6bg1M=
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/time v0.0.0-20220922220347-f3bd1da661af h1:Yx9k8YCG3dvF87UAn2tu2HQLf2dt/eR1bXxpLMWeH+Y=
golang.org/x/time v0.0.0-20220922220347-f3bd1da661af/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/sys v0.7.0 h1:3jlCCIQZPdOYu1h8BkNvLz8Kgwtae2cagcG/VamtZRU=
golang.org/x/sys v0.7.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/time v0.3.0 h1:rg5rLMjNzMS1RkNLzCG38eapWhnYLFYXDXj2gOlr8j4=
golang.org/x/time v0.3.0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8=

60
js.go
View File

@@ -1660,8 +1660,14 @@ func (js *js) subscribe(subj, queue string, cb MsgHandler, ch chan *Msg, isSync,
}
// If we are creating or updating let's process that request.
consName := o.cfg.Name
if shouldCreate {
info, err := js.upsertConsumer(stream, cfg.Durable, ccreq.Config)
if cfg.Durable != "" {
consName = cfg.Durable
} else if consName == "" {
consName = getHash(nuid.Next())
}
info, err := js.upsertConsumer(stream, consName, ccreq.Config)
if err != nil {
var apiErr *APIError
if ok := errors.As(err, &apiErr); !ok {
@@ -1964,40 +1970,22 @@ func (sub *Subscription) resetOrderedConsumer(sseq uint64) {
cfg.DeliverPolicy = DeliverByStartSequencePolicy
cfg.OptStartSeq = sseq
ccSubj := fmt.Sprintf(apiLegacyConsumerCreateT, jsi.stream)
j, err := json.Marshal(jsi.ccreq)
js := jsi.js
sub.mu.Unlock()
consName := nuid.Next()
cinfo, err := js.upsertConsumer(jsi.stream, consName, cfg)
if err != nil {
pushErr(err)
return
}
resp, err := nc.Request(js.apiSubj(ccSubj), j, js.opts.wait)
if err != nil {
if errors.Is(err, ErrNoResponders) || errors.Is(err, ErrTimeout) {
var apiErr *APIError
if errors.Is(err, ErrJetStreamNotEnabled) || errors.Is(err, ErrTimeout) {
// if creating consumer failed, retry
return
}
pushErr(err)
return
}
var cinfo consumerResponse
err = json.Unmarshal(resp.Data, &cinfo)
if err != nil {
pushErr(err)
return
}
if cinfo.Error != nil {
if cinfo.Error.ErrorCode == JSErrCodeInsufficientResourcesErr {
} else if errors.As(err, &apiErr) && apiErr.ErrorCode == JSErrCodeInsufficientResourcesErr {
// retry for insufficient resources, as it may mean that client is connected to a running
// server in cluster while the server hosting R1 JetStream resources is restarting
return
}
pushErr(cinfo.Error)
pushErr(err)
return
}
@@ -2489,6 +2477,14 @@ func ConsumerMemoryStorage() SubOpt {
})
}
// ConsumerName sets the name for a consumer.
func ConsumerName(name string) SubOpt {
return subOptFn(func(opts *subOpts) error {
opts.cfg.Name = name
return nil
})
}
func (sub *Subscription) ConsumerInfo() (*ConsumerInfo, error) {
sub.mu.Lock()
// TODO(dlc) - Better way to mark especially if we attach.
@@ -3586,3 +3582,17 @@ func (st *StorageType) UnmarshalJSON(data []byte) error {
}
return nil
}
// Length of our hash used for named consumers.
const nameHashLen = 8
// Computes a hash for the given `name`.
func getHash(name string) string {
sha := sha256.New()
sha.Write([]byte(name))
b := sha.Sum(nil)
for i := 0; i < nameHashLen; i++ {
b[i] = rdigits[int(b[i]%base)]
}
return string(b[:nameHashLen])
}

View File

@@ -369,7 +369,7 @@ func TestJetStreamOrderedConsumerDeleteAssets(t *testing.T) {
t.Run("remove consumer, expect it to be recreated", func(t *testing.T) {
createStream()
createConsSub, err := nc.SubscribeSync("$JS.API.CONSUMER.CREATE.OBJECT")
createConsSub, err := nc.SubscribeSync("$JS.API.CONSUMER.CREATE.OBJECT.*.a")
if err != nil {
t.Fatalf("Unexpected error: %v", err)
}

View File

@@ -369,13 +369,19 @@ func (obs *obs) Put(meta *ObjectMeta, r io.Reader, opts ...ObjectOpt) (*ObjectIn
return perr
}
purgePartial := func() { obs.js.purgeStream(obs.stream, &StreamPurgeRequest{Subject: chunkSubj}) }
// Create our own JS context to handle errors etc.
js, err := obs.js.nc.JetStream(PublishAsyncErrHandler(func(js JetStream, _ *Msg, err error) { setErr(err) }))
if err != nil {
return nil, err
}
purgePartial := func() {
// wait until all pubs are complete or up to default timeout before attempting purge
select {
case <-js.PublishAsyncComplete():
case <-time.After(obs.js.opts.wait):
}
obs.js.purgeStream(obs.stream, &StreamPurgeRequest{Subject: chunkSubj})
}
m, h := NewMsg(chunkSubj), sha256.New()
chunk, sent, total := make([]byte, meta.Opts.ChunkSize), 0, uint64(0)
@@ -1208,7 +1214,7 @@ func (o *objResult) Read(p []byte) (n int, err error) {
}
}
if o.err != nil {
return 0, err
return 0, o.err
}
if o.r == nil {
return 0, io.EOF

View File

@@ -1,4 +1,4 @@
// Copyright 2012-2122 The NATS Authors
// Copyright 2012-2023 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

View File

@@ -8789,3 +8789,95 @@ func TestJetStreamStreamInfoAlternates(t *testing.T) {
}
})
}
func TestJetStreamSubscribeConsumerName(t *testing.T) {
s := RunBasicJetStreamServer()
defer shutdownJSServerAndRemoveStorage(t, s)
nc, js := jsClient(t, s)
defer nc.Close()
var err error
// Create the stream using our client API.
_, err = js.AddStream(&nats.StreamConfig{
Name: "TEST",
Subjects: []string{"foo", "bar", "baz", "foo.*"},
})
if err != nil {
t.Fatalf("Unexpected error: %v", err)
}
_, err = js.Publish("foo", []byte("first"))
if err != nil {
t.Fatal(err)
}
// Lookup the stream for testing.
_, err = js.StreamInfo("TEST")
if err != nil {
t.Fatalf("stream lookup failed: %v", err)
}
sub, err := js.SubscribeSync("foo", nats.ConsumerName("my-ephemeral"))
if err != nil {
t.Fatal(err)
}
cinfo, err := sub.ConsumerInfo()
if err != nil {
t.Fatal(err)
}
got := cinfo.Config.Name
expected := "my-ephemeral"
if got != expected {
t.Fatalf("Expected: %v, got: %v", expected, got)
}
// Confirm that this is a durable.
got = cinfo.Config.Durable
expected = ""
if got != expected {
t.Fatalf("Expected: %v, got: %v", expected, got)
}
_, err = sub.NextMsg(1 * time.Second)
if err != nil {
t.Fatal(err)
}
// ConsumerName will be ignored in case a durable name has been set.
sub, err = js.SubscribeSync("foo", nats.Durable("durable"), nats.ConsumerName("custom-name"))
if err != nil {
t.Fatal(err)
}
cinfo, err = sub.ConsumerInfo()
if err != nil {
t.Fatal(err)
}
got = cinfo.Config.Name
expected = "durable"
if got != expected {
t.Fatalf("Expected: %v, got: %v", expected, got)
}
got = cinfo.Config.Durable
expected = "durable"
if got != expected {
t.Fatalf("Expected: %v, got: %v", expected, got)
}
_, err = sub.NextMsg(1 * time.Second)
if err != nil {
t.Fatal(err)
}
// Default Ephemeral name should be short like in the server.
sub, err = js.SubscribeSync("foo", nats.ConsumerName(""))
if err != nil {
t.Fatal(err)
}
cinfo, err = sub.ConsumerInfo()
if err != nil {
t.Fatal(err)
}
expectedSize := 8
result := len(cinfo.Config.Name)
if result != expectedSize {
t.Fatalf("Expected: %v, got: %v", expectedSize, result)
}
}

View File

@@ -59,9 +59,8 @@ func TestNoRaceObjectContextOpt(t *testing.T) {
expectOk(t, err)
ctx, cancel = context.WithTimeout(context.Background(), 2*time.Second)
time.AfterFunc(100*time.Millisecond, cancel)
time.AfterFunc(10*time.Millisecond, cancel)
time.AfterFunc(20*time.Millisecond, func() { shutdownJSServerAndRemoveStorage(t, s) })
start = time.Now()
_, err = obs.GetBytes("BLOB", nats.Context(ctx))
expectErr(t, err)

16
ws.go
View File

@@ -1,4 +1,4 @@
// Copyright 2021-2022 The NATS Authors
// Copyright 2021-2023 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
@@ -16,7 +16,6 @@ package nats
import (
"bufio"
"bytes"
"compress/flate"
"crypto/rand"
"crypto/sha1"
"encoding/base64"
@@ -30,6 +29,8 @@ import (
"strings"
"time"
"unicode/utf8"
"github.com/klauspost/compress/flate"
)
type wsOpCode int
@@ -448,8 +449,12 @@ func (w *websocketWriter) Write(p []byte) (int, error) {
} else {
w.compressor.Reset(buf)
}
w.compressor.Write(p)
w.compressor.Close()
if n, err = w.compressor.Write(p); err != nil {
return n, err
}
if err = w.compressor.Flush(); err != nil {
return n, err
}
b := buf.Bytes()
p = b[:len(b)-4]
}
@@ -692,6 +697,9 @@ func (nc *Conn) wsEnqueueCloseMsgLocked(status int, payload string) {
wr.cm = frame
wr.cmDone = true
nc.bw.flush()
if c := wr.compressor; c != nil {
c.Close()
}
}
func (nc *Conn) wsEnqueueControlMsg(needsLock bool, frameType wsOpCode, payload []byte) {

View File

@@ -1,4 +1,4 @@
// Copyright 2021-2022 The NATS Authors
// Copyright 2021-2023 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
@@ -15,7 +15,6 @@ package nats
import (
"bytes"
"compress/flate"
"context"
"crypto/tls"
"encoding/binary"
@@ -32,6 +31,7 @@ import (
"testing"
"time"
"github.com/klauspost/compress/flate"
"github.com/nats-io/nats-server/v2/server"
serverTest "github.com/nats-io/nats-server/v2/test"
"github.com/nats-io/nuid"