fix(scanner): add nil guards to cursor wrapping (#5139)
* fix(persistence): add nil guards to cursor wrapping in folder and mediafile repos Prevent SIGSEGV panic when queryWithStableResults yields a zero-value struct on the rows.Err() path (e.g., "database is locked" during concurrent scanning). Extract cursor wrapping into wrapFolderCursor and wrapMediaFileCursor with nil checks matching the existing pattern in album_repository.go. Fixes #5138 * fix(persistence): wrap original cursor error in nil guard messages Use %w to preserve the underlying error (e.g., "database is locked") so callers can use errors.Is/As for root cause analysis. Tests now verify the original error is accessible via errors.Is. * fix(persistence): add nil guards and error wrapping in album, folder, and mediafile cursor functions Signed-off-by: Deluan <deluan@navidrome.org> --------- Signed-off-by: Deluan <deluan@navidrome.org>
This commit is contained in:
@@ -2,6 +2,7 @@ package persistence
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
|
||||
"github.com/navidrome/navidrome/log"
|
||||
@@ -210,4 +211,44 @@ var _ = Describe("FolderRepository", func() {
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
Describe("wrapFolderCursor", func() {
|
||||
It("does not panic when the cursor yields a dbFolder with nil Folder", func() {
|
||||
// Simulate what queryWithStableResults does on the rows.Err() path:
|
||||
// it yields a zero-value dbFolder (where Folder is nil) with an error.
|
||||
dbErr := fmt.Errorf("database is locked")
|
||||
cursor := func(yield func(dbFolder, error) bool) {
|
||||
var empty dbFolder // Folder pointer is nil
|
||||
yield(empty, dbErr)
|
||||
}
|
||||
|
||||
// wrapFolderCursor should handle the nil Folder without panicking
|
||||
wrappedCursor := wrapFolderCursor(cursor)
|
||||
var gotErr error
|
||||
Expect(func() {
|
||||
for _, err := range wrappedCursor {
|
||||
gotErr = err
|
||||
}
|
||||
}).ToNot(Panic())
|
||||
Expect(gotErr).To(HaveOccurred())
|
||||
Expect(gotErr.Error()).To(ContainSubstring("unexpected nil folder"))
|
||||
Expect(errors.Is(gotErr, dbErr)).To(BeTrue(), "should wrap the original cursor error")
|
||||
})
|
||||
|
||||
It("yields folders from a valid cursor", func() {
|
||||
folder := &model.Folder{ID: "f1", Name: "Test"}
|
||||
cursor := func(yield func(dbFolder, error) bool) {
|
||||
yield(dbFolder{Folder: folder}, nil)
|
||||
}
|
||||
|
||||
wrappedCursor := wrapFolderCursor(cursor)
|
||||
var folders []model.Folder
|
||||
for f, err := range wrappedCursor {
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
folders = append(folders, f)
|
||||
}
|
||||
Expect(folders).To(HaveLen(1))
|
||||
Expect(folders[0].ID).To(Equal("f1"))
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
Reference in New Issue
Block a user