Skip to content

Commit

Permalink
Backport 1.7: Fix goroutine leak caused by updating rate quotas (#11371
Browse files Browse the repository at this point in the history
…) (#11380)

Make sure that when we modify a rate quota, we stop the existing goroutine before starting the new one.
  • Loading branch information
ncabatoff committed Apr 16, 2021
1 parent 64f4b89 commit 71ef44f
Show file tree
Hide file tree
Showing 22 changed files with 787 additions and 5 deletions.
3 changes: 3 additions & 0 deletions changelog/11371.txt
@@ -0,0 +1,3 @@
```release-note:bug
core: Fix goroutine leak when updating rate limit quota
```
1 change: 1 addition & 0 deletions go.mod
Expand Up @@ -156,6 +156,7 @@ require (
go.etcd.io/etcd v0.5.0-alpha.5.0.20200425165423-262c93980547
go.mongodb.org/mongo-driver v1.4.6
go.uber.org/atomic v1.6.0
go.uber.org/goleak v1.1.10
golang.org/x/crypto v0.0.0-20210220033148-5ea612d1eb83
golang.org/x/net v0.0.0-20201110031124-69a78807bb2b
golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d
Expand Down
5 changes: 3 additions & 2 deletions helper/metricsutil/wrapped_metrics.go
Expand Up @@ -88,8 +88,9 @@ func (m *ClusterMetricSink) MeasureSinceWithLabels(key []string, start time.Time

// BlackholeSink is a default suitable for use in unit tests.
func BlackholeSink() *ClusterMetricSink {
sink, _ := metrics.New(metrics.DefaultConfig(""),
&metrics.BlackholeSink{})
conf := metrics.DefaultConfig("")
conf.EnableRuntimeMetrics = false
sink, _ := metrics.New(conf, &metrics.BlackholeSink{})
cms := &ClusterMetricSink{
ClusterName: atomic.Value{},
Sink: sink,
Expand Down
12 changes: 9 additions & 3 deletions vault/quotas/quotas.go
Expand Up @@ -261,14 +261,16 @@ func NewManager(logger log.Logger, walkFunc leaseWalkFunc, ms *metricsutil.Clust
return manager, nil
}

// SetQuota adds a new quota rule to the db.
// SetQuota adds or updates a quota rule.
func (m *Manager) SetQuota(ctx context.Context, qType string, quota Quota, loading bool) error {
m.lock.Lock()
defer m.lock.Unlock()
return m.setQuotaLocked(ctx, qType, quota, loading)
}

// setQuotaLocked should be called with the manager's lock held
// setQuotaLocked adds or updates a quota rule, modifying the db as well as
// any runtime elements such as goroutines.
// It should be called with the write lock held.
func (m *Manager) setQuotaLocked(ctx context.Context, qType string, quota Quota, loading bool) error {
if qType == TypeLeaseCount.String() {
m.setIsPerfStandby(quota)
Expand All @@ -277,13 +279,17 @@ func (m *Manager) setQuotaLocked(ctx context.Context, qType string, quota Quota,
txn := m.db.Txn(true)
defer txn.Abort()

raw, err := txn.First(qType, "id", quota.quotaID())
raw, err := txn.First(qType, indexID, quota.quotaID())
if err != nil {
return err
}

// If there already exists an entry in the db, remove that first.
if raw != nil {
quota := raw.(Quota)
if err := quota.close(); err != nil {
return err
}
err = txn.Delete(qType, raw)
if err != nil {
return err
Expand Down
1 change: 1 addition & 0 deletions vault/quotas/quotas_rate_limit.go
Expand Up @@ -319,6 +319,7 @@ func (rlq *RateLimitQuota) allow(req *Request) (Response, error) {
}

// close stops the current running client purge loop.
// It should be called with the write lock held.
func (rlq *RateLimitQuota) close() error {
if rlq.purgeBlocked {
close(rlq.closePurgeBlockedCh)
Expand Down
19 changes: 19 additions & 0 deletions vault/quotas/quotas_rate_limit_test.go
@@ -1,6 +1,7 @@
package quotas

import (
"context"
"fmt"
"math"
"sync"
Expand All @@ -12,6 +13,7 @@ import (
"github.com/hashicorp/vault/sdk/helper/logging"
"github.com/stretchr/testify/require"
"go.uber.org/atomic"
"go.uber.org/goleak"
)

type clientResult struct {
Expand All @@ -34,6 +36,9 @@ func TestNewRateLimitQuota(t *testing.T) {
t.Run(tc.name, func(t *testing.T) {
err := tc.rlq.initialize(logging.NewVaultLogger(log.Trace), metricsutil.BlackholeSink())
require.Equal(t, tc.expectErr, err != nil, err)
if err == nil {
require.Nil(t, tc.rlq.close())
}
})
}
}
Expand Down Expand Up @@ -61,6 +66,7 @@ func TestRateLimitQuota_Allow(t *testing.T) {
}

require.NoError(t, rlq.initialize(logging.NewVaultLogger(log.Trace), metricsutil.BlackholeSink()))
defer rlq.close()

var wg sync.WaitGroup

Expand Down Expand Up @@ -135,6 +141,7 @@ func TestRateLimitQuota_Allow_WithBlock(t *testing.T) {
}

require.NoError(t, rlq.initialize(logging.NewVaultLogger(log.Trace), metricsutil.BlackholeSink()))
defer rlq.close()
require.True(t, rlq.getPurgeBlocked())

var wg sync.WaitGroup
Expand Down Expand Up @@ -204,3 +211,15 @@ func TestRateLimitQuota_Allow_WithBlock(t *testing.T) {
}
}()
}

func TestRateLimitQuota_Update(t *testing.T) {
defer goleak.VerifyNone(t)
qm, err := NewManager(logging.NewVaultLogger(log.Trace), nil, metricsutil.BlackholeSink())
require.NoError(t, err)

quota := NewRateLimitQuota("quota1", "", "", 10, time.Second, 0)
require.NoError(t, qm.SetQuota(context.Background(), TypeRateLimit.String(), quota, true))
require.NoError(t, qm.SetQuota(context.Background(), TypeRateLimit.String(), quota, true))

require.Nil(t, quota.close())
}
5 changes: 5 additions & 0 deletions vendor/go.uber.org/goleak/.gitignore

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

25 changes: 25 additions & 0 deletions vendor/go.uber.org/goleak/.travis.yml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

24 changes: 24 additions & 0 deletions vendor/go.uber.org/goleak/CHANGELOG.md

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

21 changes: 21 additions & 0 deletions vendor/go.uber.org/goleak/LICENSE

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

41 changes: 41 additions & 0 deletions vendor/go.uber.org/goleak/Makefile

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

71 changes: 71 additions & 0 deletions vendor/go.uber.org/goleak/README.md

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

22 changes: 22 additions & 0 deletions vendor/go.uber.org/goleak/doc.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

8 changes: 8 additions & 0 deletions vendor/go.uber.org/goleak/glide.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

11 changes: 11 additions & 0 deletions vendor/go.uber.org/goleak/go.mod

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

30 changes: 30 additions & 0 deletions vendor/go.uber.org/goleak/go.sum

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

0 comments on commit 71ef44f

Please sign in to comment.