package mq import ( "context" "log" "time" "github.com/oarkflow/mq/consts" ) // processDeferredTasks continuously checks for deferred tasks that are ready to be executed func (b *Broker) processDeferredTasks(ctx context.Context) { ticker := time.NewTicker(1 * time.Second) // Check every second defer ticker.Stop() for { select { case <-ctx.Done(): return case <-b.stopDeferredChan: return case <-ticker.C: now := time.Now() var tasksToProcess []*QueuedTask var taskIDsToRemove []string // Collect tasks that are ready to be processed b.deferredTasks.ForEach(func(taskID string, queuedTask *QueuedTask) bool { if queuedTask != nil && queuedTask.Task != nil { if queuedTask.Task.DeferUntil.Before(now) || queuedTask.Task.DeferUntil.Equal(now) { tasksToProcess = append(tasksToProcess, queuedTask) taskIDsToRemove = append(taskIDsToRemove, taskID) } } return true }) // Process and remove ready tasks for i, task := range tasksToProcess { if task != nil && task.Message != nil { queueName := task.Message.Queue if queue, ok := b.queues.Get(queueName); ok { // Send task to the queue for processing select { case queue.tasks <- task: log.Printf("[DEFERRED] Task %s is now ready for processing on queue %s", task.Task.ID, queueName) b.deferredTasks.Del(taskIDsToRemove[i]) default: // Queue is full, keep in deferred state log.Printf("[DEFERRED] Queue %s is full, task %s will retry", queueName, task.Task.ID) } } else { log.Printf("[DEFERRED] Queue %s not found for task %s", queueName, task.Task.ID) b.deferredTasks.Del(taskIDsToRemove[i]) } } } } } } // StartDeferredTaskProcessor starts the background processor for deferred tasks func (b *Broker) StartDeferredTaskProcessor(ctx context.Context) { go b.processDeferredTasks(ctx) log.Println("[DEFERRED] Deferred task processor started") } // StopDeferredTaskProcessor stops the deferred task processor func (b *Broker) StopDeferredTaskProcessor() { close(b.stopDeferredChan) log.Println("[DEFERRED] Deferred task processor stopped") } // AddDeferredTask adds a task to the deferred queue func (b *Broker) AddDeferredTask(task *QueuedTask) { if task != nil && task.Task != nil { b.deferredTasks.Set(task.Task.ID, task) log.Printf("[DEFERRED] Task %s deferred until %s", task.Task.ID, task.Task.DeferUntil.Format(time.RFC3339)) } } // CreateDLQConsumer creates a consumer for a DLQ queue with a retry handler func (b *Broker) CreateDLQConsumer(ctx context.Context, queueName string, retryHandler Handler) error { dlqName := queueName + "_dlq" // Check if DLQ exists dlq, ok := b.deadLetter.Get(queueName) if !ok { return nil // No DLQ for this queue } // Create a consumer for the DLQ consumerID := "dlq-consumer-" + queueName // Define DLQ processing handler dlqHandler := func(ctx context.Context, task *Task) Result { log.Printf("[DLQ] Processing task %s from DLQ %s (attempt %d)", task.ID, dlqName, task.Retries+1) // Call custom retry handler if provided if retryHandler != nil { result := retryHandler(ctx, task) if result.Status == Completed { log.Printf("[DLQ] Task %s successfully reprocessed from DLQ", task.ID) return result } } // Default behavior: retry with exponential backoff task.Retries++ if task.Retries < task.MaxRetries { // Defer for exponential backoff backoffDuration := time.Duration(1< 4 { originalQueue = queueName[:len(queueName)-4] } err := b.CreateDLQConsumer(ctx, originalQueue, retryHandler) if err != nil { log.Printf("[DLQ] Failed to create consumer for DLQ %s: %v", queueName, err) } return true }) log.Println("[DLQ] DLQ consumers setup complete") }