12.2 The Graceful Sequence
12.2.1 Step 1: Enter the shutdown state #
peinit sets an internal flag. While it is set, no new service starts.
Control commands
other than status, list, operation-status, job-status,
job-list and job-stop are rejected as invalid for the current
state.
Every live submitted job (§8.5) is stopped at this point too: SIGTERM
to each — unless it has already sent STOPPING=1 — with cause
shutdown, its own stop_timeout to the kill, and no ordering between
jobs or against the service waves, because a job has no dependencies
to order by.
A job still queued for launch is cancelled with the same
cause.
submit is refused for the rest of the shutdown; status,
wait, stop and signal keep answering, as do the control socket's
job-status, job-list and job-stop.
Timer triggers are disarmed. The timerfds stay armed and keep firing — peinit gates the handler rather than the fd, matching how every other event source is handled — but a firing during the shutdown window is classified as a no-op and nothing is started or queued.
The gate matters more than the rule reads. The states a timer firing would start from are Inactive, Completed and Failed, precisely the states step 3 classifies as not participating, so a firing would create a service after the plan was fixed: in no wave, not waited for, and not reached by the global-timeout sweep, still holding files open when peinit reaches the unmount step.
12.2.2 Step 2: Suspend Critical failure semantics #
A Critical service failing during shutdown is recorded but does not trigger a reboot. The system is already going down, and rebooting from here would loop.
12.2.3 Step 3: Classify #
Completed services — Oneshots with RemainAfterExit — have no process
and are transitioned to Inactive, releasing the dependency
relationships they were holding so their dependents can be stopped
cleanly.
The rest are classified for stop eligibility:
- Active and Reloading are graceful-stop eligible and enter the waves.
- Stopping services are already on a stop path. They join the waves for ordering and timeout purposes, but do not receive another SIGTERM.
- Starting services are not eligible. peinit cancels the startup,
SIGKILLs the service cgroup if one exists, and transitions them to
Failed with cause
ShutdownWavewhile post-kill verification is pending. A cgroup still populated after the post-kill timeout takes the service to Abandoned with causeProcessUnkillableand the cgroup is leaked. A Starting service whose job never forked skips the check and goes straight to Failed. - Inactive, Failed, Skipped, Backoff and Abandoned do not participate. Abandoned cgroups stay leaked and shutdown continues.
12.2.4 Step 4: Stop in reverse dependency order #
peinit builds the reverse dependency graph over hard dependencies and stops in waves:
- Each eligible service receives SIGTERM. Already-stopping ones do not.
- Each has
StopTimeoutto exit. - On expiry, SIGKILL to the service's entire cgroup.
- No service is stopped until everything depending on it has stopped.
The compiled-in Phase 1 service, registryd, stops in a wave of its own after every other participant, though no definition declares a dependency on it. Every service defined in the registry rests on it, as it started before any of them; stopped by the graph alone it would land in the first wave and go while the services still running needed it. The rule follows the definition's compiled-in provenance, not its name.
A participant the service table no longer has counts as stopped. A service whose definition is withdrawn while it runs is kept until it stops and then discarded (first-boot setup withdraws its own service this way), and the plan fixed at step 3 still names it. Waiting for it would hold its wave open for good.
A service that sent STOPPING=1 does not receive a SIGTERM at all: it
has already said it is shutting down, and peinit goes straight to the
stop deadline.
The notification is acknowledged as a peinit.notify.stopping.reported
event (§10.5), which is what makes the suppressed SIGTERM legible
afterwards rather than looking like one peinit failed to send.
12.2.4.1 Observing the waves #
Every stop-wave participant receives its own Shutdown-sourced Stop
record when the shutdown plan is fixed. Its requested event records the
intent; its started event records when the participant's wave becomes
eligible, including when an existing stop is adopted without another
SIGTERM. The records and their identifiers are visible through the
ordinary operation queries and peinit.operation.* events.
A service that exits cleanly before its wave completes its Pending observation without a started event. A crash before the wave fails it. For a service already stopping, reaching Inactive completes the observation; a stop timeout, global timeout, or forced reboot fails it. Post-kill abandonment does not produce a second terminal result for an observation already failed at the kill. Starting services retain their existing startup-failure records, and Completed services cleared without a process do not receive stop observations.
An administrator Stop or Restart already in flight keeps its identity and its original timing evidence. The shutdown observation is separate; it neither supersedes that record nor queues a second control command.
12.2.4.2 Timing an already-stopping service #
peinit does not reset an already-Stopping service's clock, either when shutdown begins or when its wave becomes eligible. It uses the timing evidence retained from the stop path that put the service in Stopping:
- If a Stop operation, or a Restart executing its stop leg, is in flight, that operation's retained timing governs.
- Otherwise peinit uses service-level evidence, which carries the cause
that initiated the transition —
ExplicitStop,ShutdownWave,ConflictEvictionorBindsToPropagation.
peinit requires the evidence to be present, to belong to a service actually in Stopping, to carry a cause matching the service's current cause, to name one of those four causes, and not to describe a deadline earlier than its own start.
Evidence that is missing, stale or ambiguous fails closed rather
than being guessed at.
A stop that began before the shutdown has already
spent part of its own clock, and inventing a fresh budget here would
hand the service a second full StopTimeout.
Failing closed costs that one participant, not the shutdown. A service whose evidence cannot substantiate a deadline is given no graceful period at all: its deadline is already due, so the timeout scan escalates it to SIGKILL on its wave, and the console says which way the evidence was unusable:
peinit: shutdown cannot substantiate a stop timeout for <service>
(<reason>); killing it without a graceful period
Every other participant keeps its full StopTimeout, and the shutdown
runs to completion.
An operation whose lifetime expires while it is still Pending — a later-wave stop waiting for its dependencies — fails that operation and its waiters. It does not authorise signalling the service before its wave is eligible. Shutdown owns the signalling; the operation object is an observation of it.
12.2.5 Step 5: Global timeout #
| Key | Default | Meaning |
|---|---|---|
Machine\System\Boot\ShutdownTimeout | 90 | Seconds for the whole sequence. |
Machine\System\Boot\PostKillTimeout | 5 | Seconds for a cgroup to drain after SIGKILL. |
On expiry, every remaining participant's cgroup is killed, anything that does not drain within the post-kill timeout is marked Abandoned with its cgroup leaked, and shutdown continues regardless. Live submitted jobs are killed in the same sweep, whatever phase their stop had reached, and abandoned on the same terms.
The global timeout fires once. What its sweep killed is then governed by the post-kill timeout alone: sweeping again would kill the survivors again and push their post-kill checks back each time, so nothing would ever be abandoned and the sequence would never reach step 6.
The console says what the sequence was waiting for when the timeout fired, and, after any later timeout turn that leaves it still waiting, what remains: participants with their state and wave, pending stop and post-kill checks, and live jobs.
peinit: shutdown global timeout expired waiting for lpsd (Active, wave 1), job <id>
peinit: shutdown still waiting for post-kill check of lpsd
Submitted jobs are named on the console as they are stopped, killed and
abandoned: peinit: shutdown stopping job <id>, killing job <id> and
abandoned job <id>.
The sequence does not move to step 6 while a submitted job is live.
A
job's reap advances shutdown progress exactly as a service's does, so a
session that exits promptly on SIGTERM costs the shutdown nothing, and
one that does not costs it stop_timeout and the post-kill grace.
12.2.6 Step 6: Save the random seed #
After every service has stopped and before any unmount, peinit writes a
fresh seed to /var/state/peinit/random-seed for the next boot: 512
bytes from the kernel CSPRNG on this machine, never copied from an
image, protected so that only SYSTEM-equivalent authority can read or
replace it.
The write is crash-conscious: a temporary file on the same filesystem, written, flushed, atomically renamed over the old seed, and the containing directory flushed. A failure is recorded and shutdown continues.
The forced and Critical-reboot paths skip it, along with the unmount step, and go directly to sync and the final action.
12.2.7 Step 7: Unmount #
- Snapshot the mount table first, from
/proc/self/mountinfo. - Attempt to unmount every remaining non-root mount in the namespace — not only what peinit mounted, so the Phase 1 set is covered by construction.
- Process in descending path depth, so children go before parents.
- A mount point already gone is a successful no-op.
- On failure, attempt a read-only remount. If that fails too, record it and continue.
- The root is never unmounted, but is remounted read-only at the end. A failure there is recorded and does not stop step 8.
12.2.8 Step 8: Sync and the final action #
This step is irreversible. Cleanup failures retained from steps 6 and 7 affect diagnostics only and never block it.
sync(). Called on all three paths — graceful, forced and Critical reboot.reboot(2)withRB_POWER_OFF,RB_AUTOBOOTorRB_HALT_SYSTEM.- If
reboot(2)returns, the final action failed. peinit enters a minimal failed-shutdown state, keeps PID 1 alive, records the failure, and retries the same action no more than once a second. It does not restart services and does not enter recovery mode — finalisation has begun and there is nothing to go back to.
12.2.9 Shutdown during boot #
A shutdown requested while Phase 2 is still running takes effect immediately, through the same classification: Starting services are SIGKILLed, services that reached Active are stopped gracefully, and the boot is abandoned.