fix: the download boot hook imports a model-only module, keeping the membership roster off the fetch path (#4433)
CI and images / lint (push) Failing after 4s
CI and images / extension-version (push) Successful in 4s
CI and images / extension-test (push) Successful in 18s
CI and images / frontend-build (push) Successful in 21s
CI and images / backend-lint-and-test (push) Successful in 32s
CI and images / integration (push) Successful in 2m22s
CI and images / sign-extension (push) Skipped
CI and images / build-web (push) Skipped
CI and images / smoke-web (push) Skipped
CI and images / promote (push) Skipped
CI and images / build-agent (push) Skipped
CI and images / lint (push) Failing after 4s
CI and images / extension-version (push) Successful in 4s
CI and images / extension-test (push) Successful in 18s
CI and images / frontend-build (push) Successful in 21s
CI and images / backend-lint-and-test (push) Successful in 32s
CI and images / integration (push) Successful in 2m22s
CI and images / sign-extension (push) Skipped
CI and images / build-web (push) Skipped
CI and images / smoke-web (push) Skipped
CI and images / promote (push) Skipped
CI and images / build-agent (push) Skipped
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01LVjrnpQjRgHdvq95rASoiR
This commit is contained in:
@@ -7,8 +7,7 @@ from datetime import UTC, datetime, timedelta
|
||||
from pathlib import Path
|
||||
|
||||
from PIL import Image
|
||||
from sqlalchemy import Integer, and_, cast, delete, func, literal, or_, select, update
|
||||
from sqlalchemy.dialects.postgresql import JSONB
|
||||
from sqlalchemy import Integer, and_, cast, delete, func, or_, select, update
|
||||
|
||||
from ..celery_app import celery
|
||||
from ..models import (
|
||||
@@ -732,48 +731,6 @@ def recover_stalled_download_events() -> int:
|
||||
return events_recovered
|
||||
|
||||
|
||||
DOWNLOAD_INTERRUPTED_MESSAGE = (
|
||||
"interrupted by a worker restart — the next check picks it up where it left off"
|
||||
)
|
||||
|
||||
|
||||
def interrupt_orphaned_download_events(session, *, booted_at: datetime) -> int:
|
||||
"""Close the download events a restart orphaned, without blaming the source.
|
||||
|
||||
Called when the download lane comes up (#4433). Anything still
|
||||
pending/running from before this boot belongs to the previous process:
|
||||
a walk that outlived the 90s stop grace was SIGKILLed, and a queued or
|
||||
serialize-deferred task is held unacked until Redis redelivers it about an
|
||||
hour later. Left alone, the 30-min stall sweep would error each one and
|
||||
bump `consecutive_failures`, backing the source off as if the platform had
|
||||
failed.
|
||||
|
||||
Instead they end as `skipped` (terminal, not a failure) and the source is
|
||||
not touched: `last_checked_at` keeps its old value, so the next tick finds
|
||||
it due and the walk resumes from its checkpoint. A redelivered message
|
||||
that arrives later finds no pending event and opens a fresh one.
|
||||
|
||||
An event promoted to running after the boot has `started_at` reset to its
|
||||
real start (download_service), so it is never caught here. Does NOT commit.
|
||||
"""
|
||||
now = datetime.now(UTC)
|
||||
result = session.execute(
|
||||
update(DownloadEvent)
|
||||
.where(DownloadEvent.status.in_(["pending", "running"]))
|
||||
.where(DownloadEvent.started_at < booted_at)
|
||||
.values(
|
||||
status="skipped",
|
||||
finished_at=now,
|
||||
error=DOWNLOAD_INTERRUPTED_MESSAGE,
|
||||
metadata_=DownloadEvent.metadata_.op("||")(
|
||||
literal({"error_type": "interrupted"}, JSONB)
|
||||
),
|
||||
)
|
||||
.returning(DownloadEvent.id)
|
||||
)
|
||||
return len(result.all())
|
||||
|
||||
|
||||
@celery.task(name="backend.app.tasks.maintenance.recover_stalled_backup_runs")
|
||||
def recover_stalled_backup_runs() -> int:
|
||||
"""Flip BackupRun rows stuck in running/restoring past the hard limit
|
||||
|
||||
Reference in New Issue
Block a user