From 4cf2b1b0d0c3dbcd430d228d8fe40ab43f2e1851 Mon Sep 17 00:00:00 2001 From: Matt Van Horn <455140+mvanhorn@users.noreply.github.com> Date: Sun, 19 Jul 2026 01:13:16 -0700 Subject: [PATCH] fix: release per-download status_queue proxy on close to stop FD leak --- app/tests/test_download_queue.py | 34 ++++++++++++++++++++++++++++++++ app/ytdl.py | 7 +++++-- 2 files changed, 39 insertions(+), 2 deletions(-) diff --git a/app/tests/test_download_queue.py b/app/tests/test_download_queue.py index 4b375f6..763b047 100644 --- a/app/tests/test_download_queue.py +++ b/app/tests/test_download_queue.py @@ -898,6 +898,40 @@ def _make_download(dq_env, *, download_type="video", status="downloading", filen ) +def test_download_close_releases_status_queue(dq_env): + download = _make_download(dq_env) + status_queue = MagicMock() + proc = MagicMock() + download.status_queue = status_queue + download.proc = proc + + download.close() + + proc.close.assert_called_once() + assert download.status_queue is None + + +def test_download_close_releases_status_queue_without_process(dq_env): + download = _make_download(dq_env) + download.status_queue = MagicMock() + + download.close() + + assert download.status_queue is None + + +def test_download_close_releases_status_queue_when_process_close_fails(dq_env): + download = _make_download(dq_env) + download.status_queue = MagicMock() + download.proc = MagicMock() + download.proc.close.side_effect = RuntimeError('close failed') + + with pytest.raises(RuntimeError, match='close failed'): + download.close() + + assert download.status_queue is None + + @pytest.mark.asyncio async def test_post_download_cleanup_clears_filename_on_error(dq_env): notifier = AsyncMock() diff --git a/app/ytdl.py b/app/ytdl.py index a44e02b..96ada56 100644 --- a/app/ytdl.py +++ b/app/ytdl.py @@ -758,8 +758,11 @@ class Download: def close(self): log.info(f"Closing download process for: {self.info.title}") - if self.started(): - self.proc.close() + try: + if self.started(): + self.proc.close() + finally: + self.status_queue = None def running(self): try: