From e7dfe9c7c4f74688d2d74dba2278e66ce1227d6a Mon Sep 17 00:00:00 2001 From: YueGuobin Date: Mon, 10 Aug 2026 21:48:26 +0800 Subject: [PATCH] fix: SIGKILL docker container on stop instead of 5s grace period MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Docker node stop took ~5s every time. The stop API grace period (params t=5, unchanged since 2015) was always exhausted: the business process (often an interactive shell) ignores SIGTERM, and GNS3 doesn't depend on graceful shutdown — _fix_permissions and /gns3volumes already persist container state before stop() is called. Use POST /containers/{id}/kill (SIGKILL, zero delay) instead of stop. The 409 (container already stopped) replaces the previous 304 handling for the race where the container exits between the state check and the call. t=5 traced to commit 33edbefa3 (2015-10-14) "Docker cleanup and improvements" — introduced with no recorded rationale. --- gns3server/compute/docker/docker_vm.py | 9 ++++++--- tests/compute/docker/test_docker_vm.py | 6 +++--- 2 files changed, 9 insertions(+), 6 deletions(-) diff --git a/gns3server/compute/docker/docker_vm.py b/gns3server/compute/docker/docker_vm.py index dafe3e8ee..e6f2aee1a 100644 --- a/gns3server/compute/docker/docker_vm.py +++ b/gns3server/compute/docker/docker_vm.py @@ -1049,11 +1049,14 @@ class DockerVM(BaseNode): state = await self._get_container_state() if state != "stopped" and state != "exited": - # t=5 number of seconds to wait before killing the container + # SIGKILL immediately. GNS3 has already persisted container state + # (permissions via _fix_permissions, /gns3volumes) before this + # point, and the business process (often an interactive shell) + # ignores SIGTERM — so a stop grace period buys nothing but latency. try: - await self.manager.query("POST", f"containers/{self._cid}/stop", params={"t": 5}) + await self.manager.query("POST", f"containers/{self._cid}/kill") log.info(f"Docker container '{self._name}' [{self._image}] stopped") - except DockerHttp304Error: + except DockerHttp409Error: # Container is already stopped pass # Ignore runtime error because when closing the server diff --git a/tests/compute/docker/test_docker_vm.py b/tests/compute/docker/test_docker_vm.py index bc722423e..9d8bee2b8 100644 --- a/tests/compute/docker/test_docker_vm.py +++ b/tests/compute/docker/test_docker_vm.py @@ -1209,7 +1209,7 @@ async def test_stop(vm): with asyncio_patch("gns3server.compute.docker.Docker.query") as mock_query: vm._permissions_fixed = False await vm.stop() - mock_query.assert_called_with("POST", "containers/e90e34656842/stop", params={"t": 5}) + mock_query.assert_called_with("POST", "containers/e90e34656842/kill") assert mock.stop.called assert vm._ubridge_hypervisor is None assert vm._fix_permissions.called @@ -1222,7 +1222,7 @@ async def test_stop_paused_container(vm): with asyncio_patch("gns3server.compute.docker.DockerVM.unpause") as mock_unpause: with asyncio_patch("gns3server.compute.docker.Docker.query") as mock_query: await vm.stop() - mock_query.assert_called_with("POST", "containers/e90e34656842/stop", params={"t": 5}) + mock_query.assert_called_with("POST", "containers/e90e34656842/kill") assert mock_unpause.called @@ -1894,7 +1894,7 @@ async def test_stop_exited_container_no_stop_query(vm): vm._permissions_fixed = False await vm.stop() assert not any( - call.args[:2] == ("POST", "containers/e90e34656842/stop") + call.args[:2] == ("POST", "containers/e90e34656842/kill") for call in mock_query.mock_calls ) assert vm.status == "stopped"