Bug
/next (claim-next-job, currently list(ids_only=1) in ami/jobs/views.py:296 get_queryset()) hands out CANCELLED async_api jobs forever. The exclude filter does:
jobs.exclude(
status=JobState.failed_states(),
updated_at__lt=cutoff_datetime,
)
Two problems:
JobState.failed_states() does not include CANCELLED — only [FAILURE, REVOKED, UNKNOWN]. CANCELLED jobs are treated as still-active and offered to workers indefinitely.
status= is the wrong lookup for a list-valued return — should be status__in=. As written, Django compares the column to the list as a single value; the exclude effectively never matches.
The two combine: any user-cancelled async_api job becomes a permanent zombie that workers serially "claim" every poll, then immediately bail because the corresponding NATS stream is empty/exhausted. Newer async_api jobs created after the zombies are starved out — workers never see them.
Symptom
A real async_api job sits with collect stage = SUCCESS, process stage = CREATED 0% indefinitely. NATS stream for that job ID has num_pending = total_tasks, delivered = 0. Worker stdout cycles:
Processing job <old_cancelled_id> with pipeline ...
DataLoader subprocess 0/1 starting iteration for job <old_cancelled_id>
No jobs found, sleeping for 5 seconds
…on the same handful of stale CANCELLED job IDs every 5s, never picking up the new job.
Manual unblock
Bump CANCELLED zombies to REVOKED so they fall in failed_states(). .update() bypasses auto_now, so updated_at stays at the original old value (already past the 72h FAILED_JOBS_DISPLAY_MAX_HOURS cutoff):
from django.utils import timezone
Job.objects.filter(pk__in=[<zombie ids>]).update(
status="REVOKED", finished_at=timezone.now()
)
Workers stop cycling within ~30s.
Suggested fix
Either:
- Add
CANCELLED to JobState.failed_states(), and change status= to status__in= in the exclude so the comparison actually works.
- Or short-circuit in the
/next handler with an explicit exclude(status__in=[CANCELLED, FAILURE, REVOKED, UNKNOWN]).
Links / urgency
Related to #1265 (dedicated /next action; the temporary list(ids_only=1) claim-semantics).
This bit a production deployment today — workers stuck cycling 3 zombies from 2+ weeks ago, blocking a freshly-started job. Would be good to merge alongside the next deploy today.
Bug
/next(claim-next-job, currentlylist(ids_only=1)inami/jobs/views.py:296 get_queryset()) hands out CANCELLED async_api jobs forever. The exclude filter does:Two problems:
JobState.failed_states()does not includeCANCELLED— only[FAILURE, REVOKED, UNKNOWN]. CANCELLED jobs are treated as still-active and offered to workers indefinitely.status=is the wrong lookup for a list-valued return — should bestatus__in=. As written, Django compares the column to the list as a single value; the exclude effectively never matches.The two combine: any user-cancelled async_api job becomes a permanent zombie that workers serially "claim" every poll, then immediately bail because the corresponding NATS stream is empty/exhausted. Newer async_api jobs created after the zombies are starved out — workers never see them.
Symptom
A real async_api job sits with
collectstage =SUCCESS,processstage =CREATED 0%indefinitely. NATS stream for that job ID hasnum_pending = total_tasks,delivered = 0. Worker stdout cycles:…on the same handful of stale CANCELLED job IDs every 5s, never picking up the new job.
Manual unblock
Bump CANCELLED zombies to REVOKED so they fall in
failed_states()..update()bypassesauto_now, soupdated_atstays at the original old value (already past the 72hFAILED_JOBS_DISPLAY_MAX_HOURScutoff):Workers stop cycling within ~30s.
Suggested fix
Either:
CANCELLEDtoJobState.failed_states(), and changestatus=tostatus__in=in theexcludeso the comparison actually works./nexthandler with an explicitexclude(status__in=[CANCELLED, FAILURE, REVOKED, UNKNOWN]).Links / urgency
Related to #1265 (dedicated
/nextaction; the temporarylist(ids_only=1)claim-semantics).This bit a production deployment today — workers stuck cycling 3 zombies from 2+ weeks ago, blocking a freshly-started job. Would be good to merge alongside the next deploy today.