manage.py work: a stop signal during a task is lost, and an interrupted errand shows as working for a week #474
Labels
No labels
accessibility
authentication
breaking change
bug
documentation
enhancement
interface
internationalisation
observability
performance
security
tier
1
tier
2
tier
3
tier/4
No project
No assignees
1 participant
Notifications
Due date
No due date set.
Dependencies
No dependencies set
Reference
Postulo/postulo#474
Loading…
Reference in a new issue
No description provided.
Delete branch "%!s()"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Found on 2026-09-29 in a read-only review of postulo at
95348908d, reading the worker loop againstdjango_tasks_db0.13.0'sWorker.Related to #247.
Checked again on 2026-10-01 against
mainat0a5cad587: still present.manage.py workwrapsdjango_tasks_db'sWorkerin its ownwhile Trueloop. When SIGTERM arrives while a task is running, upstream'sWorker.shutdownonly setsrunning = Falseand lets the task finish;run()then returns. The loop does not look at that: it writes the heartbeat, sleeps, builds a freshWorkerwithrunning = Trueand installs its signal handlers again. The graceful stop is forgotten, and the container is killed with SIGKILL at the end of its grace period, often in the middle of the next task. Nothing then moves that errand on.core/management/commands/work.py:60-81(Command.handle);core/errands.py:133(performsetsWORKINGbefore the handler runs);core/errands.py:169-179(forget_olddeletes by age only); upstreamdjango_tasks_db/management/commands/db_worker.py,Worker.shutdownandWorker.run.docker compose restart worker, or upgrades the stack, while a PDF render, capture or export is in progress. The entrypointexecs the command, so Python receives SIGTERM; the current task finishes, and the worker then carries on taking work until Docker sends SIGKILL. A task running at that moment leaves its errand inWORKINGand itsDBTaskResultinRUNNING, neither of which anything revisits. The person's page polls every few seconds and says "You can leave this page. The work carries on without you." untilforget_oldremoves the errand seven days later. Any other end of the worker mid-task (an out-of-memory kill during a Chromium render) leaves the same state.work.py, stop the loop when the finished worker'srunningis false (if not worker.running: returnafterworker.run()), or install the command's own handler that sets a flag the loop checks before building the nextWorker. Separately, have the scheduler mark errands stillWAITINGorWORKINGpast a generous deadline asFAILED, with a sentence saying the work was interrupted, so the page stops polling.Done when
tests/test_slow_work.pymakes a handler send the process's own shutdown handler a SIGTERM while it runs, and asserts thatcall_command("work")returns after that task instead of starting another pass.tests/test_errands.pyages aWORKINGerrand past the deadline, runs the scheduler's pass, and asserts that it isFAILEDwith a readable message.