package process import ( "context" "errors" "sync" "time" "github.com/grafana/grafana/pkg/infra/log" "github.com/grafana/grafana/pkg/plugins" "github.com/grafana/grafana/pkg/plugins/backendplugin" "github.com/grafana/grafana/pkg/plugins/manager/registry" ) var _ Service = (*Manager)(nil) type Manager struct { pluginRegistry registry.Service mu sync.Mutex log log.Logger } func ProvideService(pluginRegistry registry.Service) *Manager { return NewManager(pluginRegistry) } func NewManager(pluginRegistry registry.Service) *Manager { return &Manager{ pluginRegistry: pluginRegistry, log: log.New("plugin.process.manager"), } } func (m *Manager) Run(ctx context.Context) error { <-ctx.Done() m.shutdown(ctx) return ctx.Err() } func (m *Manager) Start(ctx context.Context, pluginID string) error { p, exists := m.pluginRegistry.Plugin(ctx, pluginID) if !exists { return backendplugin.ErrPluginNotRegistered } if !p.IsManaged() || !p.Backend || p.SignatureError != nil { return nil } m.mu.Lock() defer m.mu.Unlock() if err := startPluginAndRestartKilledProcesses(ctx, p); err != nil { return err } p.Logger().Debug("Successfully started backend plugin process") return nil } func (m *Manager) Stop(ctx context.Context, pluginID string) error { p, exists := m.pluginRegistry.Plugin(ctx, pluginID) if !exists { return backendplugin.ErrPluginNotRegistered } m.log.Debug("Stopping plugin process", "pluginID", p.ID) m.mu.Lock() defer m.mu.Unlock() if err := p.Decommission(); err != nil { return err } if err := p.Stop(ctx); err != nil { return err } return nil } // shutdown stops all backend plugin processes func (m *Manager) shutdown(ctx context.Context) { var wg sync.WaitGroup for _, p := range m.pluginRegistry.Plugins(ctx) { wg.Add(1) go func(p backendplugin.Plugin, ctx context.Context) { defer wg.Done() p.Logger().Debug("Stopping plugin") if err := p.Stop(ctx); err != nil { p.Logger().Error("Failed to stop plugin", "error", err) } p.Logger().Debug("Plugin stopped") }(p, ctx) } wg.Wait() } func startPluginAndRestartKilledProcesses(ctx context.Context, p *plugins.Plugin) error { if err := p.Start(ctx); err != nil { return err } if p.IsCorePlugin() { return nil } go func(ctx context.Context, p *plugins.Plugin) { if err := restartKilledProcess(ctx, p); err != nil { p.Logger().Error("Attempt to restart killed plugin process failed", "error", err) } }(ctx, p) return nil } func restartKilledProcess(ctx context.Context, p *plugins.Plugin) error { ticker := time.NewTicker(time.Second * 1) for { select { case <-ctx.Done(): if err := ctx.Err(); err != nil && !errors.Is(err, context.Canceled) { return err } return nil case <-ticker.C: if p.IsDecommissioned() { p.Logger().Debug("Plugin decommissioned") return nil } if !p.Exited() { continue } p.Logger().Debug("Restarting plugin") if err := p.Start(ctx); err != nil { p.Logger().Error("Failed to restart plugin", "error", err) continue } p.Logger().Debug("Plugin restarted") } } }