fix: restart QUEUED tasks and re-arm poll loop on retry failure (LOD setup wizard dead-end) - #15271
ShadowSpatula wants to merge 1 commit into
Conversation
- Allow restarting QUEUED tasks in TasksViewSet.restart() — return 200 instead of raising ValidationError so the retry flow doesn't hit the global error page (issue learningequality#15235). - Re-arm the poll loop in retryImport() after a successful restart so the loading step reflects the restarted task instead of sticking on the old FAILED snapshot. - Silence restart failures in retryImport() instead of calling handleApiError — the poll loop still runs and will surface the real task state; the user stays in the wizard. - Always re-arm the poll loop on any error in pollTask() (not just 500) so a transient network hiccup doesn't freeze the loading step. - Clear tasks in startOver() before sending START_OVER so queued/running tasks are actually cleared. - Add regression tests: poll loop re-arm after retry, non-500 resilience, and restart failure keeps the wizard loaded. Fixes learningequality#15235
|
👋 Hi @ShadowSpatula, thanks for contributing! For the review process to begin, please verify that the following is satisfied:
Also check that issue requirements are satisfied & you ran Pull requests that don't follow the guidelines will be closed. Reviewer assignment can take up to 2 weeks. |
Summary
Fix the setup wizard dead-end when importing LOD users fails.
When a
peeruserimporttask enters theQUEUED/RUNNINGstate and the user retries or the poll loop encounters a non-500 error, the wizard can stall indefinitely or kick the user to the global error page. This commit fixes five failure points in theLoadingTaskPagepoll/retry flow and the task restart API.Problem
Five distinct behaviors combine into the dead-end reported in #15235:
pollTask()only re-arms thesetTimeoutwhenthis.runningTasks.length > 0. When the only task in the queue isQUEUEDorRUNNING(notCOMPLETED/CANCELED/FAILED),runningTasksis empty, the poll loop stops, and the page shows nothing to the user..catch()handler only re-arms the poll onerror.status == 500. A transient network hiccup, 403, or any other non-500 failure permanently freezes the loading step.TaskResource.restart_v2(), the component never restarts polling — the loading step stays stuck on the oldFAILEDsnapshot.restart_v2()rejects (e.g. the task is alreadyQUEUEDand the backend raisesValidationError),retryImport()callshandleApiError({ error }), which routes the user away from the wizard to a global error page.startOver()sendsSTART_OVERwithout clearing the queue first, soQUEUED/RUNNINGtasks from a previous import attempt remain visible.Approach
Frontend (
LoadingTaskPage.vue)pollTask()— always re-arm the poll loop in.catch(), not just on 500. A transient error should never freeze the loading step.retryImport()— on success, re-armisPolling = trueand callpollTask()immediately so the loading step reflects the restarted task. On failure, silently swallow the rejection instead of callinghandleApiError— the poll loop still runs and will surface the real task state on the next fetch; the user stays in the wizard.startOver()— clear tasks viaTaskResource.clearAll_v2()before sendingSTART_OVERso queued/running tasks from a previous attempt are actually cleared.Backend (
viewsets/tasks.py)TasksViewSet.restart()— whenrestart_jobraisesJobNotRestartablebecause the job isQUEUED, return a 200 response with the current job representation instead of raisingValidationError. AQUEUEDjob is idempotent to restart-by-nothing: the caller should just poll again. This prevents the "Cannot restart job with state: QUEUED" error that would otherwise reachhandleApiErrorand kick the user to the global error page.The deeper precondition in
storage.py:restart_job(onlyCANCELED/FAILEDare restartable) is left intact — the frontend-level fix is the user-visible repair, and the backend change kills the QUEUED error before it reaches the error page.Regression tests
Three new tests in
LoadingTaskPage.spec.js, following the existing patterns (mock TaskResource.list,makeTaskhelper,flushPromises, emit assertions):TaskResource.listreturningRUNNING→COMPLETED, verifiesemit('finish')called after completion.TaskResource.listrejecting with 403, verifiessetTimeoutstill called (poll loop doesn't die).TaskResource.restart_v2rejecting, verifieshandleApiErrorcalled withreplaceWizard: false.Risk
Out of scope
peeruserimporttasks overloading devices). That is a different concern; this fix makes the current wizard usable when a single import fails, without waiting for the redesign.importLodUsersMachine— the machine is intentionally non-cancellable (cancellable: false,longRunning: true); this fix works within those constraints.AI usage
I used AI assistance (Solar via Hermes Agent) while working on this PR.
pollTask()error handling,retryImport()re-arm and error swallowing,startOver()queue clearing, and theQUEUEDrestart precondition inTasksViewSet.restart().LoadingTaskPage.spec.js.retryImport()swallow restart failures instead of routing to the global error page, clear the queue instartOver(), and relax the backendrestart()precondition forQUEUEDtasks so the error never reaches the frontend in the first place.Fixes #15235