fix(plugins): resolve race condition in plugin manager registration
Some checks failed
Pipeline: Test, Lint, Build / Get version info (push) Has been cancelled
Pipeline: Test, Lint, Build / Lint Go code (push) Has been cancelled
Pipeline: Test, Lint, Build / Test Go code (push) Has been cancelled
Pipeline: Test, Lint, Build / Test JS code (push) Has been cancelled
Pipeline: Test, Lint, Build / Lint i18n files (push) Has been cancelled
Pipeline: Test, Lint, Build / Check Docker configuration (push) Has been cancelled
Pipeline: Test, Lint, Build / Build (darwin/amd64) (push) Has been cancelled
Pipeline: Test, Lint, Build / Build (darwin/arm64) (push) Has been cancelled
Pipeline: Test, Lint, Build / Build (linux/386) (push) Has been cancelled
Pipeline: Test, Lint, Build / Build (linux/amd64) (push) Has been cancelled
Pipeline: Test, Lint, Build / Build (linux/arm/v5) (push) Has been cancelled
Pipeline: Test, Lint, Build / Build (linux/arm/v6) (push) Has been cancelled
Pipeline: Test, Lint, Build / Build (linux/arm/v7) (push) Has been cancelled
Pipeline: Test, Lint, Build / Build (linux/arm64) (push) Has been cancelled
Pipeline: Test, Lint, Build / Build (windows/386) (push) Has been cancelled
Pipeline: Test, Lint, Build / Build (windows/amd64) (push) Has been cancelled
Pipeline: Test, Lint, Build / Push Docker manifest (push) Has been cancelled
Pipeline: Test, Lint, Build / Build Windows installers (push) Has been cancelled
Pipeline: Test, Lint, Build / Package/Release (push) Has been cancelled
Pipeline: Test, Lint, Build / Upload Linux PKG (push) Has been cancelled
Close stale issues and PRs / stale (push) Has been cancelled

Fixed a race condition in the plugin manager where goroutines started during
plugin registration could concurrently access shared plugin maps while the
main registration loop was still running. The fix separates plugin registration
from background processing by collecting all plugins first, then starting
background goroutines after registration is complete.

This prevents concurrent read/write access to the plugins and adapters maps
that was causing data races detected by the Go race detector. The solution
maintains the same functionality while ensuring thread safety during the
plugin scanning and registration process.

Signed-off-by: Deluan <deluan@navidrome.org>
This commit is contained in:
Deluan
2025-07-15 12:54:09 -04:00
parent b69a7652b9
commit adef0ea1e7

View File

@@ -189,13 +189,6 @@ func (m *managerImpl) registerPlugin(pluginID, pluginDir, wasmPath string, manif
}
m.mu.Unlock()
// Start pre-compilation of WASM module in background AFTER registration
go func() {
precompilePlugin(p)
// Check if this plugin implements InitService and hasn't been initialized yet
m.initializePluginIfNeeded(p)
}()
log.Info("Discovered plugin", "folder", pluginID, "name", manifest.Name, "capabilities", manifest.Capabilities, "wasm", wasmPath, "dev_mode", isSymlink)
return m.plugins[pluginID]
}
@@ -261,6 +254,7 @@ func (m *managerImpl) ScanPlugins() {
discoveries := DiscoverPlugins(root)
var validPluginNames []string
var registeredPlugins []*plugin
for _, discovery := range discoveries {
if discovery.Error != nil {
// Handle global errors (like directory read failure)
@@ -284,7 +278,20 @@ func (m *managerImpl) ScanPlugins() {
validPluginNames = append(validPluginNames, discovery.ID)
// Register the plugin
m.registerPlugin(discovery.ID, discovery.Path, discovery.WasmPath, discovery.Manifest)
plugin := m.registerPlugin(discovery.ID, discovery.Path, discovery.WasmPath, discovery.Manifest)
if plugin != nil {
registeredPlugins = append(registeredPlugins, plugin)
}
}
// Start background processing for all registered plugins after registration is complete
// This avoids race conditions between registration and goroutines that might unregister plugins
for _, p := range registeredPlugins {
go func(plugin *plugin) {
precompilePlugin(plugin)
// Check if this plugin implements InitService and hasn't been initialized yet
m.initializePluginIfNeeded(plugin)
}(p)
}
log.Debug("Found valid plugins", "count", len(validPluginNames), "plugins", validPluginNames)