sp2-verify-1770324794/workers/background-processor/internal/handlers/jobs.go
rdev-worker 154c535204
All checks were successful
ci/woodpecker/push/woodpecker Pipeline was successful
build: /implement-feature async-jobs --requirements 'API: POST /jobs pushes ...
2026-02-05 21:04:47 +00:00

105 lines
2.7 KiB
Go

package handlers
import (
"context"
"time"
"git.threesix.ai/jordan/sp2-verify-1770324794/pkg/logging"
"git.threesix.ai/jordan/sp2-verify-1770324794/pkg/redisqueue"
)
// AsyncJobProcessor processes jobs from the Redis queue.
type AsyncJobProcessor struct {
logger *logging.Logger
queue *redisqueue.RedisQueue
simulationDuration time.Duration
pollTimeout time.Duration
}
// AsyncJobProcessorConfig holds configuration for the async job processor.
type AsyncJobProcessorConfig struct {
SimulationDuration time.Duration
PollTimeout time.Duration
}
// NewAsyncJobProcessor creates a new async job processor.
func NewAsyncJobProcessor(queue *redisqueue.RedisQueue, logger *logging.Logger, cfg AsyncJobProcessorConfig) *AsyncJobProcessor {
if cfg.SimulationDuration == 0 {
cfg.SimulationDuration = 2 * time.Second
}
if cfg.PollTimeout == 0 {
cfg.PollTimeout = 5 * time.Second
}
return &AsyncJobProcessor{
logger: logger.WithComponent("async-job-processor"),
queue: queue,
simulationDuration: cfg.SimulationDuration,
pollTimeout: cfg.PollTimeout,
}
}
// Run starts the async job processing loop.
// It continuously polls the Redis queue for jobs and processes them.
func (p *AsyncJobProcessor) Run(ctx context.Context) {
p.logger.Info("async job processor started",
"simulation_duration", p.simulationDuration,
"poll_timeout", p.pollTimeout,
)
for {
select {
case <-ctx.Done():
p.logger.Info("async job processor stopping")
return
default:
if err := p.processNextJob(ctx); err != nil {
if err == redisqueue.ErrNoJob {
// No jobs available, continue polling
continue
}
p.logger.Error("error processing job", "error", err)
// Brief pause on error to avoid tight loop
time.Sleep(time.Second)
}
}
}
}
// processNextJob dequeues and processes a single job from the Redis queue.
func (p *AsyncJobProcessor) processNextJob(ctx context.Context) error {
// Dequeue with timeout (blocks until job available or timeout)
job, err := p.queue.Dequeue(ctx, p.pollTimeout)
if err != nil {
return err
}
p.logger.Info("processing job",
"job_id", job.ID,
"type", job.Type,
"payload", job.Payload,
)
// Simulate work
select {
case <-ctx.Done():
// Context cancelled during work - don't mark as failed, let it be reprocessed
p.logger.Warn("job processing interrupted", "job_id", job.ID)
return ctx.Err()
case <-time.After(p.simulationDuration):
// Work completed
}
// Mark job as completed
if err := p.queue.Complete(ctx, job.ID); err != nil {
p.logger.Error("failed to complete job",
"job_id", job.ID,
"error", err,
)
return err
}
p.logger.Info("job completed", "job_id", job.ID)
return nil
}