From 0297521dd1870ec1ffdc2b4e71acc80a2fa7d6cf Mon Sep 17 00:00:00 2001 From: Roland Date: Sun, 4 Oct 2026 10:58:12 -0600 Subject: [PATCH] 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 --- main.py | 2 +- wrolpi/downloader.py | 5 +++++ wrolpi/test/test_downloader.py | 17 +++++++++++++++++ 3 files changed, 23 insertions(+), 1 deletion(-) diff --git a/main.py b/main.py index 92cd3506..5fdf94ea 100755 --- a/main.py +++ b/main.py @@ -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. diff --git a/wrolpi/downloader.py b/wrolpi/downloader.py index 3fc8ca27..40cff8e0 100644 --- a/wrolpi/downloader.py +++ b/wrolpi/downloader.py @@ -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 diff --git a/wrolpi/test/test_downloader.py b/wrolpi/test/test_downloader.py index 5fd7fba1..0f54b6a3 100644 --- a/wrolpi/test/test_downloader.py +++ b/wrolpi/test/test_downloader.py @@ -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."""