fix(aria2): move stderr drain after _wait_until_ready to avoid swallowing startup errors

_drain_stderr and _wait_until_ready both read from the same stderr pipe.
Starting the drain task before _wait_until_ready creates a race where the
drain task consumes aria2's early-exit error message before the startup
waiter can read it, resulting in an empty error message in the logs.

Also confirmed that --fsync does not exist as an aria2 option (exit code
28 = Invalid argument).
This commit is contained in:
Will Miao
2026-06-26 14:32:43 +08:00
parent f264bab65c
commit 75fffc1e25

View File

@@ -520,16 +520,19 @@ class Aria2Downloader:
stderr=asyncio.subprocess.PIPE, stderr=asyncio.subprocess.PIPE,
) )
await self._wait_until_ready()
# Drain aria2's stderr in a background task so the pipe buffer # Drain aria2's stderr in a background task so the pipe buffer
# never fills up. If the pipe blocks, aria2 itself freezes and # never fills up. If the pipe blocks, aria2 itself freezes and
# cannot respond to RPC — this was the root cause of the # cannot respond to RPC — this was the root cause of the
# "Failed to query aria2 download status" timeout bug. # "Failed to query aria2 download status" timeout bug.
# Must start AFTER _wait_until_ready to avoid a race where the
# drain task consumes aria2's early-exit error message before
# _wait_until_ready can read it.
self._stderr_reader_task = asyncio.create_task( self._stderr_reader_task = asyncio.create_task(
self._drain_stderr() self._drain_stderr()
) )
await self._wait_until_ready()
def _resolve_executable(self) -> str: def _resolve_executable(self) -> str:
settings = get_settings_manager() settings = get_settings_manager()
configured_path = (settings.get("aria2c_path") or "").strip() configured_path = (settings.get("aria2c_path") or "").strip()