fix(downloads): E11.5 epic-end review round 2

Pause now re-reads the job under the same asset-then-job row lock as resume,
cancel, delete and redownload. Its unlocked write waited for a concurrent
cancel's lock and then set the released job back to paused, a holding status,
so a later delete released the same hold again and could free a file another
user's job still held (measured: the overlapping-request test ends 'paused'
instead of 'canceled' with the old read). The lock test now calls the route
handlers, so it also pins which helper each route uses.

Comments follow the code: resolve_url's contract names both verdicts and that a
failed page read is raised as itself; the inherited-failure diagnosis asks the
cookie gate as it stands when the job is claimed, so its advice fits the cookie
that job's Retry would use; the undecryptable-cookie warning no longer claims a
download goes ahead, since the worker parent reaches it only to diagnose.
This commit is contained in:
2026-09-14 01:42:57 +02:00
parent e887233210
commit 2a9ddb78ac
6 changed files with 56 additions and 27 deletions
+4 -4
View File
@@ -418,10 +418,10 @@ def _process_job(job_id: int) -> None:
asset_status = asset.status if asset else None
asset_error = asset.error if asset else None
if asset_status == "error":
# A job that meets an already-failed asset gets the SAME explanation the job that failed
# it got — not a bare "Download failed". Only the text is on the asset, so it is
# diagnosed again, for this link, and whether the user's cookie went with the download
# is asked of the same gate the download child used (`credentials.usable_cookie`).
# A job that meets an already-failed asset gets that failure's explanation — not a bare
# "Download failed". Only the text is on the asset, so it is diagnosed again, for this
# link, with whether a cookie goes with it asked of the same gate the download child
# used (`credentials.usable_cookie`) — as it stands now, so the advice fits this job's Retry.
url = service.source_url(job.source_kind, job.source_ref)
cookie = credentials.usable_cookie(
db, job.user_id, url, asset, formats.normalize(job.profile_snapshot)