package adapter import ( "context" "time" "github.com/grafana/dskit/services" "go.opentelemetry.io/otel/trace" "github.com/grafana/grafana/pkg/infra/log" "github.com/grafana/grafana/pkg/infra/tracing" grafanamodules "github.com/grafana/grafana/pkg/modules" "github.com/grafana/grafana/pkg/registry" ) var ( stopTimeout = 5 * time.Second ) type ManagerAdapter struct { services.NamedService reg registry.BackgroundServiceRegistry manager grafanamodules.Manager dependencyMap map[string][]string } // NewManagerAdapter creates a new manager adapter that bridges Grafana's background // service registry with dskit's module and service patterns. The adapter converts background // services to dskit services and manages them using dskit's module Manager, which provides: // - Coordinated service initialization // - Observable service states and health monitoring // - Graceful shutdown with proper cleanup ordering // // Services implementing CanBeDisabled that are disabled will be skipped. func NewManagerAdapter(reg registry.BackgroundServiceRegistry) *ManagerAdapter { m := &ManagerAdapter{ reg: reg, dependencyMap: dependencyMap(), } m.NamedService = services.NewBasicService(m.starting, m.running, m.stopping).WithName("backgroundsvcs.managerAdapter") return m } func (m *ManagerAdapter) WithDependencies(dependencyMap map[string][]string) *ManagerAdapter { m.dependencyMap = dependencyMap return m } func (m *ManagerAdapter) starting(ctx context.Context) error { spanCtx, span := tracing.Start(ctx, "backgroundsvcs.managerAdapter.starting") defer span.End() logger := log.New("backgroundsvcs.managerAdapter").FromContext(spanCtx) manager := grafanamodules.New(logger, []string{BackgroundServices}).WithDependencies(m.dependencyMap) for _, bgSvc := range m.reg.GetServices() { //only wrap background services that are not already a NamedService namedService, ok := bgSvc.(services.NamedService) if !ok { namedService = asNamedService(bgSvc) } // skip disabled services if s, ok := bgSvc.(registry.CanBeDisabled); ok && s.IsDisabled() { logger.Debug("Skipping disabled service", "service", namedService.ServiceName()) manager.RegisterInvisibleModule(namedService.ServiceName(), nil) continue } // register the service as an invisible module manager.RegisterInvisibleModule(namedService.ServiceName(), func() (services.Service, error) { return namedService, nil }) // add the service as a background service dependency if it's not already in the dependency map if _, ok := m.dependencyMap[namedService.ServiceName()]; !ok { m.dependencyMap[namedService.ServiceName()] = []string{Core} m.dependencyMap[BackgroundServices] = append(m.dependencyMap[BackgroundServices], namedService.ServiceName()) } } manager.RegisterModule(Core, nil) manager.RegisterModule(BackgroundServices, nil) m.manager = manager if err := m.manager.StartAsync(ctx); err != nil { return err } return m.manager.AwaitRunning(ctx) } func (m *ManagerAdapter) running(ctx context.Context) error { newCtx := trace.ContextWithSpan(context.Background(), trace.SpanFromContext(ctx)) spanCtx, span := tracing.Start(newCtx, "backgroundsvcs.managerAdapter.running") defer span.End() return m.manager.AwaitTerminated(spanCtx) } func (m *ManagerAdapter) stopping(failure error) error { ctx, cancel := context.WithTimeout(context.Background(), stopTimeout) defer cancel() spanCtx, span := tracing.Start(ctx, "backgroundsvcs.managerAdapter.stopping") defer span.End() reason := "" if failure != nil { reason = failure.Error() } return m.manager.Shutdown(spanCtx, reason) } // Run initializes and starts all background services using dskit's module and service patterns. func (m *ManagerAdapter) Run(ctx context.Context) error { if err := m.StartAsync(ctx); err != nil { return err } stopCtx := trace.ContextWithSpan(context.Background(), trace.SpanFromContext(ctx)) return m.AwaitTerminated(stopCtx) } // Shutdown calls calls the underlying manager's Shutdown func (m *ManagerAdapter) Shutdown(ctx context.Context, reason string) error { m.StopAsync() return m.AwaitTerminated(ctx) }