fix: correct stale state and JSON output in queue sync-output#1287
Open
crowecawcaw wants to merge 6 commits into
Open
fix: correct stale state and JSON output in queue sync-output#1287crowecawcaw wants to merge 6 commits into
crowecawcaw wants to merge 6 commits into
Conversation
Three related defects in the incremental output download loop
(_incremental_download.py) all involved carrying wrong per-job state:
- _update_checkpoint_jobs_list reused a leftover max_session_ended_timestamp
from the last job of the first loop and wrote it onto every job in the second
loop, corrupting checkpoint session-ended timestamps (missed or redundant
downloads). The stale reassignment is removed.
- A job whose current sessions are all still running (no endedAt) had its saved
session_ended_timestamp overwritten with None. The per-job max is now seeded
from the previously saved checkpoint value.
- _get_job_sessions skipped the eventual-consistency window for re-queued jobs.
A re-queued job is categorized as 'added' but carries a saved
session_ended_timestamp, so the window is now keyed off whether the job is
genuinely brand new rather than the 'added' category alone.
- _filter_session_actions_without_manifests_from_job_sessions hard-accessed
session_action["manifests"], raising KeyError when the field is absent; it
now uses .get("manifests", []).
Signed-off-by: Stephen Crowe <6042774+crowecawcaw@users.noreply.github.com>
_get_download_candidate_jobs emitted DEBUG lines via raw print() to stdout unconditionally, corrupting the JSON stream in `queue sync-output --json`. Route them through print_function_callback, which the CLI wires to a JSON-aware logger that suppresses these messages in --json mode. Signed-off-by: Stephen Crowe <6042774+crowecawcaw@users.noreply.github.com>
Signed-off-by: Stephen Crowe <6042774+crowecawcaw@users.noreply.github.com>
crowecawcaw
marked this pull request as ready for review
July 22, 2026 20:43
Add a test that drives 'deadline queue sync-output --json' through an actual multi-file S3 download (moto bucket with real asset manifests and CAS data objects), with the real ProgressTracker firing progress callbacks. Asserts stdout is byte-clean in --json mode while the files are downloaded byte-for-byte, and contrasts with a non-json run that emits the DEBUG lines and the 100% progress message — proving the callbacks fired and were suppressed rather than the download being skipped. Signed-off-by: Stephen Crowe <6042774+crowecawcaw@users.noreply.github.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Fixes:
What was the problem/requirement? (What/Why)
Several state bugs in the
queue sync-outputincremental-download loop:max_session_ended_timestampfrom the last job of the first loop was reused in a second loop and written onto every job's checkpoint timestamp → missed output downloads / redundant re-downloads on the nextsync-output.print()DEBUG lines were emitted even under--json, corrupting the JSON output.None;session_action["manifests"]was accessed as a hard key (KeyError when absent).What was the solution? (How)
max_session_ended_timestampfrom the prior checkpoint value.print_function_callback(suppressed under--json)..get("manifests", []).What is the impact of this change?
sync-outputdownloads the correct set of outputs across runs,--jsonoutput is valid JSON again, and the command no longer crashes on partial/optional session data.How was this change tested?
Added red-green unit tests for each case (stale-timestamp corruption, running-only overwrite, requeued consistency window, missing
manifests,--jsoncleanliness). Also removed deadskipif(py<3.9)gates (3.9 is the minimum supported version) and hoisted in-body imports to the top of the test file.test/unit/deadline_client/cli/test_cli_queue_incremental_download.py(all pass, 0 skipped).Was this change documented?
Does this PR introduce new dependencies?
Is this a breaking change?
No.
Does this change impact security?
No.
Note
The Ctrl-C-mid-download checkpoint-advance issue (C9) is intentionally out of scope here — it needs a signal-handling design decision and is tracked separately.
Testing
Beyond the automated unit tests in this PR, the changed behavior was manually exercised locally (no live AWS — mocked
deadlineclient + fixture data, temp checkpoint dirs, cleaned up afterward):--jsonoutput cleanliness across two checkpoint runs — Drove the realdeadline queue sync-outputCLI (CliRunner + mocked deadline APIs) twice against the same checkpoint dir: a bootstrap run and a resume-from-checkpoint run, both with--json. stdout was completely empty (0 chars) on both runs — every line was validated as parseable JSON, and noDEBUG/progress text leaked. The checkpoint file was still written correctly. A contrast run without--jsonon the same checkpoint emitted the expected 60 lines of verbose output including theDEBUG:lines, confirming the suppression is mode-scoped rather than output being lost.session_ended_timestamp, then ran_get_job_sessionswith both that job and a never-seen job categorized asadded. Captured the per-jobsession_ended_thresholdpassed to the session-retrieval call: the brand-new job got the raw bootstrap timestamp (no window), while the requeued job got its saved timestamp minus the eventual-consistency window (observed2025-01-04T23:58:00Zfor a saved2025-01-05T00:00:00Zwith a 120s window).session_ended_timestampintegrity across runs — Ran_update_checkpoint_jobs_listtwice on one checkpoint: run 1 with two jobs having different sessionendedAtvalues verified each job records its own max timestamp (no cross-job stale-variable bleed); run 2 with only running sessions (noendedAt) verified the saved timestamps are preserved rather than overwritten withNone.hatch run lint(ruff + mypy) clean; the fulltest_cli_queue_incremental_download.pysuite passes (19 passed, 0 skipped).The one gap originally flagged for manual testing —
--jsoncleanliness during an actual multi-file download with progress callbacks — is now closed by an automated test instead:test_incremental_output_download_json_mode_with_real_s3_downloaduploads real asset manifests and CAS data objects to a moto S3 bucket and drivesdeadline queue sync-output --jsonthrough an actual 3-file S3 download, with the realProgressTrackerfiring progress callbacks. It asserts stdout is byte-clean (and that the downloaded files match the uploaded bytes), then re-runs without--jsonand asserts the DEBUG lines and the 100% progress message do appear — proving the callbacks fired and were suppressed, not that the download was skipped. The test was verified red-green: reintroducing one rawprint()in_get_download_candidate_jobsmakes it fail.No hands-on testing against a real queue is required for the changes in this PR.