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,8 @@ package persistence
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"github.com/Masterminds/squirrel"
|
||||
@@ -711,4 +713,44 @@ var _ = Describe("MediaRepository", func() {
|
||||
Expect(results).To(BeEmpty())
|
||||
})
|
||||
})
|
||||
|
||||
Describe("wrapMediaFileCursor", func() {
|
||||
It("does not panic when the cursor yields a dbMediaFile with nil MediaFile", func() {
|
||||
// Simulate what queryWithStableResults does on the rows.Err() path:
|
||||
// it yields a zero-value dbMediaFile (where MediaFile is nil) with an error.
|
||||
dbErr := fmt.Errorf("database is locked")
|
||||
cursor := func(yield func(dbMediaFile, error) bool) {
|
||||
var empty dbMediaFile // MediaFile pointer is nil
|
||||
yield(empty, dbErr)
|
||||
}
|
||||
|
||||
// wrapMediaFileCursor should handle the nil MediaFile without panicking
|
||||
wrappedCursor := wrapMediaFileCursor(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 mediafile"))
|
||||
Expect(errors.Is(gotErr, dbErr)).To(BeTrue(), "should wrap the original cursor error")
|
||||
})
|
||||
|
||||
It("yields mediafiles from a valid cursor", func() {
|
||||
mf := &model.MediaFile{ID: "mf1", Title: "Test"}
|
||||
cursor := func(yield func(dbMediaFile, error) bool) {
|
||||
yield(dbMediaFile{MediaFile: mf}, nil)
|
||||
}
|
||||
|
||||
wrappedCursor := wrapMediaFileCursor(cursor)
|
||||
var mediafiles []model.MediaFile
|
||||
for m, err := range wrappedCursor {
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
mediafiles = append(mediafiles, m)
|
||||
}
|
||||
Expect(mediafiles).To(HaveLen(1))
|
||||
Expect(mediafiles[0].ID).To(Equal("mf1"))
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
Reference in New Issue
Block a user