@@ -43,13 +43,14 @@ const (
43
43
AND status NOT IN ('processed')
44
44
FOR UPDATE SKIP LOCKED
45
45
LIMIT 1`
46
- PendingJobIDQuery = `SELECT id
46
+ PendingJobIDsQuery = `SELECT id
47
47
FROM neoq_jobs
48
48
WHERE queue = $1
49
49
AND status NOT IN ('processed')
50
50
AND run_after <= NOW()
51
+ ORDER BY created_at ASC
51
52
FOR UPDATE SKIP LOCKED
52
- LIMIT 1 `
53
+ LIMIT 100 `
53
54
FutureJobQuery = `SELECT id,fingerprint,queue,status,deadline,payload,retries,max_retries,run_after,ran_at,created_at,error
54
55
FROM neoq_jobs
55
56
WHERE queue = $1
@@ -674,8 +675,7 @@ func (p *PgBackend) start(ctx context.Context, h handler.Handler) (err error) {
674
675
return fmt .Errorf ("%w: %s" , handler .ErrNoHandlerForQueue , h .Queue )
675
676
}
676
677
677
- pendingJobsChan := p .pendingJobs (ctx , h .Queue ) // process overdue jobs *at startup*
678
-
678
+ pendingJobsChan := p .processPendingJobs (ctx , h .Queue )
679
679
// wait for the listener to connect and be ready to listen
680
680
for q := range p .readyQueues {
681
681
if q == h .Queue {
@@ -688,7 +688,7 @@ func (p *PgBackend) start(ctx context.Context, h handler.Handler) (err error) {
688
688
p .readyQueues <- q
689
689
}
690
690
691
- // process all future jobs and retries
691
+ // process all future jobs
692
692
go func () { p .scheduleFutureJobs (ctx , h .Queue ) }()
693
693
694
694
for i := 0 ; i < h .Concurrency ; i ++ {
@@ -754,7 +754,7 @@ func (p *PgBackend) initFutureJobs(ctx context.Context, queue string) (err error
754
754
return
755
755
}
756
756
757
- // scheduleFutureJobs announces future jobs using NOTIFY on an interval
757
+ // scheduleFutureJobs monitors the future job list for upcoming jobs and announces them to be processed by available workers
758
758
func (p * PgBackend ) scheduleFutureJobs (ctx context.Context , queue string ) {
759
759
err := p .initFutureJobs (ctx , queue )
760
760
if err != nil {
@@ -772,8 +772,8 @@ func (p *PgBackend) scheduleFutureJobs(ctx context.Context, queue string) {
772
772
if timeUntillRunAfter <= p .config .FutureJobWindow {
773
773
delete (p .futureJobs , jobID )
774
774
go func (jid string , j * jobs.Job ) {
775
- scheduleCh := time .After (timeUntillRunAfter )
776
- <- scheduleCh
775
+ jobDue := time .After (timeUntillRunAfter )
776
+ <- jobDue
777
777
p .announceJob (ctx , j .Queue , jid )
778
778
}(jobID , job )
779
779
}
@@ -823,9 +823,11 @@ func (p *PgBackend) announceJob(ctx context.Context, queue, jobID string) {
823
823
}
824
824
}
825
825
826
- func (p * PgBackend ) pendingJobs (ctx context.Context , queue string ) (jobsCh chan * pgconn.Notification ) {
827
- jobsCh = make (chan * pgconn.Notification )
828
-
826
+ // processPendingJobs starts a goroutine that periodically fetches pendings jobs and announces them to workers.
827
+ //
828
+ // Past due jobs are fetched on the interval [neoq.Config.JobCheckInterval]
829
+ // nolint: cyclop
830
+ func (p * PgBackend ) processPendingJobs (ctx context.Context , queue string ) (jobsCh chan * pgconn.Notification ) {
829
831
conn , err := p .acquire (ctx )
830
832
if err != nil {
831
833
p .logger .Error (
@@ -836,28 +838,37 @@ func (p *PgBackend) pendingJobs(ctx context.Context, queue string) (jobsCh chan
836
838
return
837
839
}
838
840
841
+ // check for new past-due jobs on an interval
842
+ ticker := time .NewTicker (p .config .JobCheckInterval )
839
843
go func (ctx context.Context ) {
840
844
defer conn .Release ()
841
-
845
+ // check for pending jobs on an interval until the context is canceled
842
846
for {
843
- jobID , err := p .getPendingJobID (ctx , conn , queue )
844
- if err != nil {
845
- if errors .Is (err , pgx .ErrNoRows ) || errors .Is (err , context .Canceled ) {
846
- break
847
- }
847
+ jobIDs , err := p .getPendingJobIDs (ctx , conn , queue )
848
+ if errors .Is (err , context .Canceled ) {
849
+ return
850
+ }
848
851
852
+ if err != nil && ! errors .Is (err , pgx .ErrNoRows ) {
849
853
p .logger .Error (
850
854
"failed to fetch pending job" ,
851
855
slog .String ("queue" , queue ),
852
856
slog .Any ("error" , err ),
853
- slog .String ("job_id" , jobID ),
854
857
)
855
- } else {
856
- jobsCh <- & pgconn.Notification {Channel : queue , Payload : jobID }
858
+ }
859
+
860
+ for _ , jid := range jobIDs {
861
+ jobsCh <- & pgconn.Notification {Channel : queue , Payload : jid }
862
+ }
863
+ select {
864
+ case <- ctx .Done ():
865
+ return
866
+ case <- ticker .C :
857
867
}
858
868
}
859
869
}(ctx )
860
870
871
+ jobsCh = make (chan * pgconn.Notification )
861
872
return jobsCh
862
873
}
863
874
@@ -1030,8 +1041,17 @@ func (p *PgBackend) getJob(ctx context.Context, tx pgx.Tx, jobID string) (job *j
1030
1041
return
1031
1042
}
1032
1043
1033
- func (p * PgBackend ) getPendingJobID (ctx context.Context , conn * pgxpool.Conn , queue string ) (jobID string , err error ) {
1034
- err = conn .QueryRow (ctx , PendingJobIDQuery , queue ).Scan (& jobID )
1044
+ func (p * PgBackend ) getPendingJobIDs (ctx context.Context , conn * pgxpool.Conn , queue string ) (jobIDs []string , err error ) {
1045
+ var rows pgx.Rows
1046
+ var jid int64
1047
+ rows , err = conn .Query (ctx , PendingJobIDsQuery , queue )
1048
+ for rows .Next () {
1049
+ err = rows .Scan (& jid )
1050
+ if err != nil {
1051
+ return
1052
+ }
1053
+ jobIDs = append (jobIDs , fmt .Sprint (jid ))
1054
+ }
1035
1055
return
1036
1056
}
1037
1057
0 commit comments