Files
kandev/apps/backend/internal/agent/lifecycle/runtime_registry.go
T

131 lines
3.6 KiB
Go

// Package lifecycle provides agent runtime abstractions.
package lifecycle
import (
"context"
"fmt"
"sync"
"go.uber.org/zap"
"github.com/kandev/kandev/internal/agent/runtime"
"github.com/kandev/kandev/internal/common/logger"
)
// ErrRuntimeNotFound is returned when a runtime doesn't exist in the registry.
var ErrRuntimeNotFound = fmt.Errorf("runtime not found")
// RuntimeRegistry manages multiple Runtime implementations and provides
// thread-safe access to them. It supports registering runtimes by name,
// health checking, and instance recovery across all registered runtimes.
type RuntimeRegistry struct {
runtimes map[runtime.Name]Runtime
mu sync.RWMutex
logger *logger.Logger
}
// NewRuntimeRegistry creates a new RuntimeRegistry with the given logger.
func NewRuntimeRegistry(log *logger.Logger) *RuntimeRegistry {
return &RuntimeRegistry{
runtimes: make(map[runtime.Name]Runtime),
logger: log,
}
}
// Register adds a runtime to the registry using its Name() as the key.
// If a runtime with the same name already exists, it will be replaced.
func (r *RuntimeRegistry) Register(rt Runtime) {
r.mu.Lock()
defer r.mu.Unlock()
name := rt.Name()
r.runtimes[name] = rt
r.logger.Info("registered runtime", zap.String("name", string(name)))
}
// GetRuntime returns a runtime by its name.
// Returns ErrRuntimeNotFound if the runtime doesn't exist.
func (r *RuntimeRegistry) GetRuntime(name runtime.Name) (Runtime, error) {
r.mu.RLock()
defer r.mu.RUnlock()
rt, exists := r.runtimes[name]
if !exists {
return nil, fmt.Errorf("%w: %s", ErrRuntimeNotFound, name)
}
return rt, nil
}
// List returns the names of all registered runtimes.
func (r *RuntimeRegistry) List() []runtime.Name {
r.mu.RLock()
defer r.mu.RUnlock()
names := make([]runtime.Name, 0, len(r.runtimes))
for name := range r.runtimes {
names = append(names, name)
}
return names
}
// HealthCheckAll performs health checks on all registered runtimes.
// Returns a map of runtime names to errors. A nil error indicates the runtime is healthy.
func (r *RuntimeRegistry) HealthCheckAll(ctx context.Context) map[runtime.Name]error {
r.mu.RLock()
runtimes := make(map[runtime.Name]Runtime, len(r.runtimes))
for name, rt := range r.runtimes {
runtimes[name] = rt
}
r.mu.RUnlock()
results := make(map[runtime.Name]error, len(runtimes))
for name, rt := range runtimes {
err := rt.HealthCheck(ctx)
results[name] = err
if err != nil {
r.logger.Warn("runtime health check failed",
zap.String("runtime", string(name)),
zap.Error(err))
} else {
r.logger.Debug("runtime health check passed",
zap.String("runtime", string(name)))
}
}
return results
}
// RecoverAll recovers instances from all registered runtimes.
// Returns all recovered instances and any error encountered.
// If multiple runtimes fail, only the last error is returned.
func (r *RuntimeRegistry) RecoverAll(ctx context.Context) ([]*RuntimeInstance, error) {
r.mu.RLock()
runtimes := make(map[runtime.Name]Runtime, len(r.runtimes))
for name, rt := range r.runtimes {
runtimes[name] = rt
}
r.mu.RUnlock()
var allInstances []*RuntimeInstance
var lastErr error
for name, rt := range runtimes {
instances, err := rt.RecoverInstances(ctx)
if err != nil {
r.logger.Error("failed to recover instances from runtime",
zap.String("runtime", string(name)),
zap.Error(err))
lastErr = err
continue
}
if len(instances) > 0 {
r.logger.Info("recovered instances from runtime",
zap.String("runtime", string(name)),
zap.Int("count", len(instances)))
allInstances = append(allInstances, instances...)
}
}
return allInstances, lastErr
}