chore: add cacheCloser to cleanup all opened files (#18473)

This commit is contained in:
Steven Masley
2025-06-20 13:25:33 -05:00
committed by GitHub
parent 9b5d49967c
commit 556b095d0f
3 changed files with 79 additions and 16 deletions

View File

@ -131,46 +131,48 @@ func (r *loader) Renderer(ctx context.Context, db database.Store, cache *files.C
return r.staticRender(ctx, db)
}
return r.dynamicRenderer(ctx, db, cache)
return r.dynamicRenderer(ctx, db, files.NewCacheCloser(cache))
}
// Renderer caches all the necessary files when rendering a template version's
// parameters. It must be closed after use to release the cached files.
func (r *loader) dynamicRenderer(ctx context.Context, db database.Store, cache *files.Cache) (*dynamicRenderer, error) {
func (r *loader) dynamicRenderer(ctx context.Context, db database.Store, cache *files.CacheCloser) (*dynamicRenderer, error) {
closeFiles := true // If the function returns with no error, this will toggle to false.
defer func() {
if closeFiles {
cache.Close()
}
}()
// If they can read the template version, then they can read the file for
// parameter loading purposes.
//nolint:gocritic
fileCtx := dbauthz.AsFileReader(ctx)
templateFS, err := cache.Acquire(fileCtx, r.job.FileID)
var templateFS fs.FS
var err error
templateFS, err = cache.Acquire(fileCtx, r.job.FileID)
if err != nil {
return nil, xerrors.Errorf("acquire template file: %w", err)
}
var terraformFS fs.FS = templateFS
var moduleFilesFS *files.CloseFS
if r.terraformValues.CachedModuleFiles.Valid {
moduleFilesFS, err = cache.Acquire(fileCtx, r.terraformValues.CachedModuleFiles.UUID)
if err != nil {
templateFS.Close()
return nil, xerrors.Errorf("acquire module files: %w", err)
}
terraformFS = files.NewOverlayFS(templateFS, []files.Overlay{{Path: ".terraform/modules", FS: moduleFilesFS}})
templateFS = files.NewOverlayFS(templateFS, []files.Overlay{{Path: ".terraform/modules", FS: moduleFilesFS}})
}
closeFiles = false // Caller will have to call close
return &dynamicRenderer{
data: r,
templateFS: terraformFS,
templateFS: templateFS,
db: db,
ownerErrors: make(map[uuid.UUID]error),
close: func() {
// Up to 2 files are cached, and must be released when rendering is complete.
// TODO: Might be smart to always call release when the context is
// canceled.
templateFS.Close()
if moduleFilesFS != nil {
moduleFilesFS.Close()
}
},
close: cache.Close,
}, nil
}

View File

@ -19,6 +19,10 @@ import (
"github.com/coder/coder/v2/coderd/util/lazy"
)
type FileAcquirer interface {
Acquire(ctx context.Context, fileID uuid.UUID) (*CloseFS, error)
}
// NewFromStore returns a file cache that will fetch files from the provided
// database.
func NewFromStore(store database.Store, registerer prometheus.Registerer, authz rbac.Authorizer) *Cache {

57
coderd/files/closer.go Normal file
View File

@ -0,0 +1,57 @@
package files
import (
"context"
"sync"
"github.com/google/uuid"
"golang.org/x/xerrors"
)
// CacheCloser is a cache wrapper used to close all acquired files.
// This is a more simple interface to use if opening multiple files at once.
type CacheCloser struct {
cache FileAcquirer
closers []func()
mu sync.Mutex
}
func NewCacheCloser(cache FileAcquirer) *CacheCloser {
return &CacheCloser{
cache: cache,
closers: make([]func(), 0),
}
}
func (c *CacheCloser) Close() {
c.mu.Lock()
defer c.mu.Unlock()
for _, doClose := range c.closers {
doClose()
}
// Prevent further acquisitions
c.cache = nil
// Remove any references
c.closers = nil
}
func (c *CacheCloser) Acquire(ctx context.Context, fileID uuid.UUID) (*CloseFS, error) {
c.mu.Lock()
defer c.mu.Unlock()
if c.cache == nil {
return nil, xerrors.New("cache is closed, and cannot acquire new files")
}
f, err := c.cache.Acquire(ctx, fileID)
if err != nil {
return nil, err
}
c.closers = append(c.closers, f.close)
return f, nil
}