From b4bad2e9f107e96b6be4e153985cf25df029d3d4 Mon Sep 17 00:00:00 2001 From: Neil Twigg Date: Fri, 10 Jan 2025 09:36:39 +0000 Subject: [PATCH 1/5] Error handling in `metaSnapshot` Since we use `MarshalJSON()` on some types, we must be careful that a JSON marshalling error cannot be dropped, resulting in an empty snapshot. Signed-off-by: Neil Twigg --- server/jetstream_cluster.go | 26 ++++++++++++++++++++------ server/jetstream_cluster_4_test.go | 3 ++- server/norace_test.go | 3 ++- 3 files changed, 24 insertions(+), 8 deletions(-) diff --git a/server/jetstream_cluster.go b/server/jetstream_cluster.go index dcd54ed70e..46582037b6 100644 --- a/server/jetstream_cluster.go +++ b/server/jetstream_cluster.go @@ -267,7 +267,12 @@ func (s *Server) JetStreamSnapshotMeta() error { return errNotLeader } - return meta.InstallSnapshot(js.metaSnapshot()) + snap, err := js.metaSnapshot() + if err != nil { + return err + } + + return meta.InstallSnapshot(snap) } func (s *Server) JetStreamStepdownStream(account, stream string) error { @@ -1340,7 +1345,10 @@ func (js *jetStream) monitorCluster() { } // For the meta layer we want to snapshot when asked if we need one or have any entries that we can compact. if ne, _ := n.Size(); ne > 0 || n.NeedSnapshot() { - if err := n.InstallSnapshot(js.metaSnapshot()); err == nil { + snap, err := js.metaSnapshot() + if err != nil { + s.Warnf("Error generating JetStream cluster snapshot: %v", err) + } else if err = n.InstallSnapshot(snap); err == nil { lastSnapTime = time.Now() } else if err != errNoSnapAvailable && err != errNodeClosed { s.Warnf("Error snapshotting JetStream cluster state: %v", err) @@ -1534,7 +1542,7 @@ func (js *jetStream) clusterStreamConfig(accName, streamName string) (StreamConf return StreamConfig{}, false } -func (js *jetStream) metaSnapshot() []byte { +func (js *jetStream) metaSnapshot() ([]byte, error) { start := time.Now() js.mu.RLock() s := js.srv @@ -1574,16 +1582,22 @@ func (js *jetStream) metaSnapshot() []byte { if len(streams) == 0 { js.mu.RUnlock() - return nil + return nil, nil } // Track how long it took to marshal the JSON mstart := time.Now() - b, _ := json.Marshal(streams) + b, err := json.Marshal(streams) mend := time.Since(mstart) js.mu.RUnlock() + // Must not be possible for a JSON marshaling error to result + // in an empty snapshot. + if err != nil { + return nil, err + } + // Track how long it took to compress the JSON cstart := time.Now() snap := s2.Encode(nil, b) @@ -1593,7 +1607,7 @@ func (js *jetStream) metaSnapshot() []byte { s.rateLimitFormatWarnf("Metalayer snapshot took %.3fs (streams: %d, consumers: %d, marshal: %.3fs, s2: %.3fs, uncompressed: %d, compressed: %d)", took.Seconds(), nsa, nca, mend.Seconds(), cend.Seconds(), len(b), len(snap)) } - return snap + return snap, nil } func (js *jetStream) applyMetaSnapshot(buf []byte, ru *recoveryUpdates, isRecovering bool) error { diff --git a/server/jetstream_cluster_4_test.go b/server/jetstream_cluster_4_test.go index 4988c30f2f..3b8fd75110 100644 --- a/server/jetstream_cluster_4_test.go +++ b/server/jetstream_cluster_4_test.go @@ -4030,7 +4030,8 @@ func TestJetStreamClusterMetaSnapshotMustNotIncludePendingConsumers(t *testing.T consumers[sampleCa.Name] = &sampleCa // Create snapshot, this should not contain pending consumers. - snap := mjs.metaSnapshot() + snap, err := mjs.metaSnapshot() + require_NoError(t, err) ru := &recoveryUpdates{ removeStreams: make(map[string]*streamAssignment), diff --git a/server/norace_test.go b/server/norace_test.go index cec10d7c91..cb6bd56a01 100644 --- a/server/norace_test.go +++ b/server/norace_test.go @@ -11283,7 +11283,8 @@ func TestNoRaceJetStreamClusterLargeMetaSnapshotTiming(t *testing.T) { n := js.getMetaGroup() // Now let's see how long it takes to create a meta snapshot and how big it is. start := time.Now() - snap := js.metaSnapshot() + snap, err := js.metaSnapshot() + require_NoError(t, err) require_NoError(t, n.InstallSnapshot(snap)) t.Logf("Took %v to snap meta with size of %v\n", time.Since(start), friendlyBytes(len(snap))) } From 51d9de10d07dd14cb98d744d61c11d742c53b362 Mon Sep 17 00:00:00 2001 From: Maurice van Veen Date: Fri, 10 Jan 2025 12:34:35 +0100 Subject: [PATCH 2/5] [FIXED] Health check must not recreate stream/consumer Signed-off-by: Maurice van Veen --- server/jetstream_cluster.go | 109 +------------- server/jetstream_cluster_1_test.go | 231 +++++++++++++++++++++++++++++ 2 files changed, 237 insertions(+), 103 deletions(-) diff --git a/server/jetstream_cluster.go b/server/jetstream_cluster.go index 46582037b6..57cec3873c 100644 --- a/server/jetstream_cluster.go +++ b/server/jetstream_cluster.go @@ -442,73 +442,6 @@ func (cc *jetStreamCluster) isStreamCurrent(account, stream string) bool { return false } -// Restart the stream in question. -// Should only be called when the stream is known to be in a bad state. -func (js *jetStream) restartStream(acc *Account, csa *streamAssignment) { - js.mu.Lock() - s, cc := js.srv, js.cluster - if cc == nil { - js.mu.Unlock() - return - } - // Need to lookup the one directly from the meta layer, what we get handed is a copy if coming from isStreamHealthy. - asa := cc.streams[acc.Name] - if asa == nil { - js.mu.Unlock() - return - } - sa := asa[csa.Config.Name] - if sa == nil { - js.mu.Unlock() - return - } - // Make sure to clear out the raft node if still present in the meta layer. - if rg := sa.Group; rg != nil && rg.node != nil { - if rg.node.State() != Closed { - rg.node.Stop() - } - rg.node = nil - } - sinceCreation := time.Since(sa.Created) - js.mu.Unlock() - - // Process stream assignment to recreate. - // Check that we have given system enough time to start us up. - // This will be longer than obvious, and matches consumer logic in case system very busy. - if sinceCreation < 10*time.Second { - s.Debugf("Not restarting missing stream '%s > %s', too soon since creation %v", - acc, csa.Config.Name, sinceCreation) - return - } - - js.processStreamAssignment(sa) - - // If we had consumers assigned to this server they will be present in the copy, csa. - // They also need to be processed. The csa consumers is a copy of only our consumers, - // those assigned to us, but the consumer assignment's there are direct from the meta - // layer to make this part much easier and avoid excessive lookups. - for _, cca := range csa.consumers { - if cca.deleted { - continue - } - // Need to look up original as well here to make sure node is nil. - js.mu.Lock() - ca := sa.consumers[cca.Name] - if ca != nil && ca.Group != nil { - // Make sure the node is stopped if still running. - if node := ca.Group.node; node != nil && node.State() != Closed { - node.Stop() - } - // Make sure node is wiped. - ca.Group.node = nil - } - js.mu.Unlock() - if ca != nil { - js.processConsumerAssignment(ca) - } - } -} - // isStreamHealthy will determine if the stream is up to date or very close. // For R1 it will make sure the stream is present on this server. func (js *jetStream) isStreamHealthy(acc *Account, sa *streamAssignment) bool { @@ -534,7 +467,6 @@ func (js *jetStream) isStreamHealthy(acc *Account, sa *streamAssignment) bool { // First lookup stream and make sure its there. mset, err := acc.lookupStream(streamName) if err != nil { - js.restartStream(acc, sa) return false } @@ -559,8 +491,6 @@ func (js *jetStream) isStreamHealthy(acc *Account, sa *streamAssignment) bool { s.Warnf("Detected stream cluster node skew '%s > %s'", acc.GetName(), streamName) node.Delete() mset.resetClusteredState(nil) - } else if node.State() == Closed { - js.restartStream(acc, sa) } } return false @@ -590,37 +520,9 @@ func (js *jetStream) isConsumerHealthy(mset *stream, consumer string, ca *consum node := ca.Group.node js.mu.RUnlock() - // When we try to restart we nil out the node if applicable - // and reprocess the consumer assignment. - restartConsumer := func() { - mset.mu.RLock() - accName, streamName := mset.acc.GetName(), mset.cfg.Name - mset.mu.RUnlock() - - js.mu.Lock() - deleted := ca.deleted - // Check that we have not just been created. - if !deleted && time.Since(ca.Created) < 10*time.Second { - s.Debugf("Not restarting missing consumer '%s > %s > %s', too soon since creation %v", - accName, streamName, consumer, time.Since(ca.Created)) - js.mu.Unlock() - return - } - // Make sure the node is stopped if still running. - if node != nil && node.State() != Closed { - node.Stop() - } - ca.Group.node = nil - js.mu.Unlock() - if !deleted { - js.processConsumerAssignment(ca) - } - } - // Check if not running at all. o := mset.lookupConsumer(consumer) if o == nil { - restartConsumer() return false } @@ -635,11 +537,12 @@ func (js *jetStream) isConsumerHealthy(mset *stream, consumer string, ca *consum s.Warnf("Detected consumer cluster node skew '%s > %s > %s'", accName, streamName, consumer) node.Delete() o.deleteWithoutAdvisory() - restartConsumer() - } else if node.State() == Closed { - // We have a consumer, and it should have a running node but it is closed. - o.stop() - restartConsumer() + + // When we try to restart we nil out the node and reprocess the consumer assignment. + js.mu.Lock() + ca.Group.node = nil + js.mu.Unlock() + js.processConsumerAssignment(ca) } } return false diff --git a/server/jetstream_cluster_1_test.go b/server/jetstream_cluster_1_test.go index fefbc25d7a..bc62a7eca5 100644 --- a/server/jetstream_cluster_1_test.go +++ b/server/jetstream_cluster_1_test.go @@ -7004,6 +7004,237 @@ func TestJetStreamClusterClearAllPreAcksOnRemoveMsg(t *testing.T) { checkPreAcks(3, 0) } +func TestJetStreamClusterStreamHealthCheckMustNotRecreate(t *testing.T) { + c := createJetStreamClusterExplicit(t, "R3S", 3) + defer c.shutdown() + + nc, js := jsClientConnect(t, c.randomServer()) + defer nc.Close() + + waitForStreamAssignments := func() { + t.Helper() + checkFor(t, 5*time.Second, time.Second, func() error { + for _, s := range c.servers { + if s.getJetStream().streamAssignment(globalAccountName, "TEST") == nil { + return fmt.Errorf("stream assignment not found on %s", s.Name()) + } + } + return nil + }) + } + waitForNoStreamAssignments := func() { + t.Helper() + checkFor(t, 5*time.Second, time.Second, func() error { + for _, s := range c.servers { + if s.getJetStream().streamAssignment(globalAccountName, "TEST") != nil { + return fmt.Errorf("stream assignment still available on %s", s.Name()) + } + } + return nil + }) + } + getStreamAssignment := func(rs *Server) (*jetStream, *Account, *streamAssignment, *stream) { + acc, err := rs.lookupAccount(globalAccountName) + require_NoError(t, err) + mset, err := acc.lookupStream("TEST") + require_NotNil(t, err) + + sjs := rs.getJetStream() + sjs.mu.RLock() + defer sjs.mu.RUnlock() + + sas := sjs.cluster.streams[globalAccountName] + require_True(t, sas != nil) + sa := sas["TEST"] + require_True(t, sa != nil) + sa.Created = time.Time{} + return sjs, acc, sa, mset + } + checkNodeIsClosed := func(sa *streamAssignment) { + t.Helper() + require_True(t, sa.Group != nil) + rg := sa.Group + require_True(t, rg.node != nil) + n := rg.node + require_Equal(t, n.State(), Closed) + } + + _, err := js.AddStream(&nats.StreamConfig{ + Name: "TEST", + Subjects: []string{"foo"}, + Replicas: 3, + }) + require_NoError(t, err) + waitForStreamAssignments() + + // We manually stop the RAFT node and ensure it doesn't get restarted. + rs := c.randomNonStreamLeader(globalAccountName, "TEST") + sjs, acc, sa, mset := getStreamAssignment(rs) + require_True(t, sa.Group != nil) + rg := sa.Group + require_True(t, rg.node != nil) + n := rg.node + n.Stop() + n.WaitForStop() + + // We wait for the monitor to exit, so we can set the flag back manually. + checkFor(t, 5*time.Second, time.Second, func() error { + mset.mu.RLock() + defer mset.mu.RUnlock() + if mset.inMonitor { + return errors.New("waiting for monitor to stop") + } + return nil + }) + mset.mu.Lock() + mset.inMonitor = true + mset.mu.Unlock() + + // The RAFT node should be closed. Checking health must not change that. + // Simulates a race condition where we're shutting down, but we're still in the stream monitor. + checkNodeIsClosed(sa) + sjs.isStreamHealthy(acc, sa) + checkNodeIsClosed(sa) + + err = js.DeleteStream("TEST") + require_NoError(t, err) + waitForNoStreamAssignments() + + // Underlying layer would be aware the health check made a copy. + // So we sneakily set these values back, which simulates a race condition where + // the health check is called while the deletion is in progress. This could happen + // depending on how the locks are used. + sjs.mu.Lock() + sjs.cluster.streams = make(map[string]map[string]*streamAssignment) + sjs.cluster.streams[globalAccountName] = make(map[string]*streamAssignment) + sjs.cluster.streams[globalAccountName]["TEST"] = sa + sa.Group.node = n + sjs.mu.Unlock() + + // The underlying stream has been deleted. Checking health must not recreate the stream. + checkNodeIsClosed(sa) + sjs.isStreamHealthy(acc, sa) + checkNodeIsClosed(sa) +} + +func TestJetStreamClusterConsumerHealthCheckMustNotRecreate(t *testing.T) { + c := createJetStreamClusterExplicit(t, "R3S", 3) + defer c.shutdown() + + nc, js := jsClientConnect(t, c.randomServer()) + defer nc.Close() + + waitForConsumerAssignments := func() { + t.Helper() + checkFor(t, 5*time.Second, time.Second, func() error { + for _, s := range c.servers { + if s.getJetStream().consumerAssignment(globalAccountName, "TEST", "CONSUMER") == nil { + return fmt.Errorf("stream assignment not found on %s", s.Name()) + } + } + return nil + }) + } + waitForNoConsumerAssignments := func() { + t.Helper() + checkFor(t, 5*time.Second, time.Second, func() error { + for _, s := range c.servers { + if s.getJetStream().consumerAssignment(globalAccountName, "TEST", "CONSUMER") != nil { + return fmt.Errorf("stream assignment still available on %s", s.Name()) + } + } + return nil + }) + } + getConsumerAssignment := func(rs *Server) (*jetStream, *streamAssignment, *consumerAssignment, *stream) { + acc, err := rs.lookupAccount(globalAccountName) + require_NoError(t, err) + mset, err := acc.lookupStream("TEST") + require_NotNil(t, err) + + sjs := rs.getJetStream() + sjs.mu.RLock() + defer sjs.mu.RUnlock() + + sas := sjs.cluster.streams[globalAccountName] + require_True(t, sas != nil) + sa := sas["TEST"] + require_True(t, sa != nil) + ca := sa.consumers["CONSUMER"] + require_True(t, ca != nil) + ca.Created = time.Time{} + return sjs, sa, ca, mset + } + checkNodeIsClosed := func(ca *consumerAssignment) { + t.Helper() + require_True(t, ca.Group != nil) + rg := ca.Group + require_True(t, rg.node != nil) + n := rg.node + require_Equal(t, n.State(), Closed) + } + + _, err := js.AddStream(&nats.StreamConfig{ + Name: "TEST", + Subjects: []string{"foo"}, + Replicas: 3, + }) + require_NoError(t, err) + _, err = js.AddConsumer("TEST", &nats.ConsumerConfig{Durable: "CONSUMER"}) + require_NoError(t, err) + waitForConsumerAssignments() + + // We manually stop the RAFT node and ensure it doesn't get restarted. + rs := c.randomNonConsumerLeader(globalAccountName, "TEST", "CONSUMER") + sjs, sa, ca, mset := getConsumerAssignment(rs) + require_True(t, ca.Group != nil) + rg := ca.Group + require_True(t, rg.node != nil) + n := rg.node + n.Stop() + n.WaitForStop() + + // The RAFT node should be closed. Checking health must not change that. + // Simulates a race condition where we're shutting down. + checkNodeIsClosed(ca) + sjs.isConsumerHealthy(mset, "CONSUMER", ca) + checkNodeIsClosed(ca) + + // We create a new RAFT group, the health check should detect this skew and restart. + err = sjs.createRaftGroup(globalAccountName, ca.Group, MemoryStorage, pprofLabels{}) + require_NoError(t, err) + sjs.mu.Lock() + // We set creating to now, since previously it would delete all data but NOT restart if created within <10s. + ca.Created = time.Now() + // Setting ca.pending, since a side effect of js.processConsumerAssignment is that it resets it. + ca.pending = true + sjs.mu.Unlock() + sjs.isConsumerHealthy(mset, "CONSUMER", ca) + require_False(t, ca.pending) + + err = js.DeleteConsumer("TEST", "CONSUMER") + require_NoError(t, err) + waitForNoConsumerAssignments() + + // Underlying layer would be aware the health check made a copy. + // So we sneakily set these values back, which simulates a race condition where + // the health check is called while the deletion is in progress. This could happen + // depending on how the locks are used. + sjs.mu.Lock() + sjs.cluster.streams = make(map[string]map[string]*streamAssignment) + sjs.cluster.streams[globalAccountName] = make(map[string]*streamAssignment) + sjs.cluster.streams[globalAccountName]["TEST"] = sa + ca.Created = time.Time{} + ca.Group.node = n + ca.deleted = false + sjs.mu.Unlock() + + // The underlying consumer has been deleted. Checking health must not recreate the consumer. + checkNodeIsClosed(ca) + sjs.isConsumerHealthy(mset, "CONSUMER", ca) + checkNodeIsClosed(ca) +} + // // DO NOT ADD NEW TESTS IN THIS FILE (unless to balance test times) // Add at the end of jetstream_cluster__test.go, with being the highest value. From 797b03d485c414bf65aec1e57d45d341ceb8e6d0 Mon Sep 17 00:00:00 2001 From: Maurice van Veen Date: Fri, 10 Jan 2025 12:49:45 +0100 Subject: [PATCH 3/5] Remove TestJetStreamClusterHealthzCheckForStoppedAssets Signed-off-by: Maurice van Veen --- server/jetstream_cluster_3_test.go | 86 ------------------------------ 1 file changed, 86 deletions(-) diff --git a/server/jetstream_cluster_3_test.go b/server/jetstream_cluster_3_test.go index 0898da0e6e..f18beecefe 100644 --- a/server/jetstream_cluster_3_test.go +++ b/server/jetstream_cluster_3_test.go @@ -3813,92 +3813,6 @@ func TestJetStreamClusterConsumerInfoForJszForFollowers(t *testing.T) { } } -// Under certain scenarios we have seen consumers become stopped and cause healthz to fail. -// The specific scneario is heavy loads, and stream resets on upgrades that could orphan consumers. -func TestJetStreamClusterHealthzCheckForStoppedAssets(t *testing.T) { - c := createJetStreamClusterExplicit(t, "NATS", 3) - defer c.shutdown() - - nc, js := jsClientConnect(t, c.randomServer()) - defer nc.Close() - - _, err := js.AddStream(&nats.StreamConfig{ - Name: "TEST", - Subjects: []string{"*"}, - Replicas: 3, - }) - require_NoError(t, err) - - for i := 0; i < 1000; i++ { - sendStreamMsg(t, nc, "foo", "HELLO") - } - - sub, err := js.PullSubscribe("foo", "d") - require_NoError(t, err) - - fetch, ack := 122, 22 - msgs, err := sub.Fetch(fetch, nats.MaxWait(10*time.Second)) - require_NoError(t, err) - require_True(t, len(msgs) == fetch) - for _, m := range msgs[:ack] { - m.AckSync() - } - // Let acks propagate. - time.Sleep(100 * time.Millisecond) - - // We will now stop a stream on a given server. - s := c.randomServer() - mset, err := s.GlobalAccount().lookupStream("TEST") - require_NoError(t, err) - // Stop the stream - mset.stop(false, false) - - // Wait for exit. - time.Sleep(100 * time.Millisecond) - - checkFor(t, 15*time.Second, 500*time.Millisecond, func() error { - hs := s.healthz(nil) - if hs.Error != _EMPTY_ { - return errors.New(hs.Error) - } - return nil - }) - - // Now take out the consumer. - mset, err = s.GlobalAccount().lookupStream("TEST") - require_NoError(t, err) - - o := mset.lookupConsumer("d") - require_NotNil(t, o) - - o.stop() - // Wait for exit. - time.Sleep(100 * time.Millisecond) - - checkFor(t, 5*time.Second, 500*time.Millisecond, func() error { - hs := s.healthz(nil) - if hs.Error != _EMPTY_ { - return errors.New(hs.Error) - } - return nil - }) - - // Now just stop the raft node from underneath the consumer. - o = mset.lookupConsumer("d") - require_NotNil(t, o) - node := o.raftNode() - require_NotNil(t, node) - node.Stop() - - checkFor(t, 5*time.Second, 500*time.Millisecond, func() error { - hs := s.healthz(nil) - if hs.Error != _EMPTY_ { - return errors.New(hs.Error) - } - return nil - }) -} - // Make sure that stopping a stream shutdowns down it's raft node. func TestJetStreamClusterStreamNodeShutdownBugOnStop(t *testing.T) { c := createJetStreamClusterExplicit(t, "NATS", 3) From 9900e4caee622631131f9d6424a83a27d504958a Mon Sep 17 00:00:00 2001 From: Maurice van Veen Date: Fri, 10 Jan 2025 17:41:56 +0100 Subject: [PATCH 4/5] Test: expected a fully formed cluster error Signed-off-by: Maurice van Veen --- server/jetstream_helpers_test.go | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/server/jetstream_helpers_test.go b/server/jetstream_helpers_test.go index 622c5894bc..e16d933a7b 100644 --- a/server/jetstream_helpers_test.go +++ b/server/jetstream_helpers_test.go @@ -1515,14 +1515,12 @@ func (c *cluster) waitOnClusterReadyWithNumPeers(numPeersExpected int) { c.t.Helper() var leader *Server expires := time.Now().Add(40 * time.Second) + // Make sure we have all peers, and take into account the meta leader could still change. for time.Now().Before(expires) { - if leader = c.leader(); leader != nil { - break + if leader = c.leader(); leader == nil { + time.Sleep(50 * time.Millisecond) + continue } - time.Sleep(50 * time.Millisecond) - } - // Now make sure we have all peers. - for leader != nil && time.Now().Before(expires) { if len(leader.JetStreamClusterPeers()) == numPeersExpected { time.Sleep(100 * time.Millisecond) return From c68bc8fd022dd33e18cd3b8b7d89f80a0b448fbb Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 13 Jan 2025 09:03:16 +0000 Subject: [PATCH 5/5] Bump golang.org/x/crypto from 0.31.0 to 0.32.0 Bumps [golang.org/x/crypto](https://github.com/golang/crypto) from 0.31.0 to 0.32.0. - [Commits](https://github.com/golang/crypto/compare/v0.31.0...v0.32.0) --- updated-dependencies: - dependency-name: golang.org/x/crypto dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index a056e0aabc..efd364c7e4 100644 --- a/go.mod +++ b/go.mod @@ -12,7 +12,7 @@ require ( github.com/nats-io/nkeys v0.4.9 github.com/nats-io/nuid v1.0.1 go.uber.org/automaxprocs v1.6.0 - golang.org/x/crypto v0.31.0 + golang.org/x/crypto v0.32.0 golang.org/x/sys v0.29.0 golang.org/x/time v0.9.0 ) diff --git a/go.sum b/go.sum index b74f49937a..cd6b8c1735 100644 --- a/go.sum +++ b/go.sum @@ -20,8 +20,8 @@ github.com/stretchr/testify v1.7.1 h1:5TQK59W5E3v0r2duFAb7P95B6hEeOyEnHRa8MjYSMT github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= go.uber.org/automaxprocs v1.6.0 h1:O3y2/QNTOdbF+e/dpXNNW7Rx2hZ4sTIPyybbxyNqTUs= go.uber.org/automaxprocs v1.6.0/go.mod h1:ifeIMSnPZuznNm6jmdzmU3/bfk01Fe2fotchwEFJ8r8= -golang.org/x/crypto v0.31.0 h1:ihbySMvVjLAeSH1IbfcRTkD/iNscyz8rGzjF/E5hV6U= -golang.org/x/crypto v0.31.0/go.mod h1:kDsLvtWBEx7MV9tJOj9bnXsPbxwJQ6csT/x4KIN4Ssk= +golang.org/x/crypto v0.32.0 h1:euUpcYgM8WcP71gNpTqQCn6rC2t6ULUPiOzfWaXVVfc= +golang.org/x/crypto v0.32.0/go.mod h1:ZnnJkOaASj8g0AjIduWNlq2NRxL0PlBrbKVyZ6V/Ugc= golang.org/x/sys v0.21.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/sys v0.29.0 h1:TPYlXGxvx1MGTn2GiZDhnjPA9wZzZeGKHHmKhHYvgaU= golang.org/x/sys v0.29.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=