R88: make StopAllTasks cover ResourceManager singleton tasks

TaskRegistry.CreateSingletonTask delegates to ResourceManager.RegisterBackgroundTask
and stores the task in the ResourceManager's registry, not TaskRegistry.tasks.
TaskRegistry.StopAllTasks previously iterated its own (always-empty) registry,
silently leaking every process-global singleton task's ticker goroutine past the
last Close (memory-monitor, replay-cache-cleanup) — tasks kept running real
periodic work with no instance to serve.

Add ResourceManager.StopAllTasks (stops all rm-owned tasks) and have
TaskRegistry.StopAllTasks stop both its own RegisterTask-direct tasks and the
ResourceManager-owned singletons.

Regression TestR88StopAllTasksStopsResourceManagerOwnedTasks (review_r88_test.go):
fails on old code (task still running after StopAllTasks), passes on new.
This commit is contained in:
2026-08-13 19:25:03 +01:00
parent 9e39827f7d
commit 866e57380d
3 changed files with 100 additions and 7 deletions
+18
View File
@@ -178,6 +178,24 @@ func (rm *ResourceManager) StopBackgroundTask(name string) error {
return nil
}
// StopAllTasks stops every background task registered with this ResourceManager.
// BackgroundTask.Stop is stopOnce-guarded, so repeated calls are safe. This is
// the terminal path for process-global singleton tasks; it must be called only
// when the last plugin instance is shutting down, otherwise it would kill
// cleanup for surviving instances sharing the same singletons.
func (rm *ResourceManager) StopAllTasks() {
rm.tasksMu.RLock()
tasks := make([]*BackgroundTask, 0, len(rm.tasks))
for _, task := range rm.tasks {
tasks = append(tasks, task)
}
rm.tasksMu.RUnlock()
for _, task := range tasks {
task.Stop()
}
}
// IsTaskRunning checks if a background task is running
func (rm *ResourceManager) IsTaskRunning(name string) bool {
rm.tasksMu.RLock()