5.1 The Cgroup Tree

peinit uses cgroups v2 for exactly two things: knowing which processes belong to a service, and killing all of them at once. It does no resource accounting and sets no limits.

Every service gets a tree:

/sys/fs/cgroup/peinit/<cgroup-id>/          service root
/sys/fs/cgroup/peinit/<cgroup-id>/main/     the main process
/sys/fs/cgroup/peinit/<cgroup-id>/hooks/    hooks and reload commands
/sys/fs/cgroup/peinit/<cgroup-id>/health/   health check invocations
/sys/fs/cgroup/peinit/<cgroup-id>/checks/   pre-start filesystem check helpers

The sub-cgroups satisfy cgroups v2's "no internal processes" rule, which applies whenever controllers are enabled, and give hooks and probes containment of their own so that killing one does not touch the service.

They are not all created at once. The root and hooks/ are created when the first thing needs them — the first pre-exec hook, if there is one — and main/ and health/ when the main process launches.

5.1.1 The cgroup id #

<cgroup-id> is the service name with every byte outside [A-Za-z0-9._-] percent-encoded as % plus two uppercase hex digits. Service names are already restricted to that set (§3.1), so in practice the id equals the name.

The encoding is a defensive guarantee that distinct names always map to distinct, cgroup-safe ids. % is not itself in the safe set, so it escapes to %25 and the encoding is prefix-free. That is what makes it injective, unlike a plain substitution of / for -, under which a/b and a-b would collide.

The id is internal. The name a user sees is unchanged.

5.1.2 Generations #

A cgroup whose processes survived SIGKILL cannot be removed — rmdir on it fails with EBUSY. When peinit detects that a service's tree still has live processes after the post-kill deadline, it records the leak and increments the service's cgroup generation. The next start uses a fresh tree:

/sys/fs/cgroup/peinit/<cgroup-id>.gen<N>/

Old leaked trees persist until the next reboot.

The generation counter advances once per recorded leak rather than once per restart, and leaks are deduplicated by path and kind. A single failed start can record two — one for hooks/ and one for the service tree — so the number can advance by more than one at a time.

Because . is a legal character in a service name and the generational suffix uses one, the tree path is not injective the way the id is: a service literally named app.gen1 and generation 1 of a service named app resolve to the same directory.

Edit this page