package queue import ( "context" "fmt" "sync" "git.warky.dev/wdevs/pgsql-broker/pkg/broker/adapter" "git.warky.dev/wdevs/pgsql-broker/pkg/broker/models" "git.warky.dev/wdevs/pgsql-broker/pkg/broker/worker" ) // Queue manages a collection of workers for a specific queue number type Queue struct { Number int InstanceID int64 workers []*worker.Worker db adapter.DBAdapter logger adapter.Logger ctx context.Context cancel context.CancelFunc mu sync.RWMutex workerCount int } // Config holds queue configuration type Config struct { Number int InstanceID int64 WorkerCount int DBAdapter adapter.DBAdapter Logger adapter.Logger BufferSize int TimerSeconds int FetchSize int } // New creates a new queue manager func New(cfg Config) *Queue { ctx, cancel := context.WithCancel(context.Background()) logger := cfg.Logger.With("queue", cfg.Number) return &Queue{ Number: cfg.Number, InstanceID: cfg.InstanceID, workers: make([]*worker.Worker, 0, cfg.WorkerCount), db: cfg.DBAdapter, logger: logger, ctx: ctx, cancel: cancel, workerCount: cfg.WorkerCount, } } // Start initializes and starts all workers in the queue func (q *Queue) Start(cfg Config) error { q.mu.Lock() defer q.mu.Unlock() q.logger.Info("starting queue", "worker_count", q.workerCount) for i := 0; i < q.workerCount; i++ { w := worker.New(worker.Config{ ID: i + 1, QueueNumber: q.Number, InstanceID: q.InstanceID, DBAdapter: cfg.DBAdapter, Logger: cfg.Logger, BufferSize: cfg.BufferSize, TimerSeconds: cfg.TimerSeconds, FetchSize: cfg.FetchSize, }) if err := w.Start(q.ctx); err != nil { return fmt.Errorf("failed to start worker %d: %w", i+1, err) } q.workers = append(q.workers, w) } q.logger.Info("queue started successfully", "workers", len(q.workers)) return nil } // Stop gracefully stops all workers in the queue func (q *Queue) Stop() error { q.mu.Lock() defer q.mu.Unlock() q.logger.Info("stopping queue") // Cancel context to signal all workers q.cancel() // Stop each worker var stopErrors []error for i, w := range q.workers { if err := w.Stop(); err != nil { stopErrors = append(stopErrors, fmt.Errorf("worker %d: %w", i+1, err)) } } if len(stopErrors) > 0 { return fmt.Errorf("errors stopping workers: %v", stopErrors) } q.logger.Info("queue stopped successfully") return nil } // AddJob adds a job to the least busy worker func (q *Queue) AddJob(job models.Job) error { q.mu.RLock() defer q.mu.RUnlock() if len(q.workers) == 0 { return fmt.Errorf("no workers available") } // Simple round-robin: use first available worker // Could be enhanced with load balancing for _, w := range q.workers { if err := w.AddJob(job); err == nil { return nil } } return fmt.Errorf("all workers are busy") } // GetStats returns statistics for all workers in the queue func (q *Queue) GetStats() map[int]worker.Stats { q.mu.RLock() defer q.mu.RUnlock() stats := make(map[int]worker.Stats) for i, w := range q.workers { lastActivity, jobsHandled, running := w.GetStats() stats[i+1] = worker.Stats{ LastActivity: lastActivity, JobsHandled: jobsHandled, Running: running, } } return stats }