105 lines
2.7 KiB
Go
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
|
|
}
|