Disable timeout for Docker image pull operations (#6391)

* Disable timeout for Docker image pull operations

The aiodocker migration introduced a regression where image pulls could
timeout during slow downloads. The session-level timeout (900s total)
was being applied to pull operations, but docker-py explicitly sets
timeout=None for pulls, allowing them to run indefinitely.

When aiodocker receives timeout=None, it converts it to
ClientTimeout(total=None), which aiohttp treats as "no timeout"
(returns TimerNoop instead of enforcing a timeout).

This fixes TimeoutError exceptions that could occur during installation
on systems with slow network connections or when pulling large images.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>

* Fix pytests

---------

Co-authored-by: Claude <noreply@anthropic.com>
This commit is contained in:
Stefan Agner 2025-12-03 21:52:46 +01:00 committed by GitHub
parent 3b3db2a9bc
commit 382f0e8aef
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
2 changed files with 28 additions and 6 deletions

View File

@ -474,8 +474,10 @@ class DockerAPI(CoreSysAttributes):
raises only if the get fails afterwards. Additionally it fires progress reports for the pull
on the bus so listeners can use that to update status for users.
"""
# Use timeout=None to disable timeout for pull operations, matching docker-py behavior.
# aiodocker converts None to ClientTimeout(total=None) which disables the timeout.
async for e in self.images.pull(
repository, tag=tag, platform=platform, auth=auth, stream=True
repository, tag=tag, platform=platform, auth=auth, stream=True, timeout=None
):
entry = PullLogEntry.from_pull_log_dict(job_id, e)
if entry.error:

View File

@ -54,7 +54,7 @@ async def test_docker_image_platform(
coresys.docker.images.inspect.return_value = {"Id": "test:1.2.3"}
await test_docker_interface.install(AwesomeVersion("1.2.3"), "test", arch=cpu_arch)
coresys.docker.images.pull.assert_called_once_with(
"test", tag="1.2.3", platform=platform, auth=None, stream=True
"test", tag="1.2.3", platform=platform, auth=None, stream=True, timeout=None
)
coresys.docker.images.inspect.assert_called_once_with("test:1.2.3")
@ -71,7 +71,12 @@ async def test_docker_image_default_platform(
):
await test_docker_interface.install(AwesomeVersion("1.2.3"), "test")
coresys.docker.images.pull.assert_called_once_with(
"test", tag="1.2.3", platform="linux/386", auth=None, stream=True
"test",
tag="1.2.3",
platform="linux/386",
auth=None,
stream=True,
timeout=None,
)
coresys.docker.images.inspect.assert_called_once_with("test:1.2.3")
@ -111,7 +116,12 @@ async def test_private_registry_credentials_passed_to_pull(
expected_auth["registry"] = registry_key
coresys.docker.images.pull.assert_called_once_with(
image, tag="1.2.3", platform="linux/amd64", auth=expected_auth, stream=True
image,
tag="1.2.3",
platform="linux/amd64",
auth=expected_auth,
stream=True,
timeout=None,
)
@ -360,7 +370,12 @@ async def test_install_fires_progress_events(
):
await test_docker_interface.install(AwesomeVersion("1.2.3"), "test")
coresys.docker.images.pull.assert_called_once_with(
"test", tag="1.2.3", platform="linux/386", auth=None, stream=True
"test",
tag="1.2.3",
platform="linux/386",
auth=None,
stream=True,
timeout=None,
)
coresys.docker.images.inspect.assert_called_once_with("test:1.2.3")
@ -817,7 +832,12 @@ async def test_install_progress_containerd_snapshot(
with patch.object(Supervisor, "arch", PropertyMock(return_value="i386")):
await test_docker_interface.mock_install()
coresys.docker.images.pull.assert_called_once_with(
"test", tag="1.2.3", platform="linux/386", auth=None, stream=True
"test",
tag="1.2.3",
platform="linux/386",
auth=None,
stream=True,
timeout=None,
)
coresys.docker.images.inspect.assert_called_once_with("test:1.2.3")