downloads: startup retries in a write session of its own

start_single_tasks still called retry_downloads() without a Session after it began requiring one, so any
start after a completed refresh raised TypeError there: incomplete downloads were not reset and the
download_on_startup enable after it never ran.  Startup is not a request, so
DownloadManager.retry_downloads_on_startup() opens and commits its own short write transaction.

Tested through the method, not start_single_tasks: importing main registers its startup listeners on the
shared app, and every later test in that worker then runs them.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Roland 2026-10-04 10:58:12 -06:00
parent 24c5796fc6
commit 0297521dd1
No known key found for this signature in database
GPG key ID: 1BA620CE6C9A4680
3 changed files with 23 additions and 1 deletions

View file

@ -309,7 +309,7 @@ async def start_single_tasks(app: Sanic):
if flags.refresh_complete.is_set():
# Set all downloads to new.
download_manager.retry_downloads()
download_manager.retry_downloads_on_startup()
# Hotspot/throttle on startup are now handled by the Controller service.

View file

@ -1317,6 +1317,11 @@ class DownloadManager:
.filter(Download.status.in_((DownloadStatus.pending, DownloadStatus.deferred, DownloadStatus.failed))) \
.update(values, synchronize_session=False)
def retry_downloads_on_startup(self):
"""Startup is not a request: retry incomplete Downloads in a short write transaction of its own."""
with get_db_session(commit=True) as session:
self.retry_downloads(session)
def get_new_downloads(self, session: Session) -> Generator[Download, None, None]:
"""
Get all "new" downloads. This method fetches the first download each iteration, so it will fetch downloads

View file

@ -1282,6 +1282,23 @@ async def test_retry_downloads_includes_failed(test_session, test_download_manag
assert d4.status == 'complete' # unchanged
@pytest.mark.asyncio
async def test_retry_downloads_on_startup(test_session, test_download_manager, test_downloader):
"""Startup is not a request; it retries incomplete Downloads in a write transaction it commits itself."""
failed = test_download_manager.create_download(test_session, 'https://example.com/failed', test_downloader.name)
failed.fail()
complete = test_download_manager.create_download(test_session, 'https://example.com/done', test_downloader.name)
complete.complete()
test_session.commit()
with production_like_sessions(test_session):
test_download_manager.retry_downloads_on_startup()
test_session.expire_all()
assert failed.status == 'new'
assert complete.status == 'complete'
@pytest.mark.asyncio
async def test_batch_retry_downloads(test_session, test_download_manager, test_downloader):
"""Test retrying specific downloads by their IDs."""