NS-039
documentedname-belongs-to-the-parent
The process bearing the service's name is a wrapper, and the worker is its child
- reads as
- ps shows one process named run-analytics; it is killed and it leaves the list. Conclusion drawn: the service is stopped.
- actually
- A shell wrapper ending in a plain command rather than `exec` forks a child and waits on it, so there are two processes: the wrapper carrying the recognisable name, and the worker carrying the interpreter's or binary's own name. Killing the wrapper leaves the worker running, reparented to PID 1, still holding its port, its lock and its open files.
- blind because
- The process list is flat and reports names. Nothing in it indicates that the row matching the search is the parent of the row doing the work, and after the kill the name is genuinely gone.
- the check
- Verify the resource rather than the name: `ss -ltnp 'sport = :PORT'` after the stop — empty means stopped, a listener means the worker outlived the name. Observed here: killing `/bin/bash ./run-analytics` left its `sleep 120` child alive with PPID reassigned to 1; `pgrep -P PID` lists such children before the kill rather than after.
- cost of missing
- A restart yields two live workers competing for the same resource, or a stop that reports success while the old worker keeps writing. Neither produces an error.
- mitigation
- `exec` as the last line of a wrapper replaces the shell instead of forking, so the name and the worker are one process; systemd's default control-group-based KillMode stops the whole tree rather than the named process.
- generalises to
- Every process tree observed as a flat list: container entrypoints, npm and make targets, virtualenv shims, ssh command wrappers.
- source
- gnu.org