package scheduler import ( "context" "time" "go.uber.org/zap" tenantapp "github.com/geo-platform/tenant-api/internal/tenant/app" ) const ( defaultPublishLeaseRecoveryInterval = 3 * time.Minute defaultPublishLeaseRecoveryTimeout = 30 * time.Second defaultPublishLeaseRecoveryLimit = 1000 ) type PublishLeaseRecoveryWorker struct { service *tenantapp.DesktopTaskService logger *zap.Logger interval time.Duration timeout time.Duration limit int } func NewPublishLeaseRecoveryWorker(service *tenantapp.DesktopTaskService, logger *zap.Logger) *PublishLeaseRecoveryWorker { return &PublishLeaseRecoveryWorker{ service: service, logger: logger, interval: defaultPublishLeaseRecoveryInterval, timeout: defaultPublishLeaseRecoveryTimeout, limit: defaultPublishLeaseRecoveryLimit, } } func (w *PublishLeaseRecoveryWorker) Start(ctx context.Context) { go w.Run(ctx) } func (w *PublishLeaseRecoveryWorker) Run(ctx context.Context) { if w == nil || w.service == nil { return } w.run(ctx) } func (w *PublishLeaseRecoveryWorker) run(ctx context.Context) { w.runOnce(context.Background()) ticker := time.NewTicker(w.interval) defer ticker.Stop() for { select { case <-ctx.Done(): return case <-ticker.C: w.runOnce(context.Background()) } } } func (w *PublishLeaseRecoveryWorker) runOnce(parent context.Context) { ctx, cancel := context.WithTimeout(parent, w.timeout) defer cancel() result, err := w.service.RecoverExpiredPublishTasks(ctx, w.limit) if err != nil { if w.logger != nil { w.logger.Warn("publish lease recovery failed", zap.Error(err)) } return } if (result.Requeued > 0 || result.Failed > 0 || result.Uncertain > 0) && w.logger != nil { w.logger.Info("publish lease recovery completed", zap.Int("requeued_task_count", result.Requeued), zap.Int("failed_task_count", result.Failed), zap.Int("uncertain_task_count", result.Uncertain), ) } } func (w *PublishLeaseRecoveryWorker) RunOnce(parent context.Context, run JobRunContext) (map[string]any, error) { if w == nil || w.service == nil { return map[string]any{"skipped": true, "reason": "worker_not_ready"}, nil } startedAt := time.Now() timeout := w.timeout if run.Job != nil && run.Job.TimeoutSeconds > 0 { timeout = time.Duration(run.Job.TimeoutSeconds) * time.Second } limit := w.limit if run.Job != nil && run.Job.BatchSize != nil && *run.Job.BatchSize > 0 { limit = *run.Job.BatchSize } if value := AsInt(run.Config, "batch_size", 0); value > 0 && (run.Job == nil || run.Job.BatchSize == nil) { limit = value } ctx, cancel := context.WithTimeout(parent, timeout) defer cancel() result, err := w.service.RecoverExpiredPublishTasks(ctx, limit) if err != nil { return map[string]any{"stage": "recover"}, err } return map[string]any{ "requeued_task_count": result.Requeued, "failed_task_count": result.Failed, "uncertain_task_count": result.Uncertain, "duration_ms": time.Since(startedAt).Milliseconds(), "batch_size": limit, }, nil }