chore: use database in current context for file cache (#18490)

Using the db.Store when in a TX causes a deadlock for dbmem.
In production, this can cause a deadlock if at the current conn pool
limit.
This commit is contained in:
Steven Masley
2025-06-23 11:58:52 -05:00
committed by GitHub
parent 659b787b9f
commit c1b35bf2f6
5 changed files with 62 additions and 63 deletions
+26 -20
View File
@@ -8,8 +8,8 @@ import (
"github.com/google/uuid"
"github.com/prometheus/client_golang/prometheus"
"github.com/spf13/afero"
"github.com/stretchr/testify/require"
"go.uber.org/mock/gomock"
"golang.org/x/sync/errgroup"
"cdr.dev/slog/sloggers/slogtest"
@@ -18,6 +18,7 @@ import (
"github.com/coder/coder/v2/coderd/database"
"github.com/coder/coder/v2/coderd/database/dbauthz"
"github.com/coder/coder/v2/coderd/database/dbgen"
"github.com/coder/coder/v2/coderd/database/dbmock"
"github.com/coder/coder/v2/coderd/database/dbtestutil"
"github.com/coder/coder/v2/coderd/files"
"github.com/coder/coder/v2/coderd/rbac"
@@ -58,7 +59,7 @@ func TestCacheRBAC(t *testing.T) {
require.Equal(t, 0, cache.Count())
rec.Reset()
_, err := cache.Acquire(nobody, file.ID)
_, err := cache.Acquire(nobody, db, file.ID)
require.Error(t, err)
require.True(t, rbac.IsUnauthorizedError(err))
@@ -75,18 +76,18 @@ func TestCacheRBAC(t *testing.T) {
require.Equal(t, 0, cache.Count())
// Read the file with a file reader to put it into the cache.
a, err := cache.Acquire(cacheReader, file.ID)
a, err := cache.Acquire(cacheReader, db, file.ID)
require.NoError(t, err)
require.Equal(t, 1, cache.Count())
// "nobody" should not be able to read the file.
_, err = cache.Acquire(nobody, file.ID)
_, err = cache.Acquire(nobody, db, file.ID)
require.Error(t, err)
require.True(t, rbac.IsUnauthorizedError(err))
require.Equal(t, 1, cache.Count())
// UserReader can
b, err := cache.Acquire(userReader, file.ID)
b, err := cache.Acquire(userReader, db, file.ID)
require.NoError(t, err)
require.Equal(t, 1, cache.Count())
@@ -110,16 +111,21 @@ func TestConcurrency(t *testing.T) {
ctx := dbauthz.AsFileReader(t.Context())
const fileSize = 10
emptyFS := afero.NewIOFS(afero.NewReadOnlyFs(afero.NewMemMapFs()))
var fetches atomic.Int64
reg := prometheus.NewRegistry()
c := files.New(func(_ context.Context, _ uuid.UUID) (files.CacheEntryValue, error) {
dbM := dbmock.NewMockStore(gomock.NewController(t))
dbM.EXPECT().GetFileByID(gomock.Any(), gomock.Any()).DoAndReturn(func(mTx context.Context, fileID uuid.UUID) (database.File, error) {
fetches.Add(1)
// Wait long enough before returning to make sure that all of the goroutines
// Wait long enough before returning to make sure that all the goroutines
// will be waiting in line, ensuring that no one duplicated a fetch.
time.Sleep(testutil.IntervalMedium)
return files.CacheEntryValue{FS: emptyFS, Size: fileSize}, nil
}, reg, &coderdtest.FakeAuthorizer{})
return database.File{
Data: make([]byte, fileSize),
}, nil
}).AnyTimes()
c := files.New(reg, &coderdtest.FakeAuthorizer{})
batches := 1000
groups := make([]*errgroup.Group, 0, batches)
@@ -137,7 +143,7 @@ func TestConcurrency(t *testing.T) {
g.Go(func() error {
// We don't bother to Release these references because the Cache will be
// released at the end of the test anyway.
_, err := c.Acquire(ctx, id)
_, err := c.Acquire(ctx, dbM, id)
return err
})
}
@@ -164,14 +170,15 @@ func TestRelease(t *testing.T) {
ctx := dbauthz.AsFileReader(t.Context())
const fileSize = 10
emptyFS := afero.NewIOFS(afero.NewReadOnlyFs(afero.NewMemMapFs()))
reg := prometheus.NewRegistry()
c := files.New(func(_ context.Context, _ uuid.UUID) (files.CacheEntryValue, error) {
return files.CacheEntryValue{
FS: emptyFS,
Size: fileSize,
dbM := dbmock.NewMockStore(gomock.NewController(t))
dbM.EXPECT().GetFileByID(gomock.Any(), gomock.Any()).DoAndReturn(func(mTx context.Context, fileID uuid.UUID) (database.File, error) {
return database.File{
Data: make([]byte, fileSize),
}, nil
}, reg, &coderdtest.FakeAuthorizer{})
}).AnyTimes()
c := files.New(reg, &coderdtest.FakeAuthorizer{})
batches := 100
ids := make([]uuid.UUID, 0, batches)
@@ -184,9 +191,8 @@ func TestRelease(t *testing.T) {
batchSize := 10
for openedIdx, id := range ids {
for batchIdx := range batchSize {
it, err := c.Acquire(ctx, id)
it, err := c.Acquire(ctx, dbM, id)
require.NoError(t, err)
require.Equal(t, emptyFS, it.FS)
releases[id] = append(releases[id], it.Close)
// Each time a new file is opened, the metrics should be updated as so:
@@ -257,7 +263,7 @@ func cacheAuthzSetup(t *testing.T) (database.Store, *files.Cache, *coderdtest.Re
// Dbauthz wrap the db
db = dbauthz.New(db, rec, logger, coderdtest.AccessControlStorePointer())
c := files.NewFromStore(db, reg, rec)
c := files.New(reg, rec)
return db, c, rec
}