Skip to content

Commit adef0ea

Browse files
committed
fix(plugins): resolve race condition in plugin manager registration
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>
1 parent b69a765 commit adef0ea

File tree

1 file changed

+15
-8
lines changed

1 file changed

+15
-8
lines changed

plugins/manager.go

Lines changed: 15 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -189,13 +189,6 @@ func (m *managerImpl) registerPlugin(pluginID, pluginDir, wasmPath string, manif
189189
}
190190
m.mu.Unlock()
191191

192-
// Start pre-compilation of WASM module in background AFTER registration
193-
go func() {
194-
precompilePlugin(p)
195-
// Check if this plugin implements InitService and hasn't been initialized yet
196-
m.initializePluginIfNeeded(p)
197-
}()
198-
199192
log.Info("Discovered plugin", "folder", pluginID, "name", manifest.Name, "capabilities", manifest.Capabilities, "wasm", wasmPath, "dev_mode", isSymlink)
200193
return m.plugins[pluginID]
201194
}
@@ -261,6 +254,7 @@ func (m *managerImpl) ScanPlugins() {
261254
discoveries := DiscoverPlugins(root)
262255

263256
var validPluginNames []string
257+
var registeredPlugins []*plugin
264258
for _, discovery := range discoveries {
265259
if discovery.Error != nil {
266260
// Handle global errors (like directory read failure)
@@ -284,7 +278,20 @@ func (m *managerImpl) ScanPlugins() {
284278
validPluginNames = append(validPluginNames, discovery.ID)
285279

286280
// Register the plugin
287-
m.registerPlugin(discovery.ID, discovery.Path, discovery.WasmPath, discovery.Manifest)
281+
plugin := m.registerPlugin(discovery.ID, discovery.Path, discovery.WasmPath, discovery.Manifest)
282+
if plugin != nil {
283+
registeredPlugins = append(registeredPlugins, plugin)
284+
}
285+
}
286+
287+
// Start background processing for all registered plugins after registration is complete
288+
// This avoids race conditions between registration and goroutines that might unregister plugins
289+
for _, p := range registeredPlugins {
290+
go func(plugin *plugin) {
291+
precompilePlugin(plugin)
292+
// Check if this plugin implements InitService and hasn't been initialized yet
293+
m.initializePluginIfNeeded(plugin)
294+
}(p)
288295
}
289296

290297
log.Debug("Found valid plugins", "count", len(validPluginNames), "plugins", validPluginNames)

0 commit comments

Comments
 (0)