mirror of
https://github.com/navidrome/navidrome.git
synced 2026-08-31 07:30:32 +00:00
fix(plugins): read the loaded plugin from a local, not the shared map (#6014)
Plugins load concurrently through an errgroup. loadPluginWithConfig wrote m.plugins under m.mu but read it back unlocked to pass to callPluginInit, so one goroutine's write raced another's read. Caught by -race on master (run 32608293134): all 640 specs passed, the job failed only on the race. Capture the pointer while holding the lock and use the local. Holding m.mu across callPluginInit would be wrong, since that runs arbitrary plugin code.
This commit is contained in:
parent
3cb9850872
commit
fc9d93d22a
@ -434,8 +434,7 @@ func (m *Manager) loadPluginWithConfig(p *model.Plugin) error {
|
||||
return fmt.Errorf("manifest validation: %w", err)
|
||||
}
|
||||
|
||||
m.mu.Lock()
|
||||
m.plugins[p.ID] = &plugin{
|
||||
loadedPlugin := &plugin{
|
||||
name: p.ID,
|
||||
path: p.Path,
|
||||
manifest: pkg.Manifest,
|
||||
@ -449,13 +448,16 @@ func (m *Manager) loadPluginWithConfig(p *model.Plugin) error {
|
||||
fsConfig: fsConfig,
|
||||
lyricsSem: make(chan struct{}, maxConcurrentLyricsCalls),
|
||||
}
|
||||
m.mu.Lock()
|
||||
m.plugins[p.ID] = loadedPlugin
|
||||
m.mu.Unlock()
|
||||
loaded = true
|
||||
|
||||
// Init is the plugin's first chance to run arbitrary code: open sockets, create task queues,
|
||||
// schedule work. Only a caller that already intends to reach the network asks for it.
|
||||
// Use the local: loads run concurrently, so reading the map back here would race the writes.
|
||||
if m.transient == nil || m.transient.runInit {
|
||||
callPluginInit(ctx, m.plugins[p.ID])
|
||||
callPluginInit(ctx, loadedPlugin)
|
||||
}
|
||||
|
||||
return nil
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user