package traefikoidc import ( "context" "sync" "time" ) // GoroutineManager manages background goroutines with proper lifecycle type GoroutineManager struct { ctx context.Context cancel context.CancelFunc goroutines map[string]*managedGoroutine logger *Logger wg sync.WaitGroup mu sync.RWMutex } type managedGoroutine struct { startTime time.Time cancel context.CancelFunc name string running bool } // NewGoroutineManager creates a new goroutine manager func NewGoroutineManager(logger *Logger) *GoroutineManager { ctx, cancel := context.WithCancel(context.Background()) return &GoroutineManager{ ctx: ctx, cancel: cancel, goroutines: make(map[string]*managedGoroutine), logger: logger, } } // StartGoroutine starts a managed goroutine with context-based cancellation func (m *GoroutineManager) StartGoroutine(name string, fn func(context.Context)) { m.mu.Lock() defer m.mu.Unlock() // Check if goroutine with this name already exists if existing, exists := m.goroutines[name]; exists && existing.running { m.logger.Debugf("Goroutine %s already running, skipping start", name) return } // Create goroutine-specific context goroutineCtx, goroutineCancel := context.WithCancel(m.ctx) managed := &managedGoroutine{ name: name, cancel: goroutineCancel, startTime: time.Now(), running: true, } m.goroutines[name] = managed m.wg.Add(1) go func(managedGoroutine *managedGoroutine, goroutineName string) { defer func() { m.wg.Done() m.mu.Lock() managedGoroutine.running = false m.mu.Unlock() // Recover from panics if r := recover(); r != nil { m.logger.Errorf("Goroutine %s panic recovered: %v", goroutineName, r) } }() m.logger.Debugf("Starting goroutine: %s", goroutineName) fn(goroutineCtx) m.logger.Debugf("Goroutine %s finished", goroutineName) }(managed, name) } // StartPeriodicTask starts a periodic task with context-based cancellation func (m *GoroutineManager) StartPeriodicTask(name string, interval time.Duration, task func()) { m.StartGoroutine(name, func(ctx context.Context) { ticker := time.NewTicker(interval) defer ticker.Stop() for { select { case <-ctx.Done(): m.logger.Debugf("Periodic task %s canceled", name) return case <-ticker.C: task() } } }) } // StopGoroutine stops a specific goroutine by name func (m *GoroutineManager) StopGoroutine(name string) { m.mu.Lock() defer m.mu.Unlock() if managed, exists := m.goroutines[name]; exists && managed.running { m.logger.Debugf("Stopping goroutine: %s", name) managed.cancel() } } // Shutdown gracefully shuts down all managed goroutines func (m *GoroutineManager) Shutdown(timeout time.Duration) error { m.logger.Debug("Starting goroutine manager shutdown") // Cancel the main context to signal all goroutines to stop m.cancel() // Wait for all goroutines with timeout done := make(chan struct{}) go func() { m.wg.Wait() close(done) }() select { case <-done: m.logger.Debug("All goroutines stopped gracefully") return nil case <-time.After(timeout): m.logger.Error("Timeout waiting for goroutines to stop") return ErrShutdownTimeout } } // GetStatus returns the status of all managed goroutines func (m *GoroutineManager) GetStatus() map[string]GoroutineStatus { m.mu.RLock() defer m.mu.RUnlock() status := make(map[string]GoroutineStatus) for name, managed := range m.goroutines { status[name] = GoroutineStatus{ Name: managed.name, Running: managed.running, StartTime: managed.startTime, Runtime: time.Since(managed.startTime), } } return status } // GoroutineStatus represents the status of a managed goroutine type GoroutineStatus struct { StartTime time.Time Name string Runtime time.Duration Running bool } // ErrShutdownTimeout is returned when shutdown times out var ErrShutdownTimeout = &shutdownTimeoutError{} type shutdownTimeoutError struct{} func (e *shutdownTimeoutError) Error() string { return "shutdown timeout: some goroutines did not stop in time" }