refactor: plain error assignment in eviction, storage and config (noinlineerr)
Hand-reviewed rather than mechanical, because two of these functions carried cleanup that depended on the shape being replaced. internal/imgcache/storage.go, writeIfAbsent: dropped the named result and the deferred temp-file cleanup that read it (nonamedreturns), in favour of an explicit os.Remove(tmpPath) on each failing path. The deferred form removed tmpPath whenever the function returned a non-nil error, which is reachable on exactly three paths once the temp file exists: Write, Close and Rename. Each of those now unlinks explicitly, in the same order relative to tmpFile.Close(). The paths that must NOT unlink are unchanged and still cannot: the content-already-present early return, a MkdirAll failure and a CreateTemp failure all happen before tmpPath exists, and the success path renames the temp file away. This is the same cleanup shape MetadataStorage.Store and VariantStorage.Store already use in this file. StoreHashed likewise loses its named results. internal/imgcache/eviction.go: converted 26 inline assignments. In evictSourceBlob the conversions reuse the function-scope err that the transaction already used; the rollback defer does not read it, and the ordering of the delete transaction, its commit, the sidecar deletes and the blob unlink is untouched. In the rows.Next() loops the scan error is declared inside the loop body and rows.Err() is checked after it, as before. internal/config: the remaining conversions are in straight-line code with no defer or named result. No behavior changes. make test (with -race, per script/test) is green, including TestEvictSourceBlobExcludesConcurrentStoreOfIdenticalContent, which exercises the commit-to-unlink window this cleanup protects.
This commit is contained in:
@@ -66,7 +66,8 @@ func (s *ContentStorage) Store(r io.Reader) (ContentHash, int64, error) {
|
||||
hash := ContentHash(hex.EncodeToString(h[:]))
|
||||
size := int64(len(data))
|
||||
|
||||
if err := s.writeIfAbsent(hash, data); err != nil {
|
||||
err = s.writeIfAbsent(hash, data)
|
||||
if err != nil {
|
||||
return "", 0, err
|
||||
}
|
||||
|
||||
@@ -81,8 +82,9 @@ func (s *ContentStorage) Store(r io.Reader) (ContentHash, int64, error) {
|
||||
// left untouched.
|
||||
func (s *ContentStorage) StoreHashed(
|
||||
hash ContentHash, data []byte,
|
||||
) (size int64, err error) {
|
||||
if err := s.writeIfAbsent(hash, data); err != nil {
|
||||
) (int64, error) {
|
||||
err := s.writeIfAbsent(hash, data)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
@@ -151,18 +153,21 @@ func (s *ContentStorage) Exists(hash ContentHash) bool {
|
||||
// writeIfAbsent writes data to the path derived from hash, unless
|
||||
// content already exists there, via a temp-file-plus-rename so
|
||||
// concurrent readers never observe a partial file.
|
||||
func (s *ContentStorage) writeIfAbsent(hash ContentHash, data []byte) (err error) {
|
||||
func (s *ContentStorage) writeIfAbsent(hash ContentHash, data []byte) error {
|
||||
// Build path: <basedir>/<ab>/<cd>/<hash>
|
||||
path := s.hashToPath(hash)
|
||||
|
||||
// Check if already exists
|
||||
if _, statErr := os.Stat(path); statErr == nil {
|
||||
_, statErr := os.Stat(path)
|
||||
if statErr == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Create directory structure
|
||||
dir := filepath.Dir(path)
|
||||
if err := os.MkdirAll(dir, StorageDirPerm); err != nil {
|
||||
|
||||
err := os.MkdirAll(dir, StorageDirPerm)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to create directory: %w", err)
|
||||
}
|
||||
|
||||
@@ -174,24 +179,32 @@ func (s *ContentStorage) writeIfAbsent(hash ContentHash, data []byte) (err error
|
||||
|
||||
tmpPath := tmpFile.Name()
|
||||
|
||||
defer func() {
|
||||
if err != nil {
|
||||
_ = os.Remove(tmpPath)
|
||||
}
|
||||
}()
|
||||
|
||||
if _, err := tmpFile.Write(data); err != nil {
|
||||
// Each failure path below unlinks the temp file explicitly. This
|
||||
// replaces a deferred cleanup that read a named result, which the
|
||||
// canonical config does not permit; the set of paths that remove
|
||||
// tmpPath, and the order relative to Close, is unchanged. This
|
||||
// mirrors how MetadataStorage.Store and VariantStorage.Store below
|
||||
// already express the same cleanup.
|
||||
_, err = tmpFile.Write(data)
|
||||
if err != nil {
|
||||
_ = tmpFile.Close()
|
||||
_ = os.Remove(tmpPath)
|
||||
|
||||
return fmt.Errorf("failed to write content: %w", err)
|
||||
}
|
||||
|
||||
if err := tmpFile.Close(); err != nil {
|
||||
err = tmpFile.Close()
|
||||
if err != nil {
|
||||
_ = os.Remove(tmpPath)
|
||||
|
||||
return fmt.Errorf("failed to close temp file: %w", err)
|
||||
}
|
||||
|
||||
// Atomic rename
|
||||
if err := os.Rename(filepath.Clean(tmpPath), filepath.Clean(path)); err != nil {
|
||||
err = os.Rename(filepath.Clean(tmpPath), filepath.Clean(path))
|
||||
if err != nil {
|
||||
_ = os.Remove(tmpPath)
|
||||
|
||||
return fmt.Errorf("failed to rename temp file: %w", err)
|
||||
}
|
||||
|
||||
@@ -547,13 +560,14 @@ func (s *VariantStorage) Delete(key VariantKey) error {
|
||||
// DeleteWithMeta removes the content at the given key together with
|
||||
// its .meta sidecar file. A missing file is not an error.
|
||||
func (s *VariantStorage) DeleteWithMeta(key VariantKey) error {
|
||||
if err := s.Delete(key); err != nil {
|
||||
err := s.Delete(key)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
metaPath := s.keyToPath(key) + ".meta"
|
||||
|
||||
err := os.Remove(metaPath)
|
||||
err = os.Remove(metaPath)
|
||||
if err != nil && !os.IsNotExist(err) {
|
||||
return fmt.Errorf("failed to delete variant metadata: %w", err)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user