Repository navigation
Fix background assembly waiting after task result expiration - #1113
nonprofittechy wants to merge 3 commits into
Conversation
There was a problem hiding this comment.
Copilot review overview
🟢 Approval recommended
No unresolved blocking issues were identified.
Review effort: Lite
Findings: None
What changed in this PR
Fixes background document assembly getting stuck after Celery task-result expiration by using durable completion gates.
Changes:
- Adds PDF, DOCX, and preview completion gates.
- Invalidates cached results during regeneration.
- Updates the example interview and adds focused tests.
| File | Description |
|---|---|
docassemble/AssemblyLine/test_background_assembly.py |
Tests expiration-safe completion and regeneration. |
docassemble/AssemblyLine/data/questions/test_aldocument_background_assembly.yml |
Demonstrates durable gates and regeneration. |
docassemble/AssemblyLine/data/questions/al_document.yml |
Implements completion gates and cache invalidation. |
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
rajeswaripedaballi
left a comment
There was a problem hiding this comment.
Looks good to me, but one thing I noticed while testing is I made the job crash on purpose, and the page stayed on the waiting screen and kept refreshing with the error showing in the worker log. I think with the old check a crashed job would still let the person reach the download screen, so I wanted to know if this is intended?
rajeswaripedaballi
left a comment
There was a problem hiding this comment.
I tested the crash case again, the page now shows an error with Try again and it makes the documents. Looks good to me.
Fixes #1069.
Returning to a completed interview after Celery expires its result currently sends users back to the document-generation waiting screen indefinitely. This change adds saved completion gates that recognize the files persisted by the background response callback, including when the user leaves before generation finishes.
The standard task-start blocks clear the saved files and completion gates when explicitly reconsidered. The runnable example uses the new gates and includes a regenerate action. Existing interviews must replace their direct
.ready()checks; upgrading AssemblyLine alone cannot change those interview conditions.Validation: 51 automated tests passed, plus localhost expiration, reopen, leave-during-generation, and regeneration checks for PDF, DOCX, and preview. The full example interview also passed. Details and concrete migration examples follow; no documentation file is included in this PR.
Background assembly that survives task-result expiration
Use a saved completion variable in your interview order, instead of calling a
Celery task's
.ready()method. Celery's result is temporary; the files saved tothe interview by AssemblyLine's completion callback are durable.
Standard workflow
After collecting all answers needed by your attachments, use one of these
variables:
al_user_bundle.preview_readyal_user_bundle.generate_preview_taskal_user_bundle.downloads_readyal_user_bundle.generate_downloads_taskal_user_bundle.downloads_with_docx_readyal_user_bundle.generate_downloads_with_docx_taskFor example, an interview with preview and editable downloads can end this way:
The standard gates start the task once and show the standard waiting screen until
its callback saves the files. They then save
Trueto the completion variable.They do not ask Celery whether an old task is still ready. If the user closes the
browser while generation is running, the callback still saves the files. On
return, the gate recognizes those files even if Celery has already expired its
result. An empty bundle is also a completed result; the check uses
defined(),not the truthiness of the saved files.
For a runnable example with attachments, see
test_aldocument_background_assembly.yml.Regenerate after editing answers
Once the user has finished editing and all attachment inputs are defined again,
explicitly reconsider the appropriate task. For example, an action linked from a
completed download screen can use:
Link to that action with:
${ action_button_html(url_action('regenerate_downloads'), label='Regenerate documents') }The task-start block clears the saved download files and both download completion
variables before queuing a new task. The interview order then reaches
al_user_bundle.downloads_with_docx_readyagain and waits for the replacementfiles. Reconsidering
al_user_bundle.generate_preview_tasksimilarly clears thepreview file and
preview_ready. If your preview screen has a continue-buttonvariable such as
preview_screen, undefine that variable too when you want theuser to see the preview screen again.
Reconsidering only the completion variable rechecks the current saved result;
it does not regenerate documents. Do not place the task's
reconsider()in amandatory block, because that would restart assembly on every reload. Use one
final-download mode per bundle at a time, and finish an existing task before
starting another for that same bundle. The PDF and DOCX modes share a saved file
cache; starting either mode invalidates the other mode's task handle and gate.
If your interview separately assembled attachments in the foreground before an
edit, invalidate those attachment variables and any document/bundle caches as
part of your existing edit workflow. Restarting the background task clears the
background result, not every possible foreground attachment cache.
Updating an existing interview
Replace this older pattern:
with a reference in your interview order:
Use the other gates in the table for PDF-only downloads or previews. Keep
use_previously_cached_files=Trueon the download screen. Existing saved sessionswith the standard callback's cached files can use the new gates without running
their expired tasks again. Updating AssemblyLine alone cannot rewrite an
interview's existing
.ready()condition.Custom background actions
A custom action should save its result with
background_response_action()anduse an intermediate completion block that checks for the saved result. Do not
save a one-time
Falseresult while waiting. For example:To regenerate, call
reconsider("report_task")from an explicit action after anyrequired edits. This invalidates both the old result and the intermediate gate.
See docassemble's background action documentation
for how the callback saves changes to the interview.
Expiration and verification
Celery defaults to retaining results for one day. The localhost docassemble
1.10.10 installation exposes
celery result retention days; itsconfig_worker.pyconverts that value to seconds for Celery'sresult_expires.Older versions may not expose this setting. Increasing retention merely delays
the failure of an interview that repeatedly checks
.ready().The issue #1069 regression was tested on localhost using uniquely named interview
fixtures and fresh sessions. Only the fixtures' exact
celery-task-meta-<UUID>Redis keys had their TTLs shortened to two seconds. No global configuration,
worker restart, queue purge, or changes to another test's result keys were needed.
For each of PDF downloads, DOCX downloads, and previews, verify:
.ready()condition; expire its result; reopen thesession. It returns to the waiting screen despite having saved files.
downloadable while
.ready()isFalse, without a replacement task.reopen. The gate recognizes the saved files on the first return.
returns, a new task runs, and the downloaded PDF contains the updated input.
The focused automated tests in
test_background_assembly.pycover expired-taskindependence, waiting until the callback saves a result, empty results, and
invalidation when regeneration starts.
Observed on localhost on September 26, 2026 (docassemble 1.10.10):
The DOCX downloads were also fetched after expiration and their document text
checked, including the changed input after regeneration. The original TTLs of
the test results were 86,392–86,400 seconds before being shortened to two seconds.
The focused tests plus the existing
test_al_document.pysuite passed (51 tests).An isolated copy of the runnable example above also passed its full preview,
download, and regenerate flow on localhost.