fix(plugins): share lyrics coalescing state

This commit is contained in:
ranokay 2026-08-23 07:03:28 +03:00
commit f1b5660532
No known key found for this signature in database
3 changed files with 14 additions and 10 deletions

View file

@ -9,7 +9,6 @@ import (
"github.com/navidrome/navidrome/log"
"github.com/navidrome/navidrome/model"
"github.com/navidrome/navidrome/plugins/capabilities"
"golang.org/x/sync/singleflight"
)
const CapabilityLyrics Capability = "Lyrics"
@ -41,7 +40,6 @@ func newLyricsPlugin(p *plugin) *LyricsPlugin {
type LyricsPlugin struct {
name string
plugin *plugin
calls singleflight.Group
}
// GetLyrics coalesces concurrent lookups for the same track. The shared call is
@ -60,7 +58,7 @@ func (l *LyricsPlugin) GetLyrics(ctx context.Context, mf *model.MediaFile) (mode
return nil, err
}
result := l.calls.DoChan(key, func() (any, error) {
result := l.plugin.lyricsCalls.DoChan(key, func() (any, error) {
callCtx, cancel := context.WithTimeout(context.WithoutCancel(ctx), lyricsPluginCallTimeout)
defer cancel()
return l.getLyrics(callCtx, mf, req)

View file

@ -66,11 +66,15 @@ var _ = Describe("LyricsPlugin", Ordered, func() {
"test-lyrics"+PackageExtension,
)
p, ok := manager.LoadLyricsProvider("test-lyrics")
first, ok := manager.LoadLyricsProvider("test-lyrics")
Expect(ok).To(BeTrue())
coalescingProvider := p.(*LyricsPlugin)
second, ok := manager.LoadLyricsProvider("test-lyrics")
Expect(ok).To(BeTrue())
firstProvider := first.(*LyricsPlugin)
secondProvider := second.(*LyricsPlugin)
Expect(firstProvider).ToNot(BeIdenticalTo(secondProvider))
sem := coalescingProvider.plugin.lyricsSem
sem := firstProvider.plugin.lyricsSem
for range cap(sem) {
sem <- struct{}{}
}
@ -87,10 +91,10 @@ var _ = Describe("LyricsPlugin", Ordered, func() {
start := make(chan struct{})
results := make(chan callResult, 2)
track := &model.MediaFile{ID: "shared-track", Title: "Test Song", Artist: "Test Artist"}
for range 2 {
for _, provider := range []*LyricsPlugin{firstProvider, secondProvider} {
go func() {
<-start
lyrics, err := coalescingProvider.GetLyrics(GinkgoT().Context(), track)
lyrics, err := provider.GetLyrics(GinkgoT().Context(), track)
results <- callResult{lyrics: lyrics, err: err}
}()
}

View file

@ -10,6 +10,7 @@ import (
extism "github.com/extism/go-sdk"
"github.com/navidrome/navidrome/model"
"github.com/tetratelabs/wazero"
"golang.org/x/sync/singleflight"
)
// plugin represents a loaded plugin
@ -24,8 +25,9 @@ type plugin struct {
allowedUserIDs []string // User IDs this plugin can access (from DB configuration)
allUsers bool // If true, plugin can access all users
libraries libraryAccess
lyricsSem chan struct{} // Caps concurrent lyrics calls (see LyricsPlugin.GetLyrics)
fsConfig wazero.FSConfig // Sandboxed library mounts, nil if no filesystem permission
lyricsSem chan struct{} // Caps concurrent lyrics calls (see LyricsPlugin.GetLyrics)
lyricsCalls singleflight.Group // Shared by the transient LyricsPlugin adapters
fsConfig wazero.FSConfig // Sandboxed library mounts, nil if no filesystem permission
}
// instanceConfig is used by every call site, so all instances get the sandboxed mounts.