container: Stop gracefully on podman stop
Containers used `sleep infinity` as PID 1, which never reacts to SIGTERM: the kernel does not apply default signal dispositions to PID 1, and sleep has no SIGTERM handler, so every `podman stop` silently ignored the signal, waited out the full 10-second grace period, and fell back to SIGKILL. PID 1 now traps TERM and INT itself and exits on request, so one-off manual stops of a still-running agent container end immediately instead of after the kill timeout. The PID 1 command is baked in at create time, so containers predating this change are detected and recreated automatically on the next run.
This commit is contained in:
parent
43dc757b92
commit
fcbfa32545
2 changed files with 53 additions and 1 deletions
22
CHANGELOG.md
22
CHANGELOG.md
|
|
@ -1,5 +1,27 @@
|
||||||
# Release notes
|
# Release notes
|
||||||
|
|
||||||
|
## 1.0.1 — 2026-10-29
|
||||||
|
|
||||||
|
`podman stop` now stops a running agent container immediately instead of
|
||||||
|
after a 10-second SIGTERM timeout followed by SIGKILL.
|
||||||
|
|
||||||
|
### Why
|
||||||
|
|
||||||
|
Containers used `sleep infinity` as PID 1. The kernel does not apply
|
||||||
|
default signal dispositions to PID 1, and `sleep` has no SIGTERM handler,
|
||||||
|
so the stop signal was silently ignored and every stop fell through to
|
||||||
|
SIGKILL. PID 1 now traps TERM (and INT) itself and exits on request:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
trap "exit 0" TERM INT; sleep infinity & wait
|
||||||
|
```
|
||||||
|
|
||||||
|
### Upgrading
|
||||||
|
|
||||||
|
The PID 1 command is baked in when a container is created, so existing
|
||||||
|
containers are detected on the next run and recreated automatically.
|
||||||
|
Nothing in the container home is touched.
|
||||||
|
|
||||||
## 1.0.0 — 2026-10-29
|
## 1.0.0 — 2026-10-29
|
||||||
|
|
||||||
Containers now run with rootless Podman instead of Docker.
|
Containers now run with rootless Podman instead of Docker.
|
||||||
|
|
|
||||||
|
|
@ -179,6 +179,12 @@ class AgentContainer:
|
||||||
)
|
)
|
||||||
self.remove_container()
|
self.remove_container()
|
||||||
|
|
||||||
|
if self.container_exists() and not self._container_cmd_matches():
|
||||||
|
logger.info(
|
||||||
|
"Recreating container: it predates the SIGTERM-aware PID 1"
|
||||||
|
)
|
||||||
|
self.remove_container()
|
||||||
|
|
||||||
if not self.container_exists():
|
if not self.container_exists():
|
||||||
self.create_container()
|
self.create_container()
|
||||||
|
|
||||||
|
|
@ -513,7 +519,11 @@ class AgentContainer:
|
||||||
self.IMAGE,
|
self.IMAGE,
|
||||||
"sh",
|
"sh",
|
||||||
"-c",
|
"-c",
|
||||||
"sleep infinity",
|
# PID 1 ignores signals with default dispositions, so a bare
|
||||||
|
# `sleep infinity` never reacts to SIGTERM and every
|
||||||
|
# `podman stop` times out into SIGKILL. Trapping TERM (and
|
||||||
|
# INT) makes stop requests end the container immediately.
|
||||||
|
'trap "exit 0" TERM INT; sleep infinity & wait',
|
||||||
]
|
]
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
@ -555,6 +565,26 @@ class AgentContainer:
|
||||||
).stdout
|
).stdout
|
||||||
return "AGENT_CONTAINER=1" in env
|
return "AGENT_CONTAINER=1" in env
|
||||||
|
|
||||||
|
def _container_cmd_matches(self) -> bool:
|
||||||
|
"""Whether the container runs the current PID 1 command.
|
||||||
|
|
||||||
|
The PID 1 entrypoint is baked in at create time and can only change
|
||||||
|
by recreating the container. Comparing against Config.Cmd catches
|
||||||
|
containers created before the SIGTERM-aware fix."""
|
||||||
|
cmd = subprocess.run(
|
||||||
|
[
|
||||||
|
self.RUNTIME,
|
||||||
|
"container",
|
||||||
|
"inspect",
|
||||||
|
"-f",
|
||||||
|
"{{json .Config.Cmd}}",
|
||||||
|
self.container_name,
|
||||||
|
],
|
||||||
|
capture_output=True,
|
||||||
|
text=True,
|
||||||
|
).stdout.strip()
|
||||||
|
return "TERM INT" in cmd
|
||||||
|
|
||||||
def start_container(self) -> bool:
|
def start_container(self) -> bool:
|
||||||
subprocess.run(
|
subprocess.run(
|
||||||
[self.RUNTIME, "start", self.container_name],
|
[self.RUNTIME, "start", self.container_name],
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue