7.5 Readiness Levels
network-online.target on systemd is famously meaningless, because
"online" has no single definition. A service needing a default route and
a service needing only a loopback address want different things, and one
target cannot mean both.
peinit does not have an online target. A service says what it is waiting for, in the publisher's own vocabulary:
Requires = ["netd:routed"]
Wants = ["timed:synchronised"]
netd:routed waits for netd to be active and to have published the
level routed. netd on its own is unchanged — the ordinary "that
service must be active" dependency.
7.5.1 The syntax #
<service>:<level>, split on the first colon. It rides on the existing
Requires, Wants and BindsTo fields rather than a field of its own,
because a level dependency is a service dependency with a stricter
predicate: netd:routed subsumes netd. Each relationship keeps exactly
the semantics it already has, applied to
the stricter test.
It cannot collide with a service name. A service name may contain only
letters, digits, ., _ and -, so a colon never appears in one, and
no existing definition can accidentally become a level dependency.
A trailing colon — "netd:" — reads as a plain dependency on netd. It
is a typo, and treating it as a request for the empty level would produce
a condition nothing could ever satisfy.
7.5.2 How a level gets there #
The service publishes it on the notification channel it already has, as
LEVEL= beside READY=1 and STATUS=:
LEVEL=routed
An empty value retracts it. peinit records the level against the sending service, and holds any dependent whose declaration does not match.
There is no subscription. peinit does not connect to netd's socket or timed's, does not need to find them before they exist, and carries no knowledge of their wire formats — which matters, because PID 1 is the one process on the machine that cannot afford to fail to start.
7.5.3 Levels are exact, not ordered #
Requires = ["netd:addressed"] is not satisfied by netd:routed.
peinit has no ordering over another daemon's vocabulary and does not
invent one. netd knows that routed implies addressed; peinit does
not, and a central table of which levels imply which would recreate
exactly the coupling that naming levels after their publisher avoids.
A service that genuinely wants "addressed or better" should depend on the level it actually needs. If a publisher's levels are ordered in a way dependents keep needing to express, that is a sign the publisher should name the useful condition directly.
7.5.4 A level is never stale #
peinit clears a service's level whenever that service leaves a state that satisfies dependents. A level is a claim about a process that is currently making it, and one that outlived its process would hold a dependent open on a promise nobody was keeping — the exact failure the mechanism exists to prevent.
Because peinit is the process that notices a service exiting, this needs no timeout and no liveness check.
7.5.5 How the gate holds #
A level is settled at dispatch, not at planning. The plan decides what to start; whether a level is met is a live question — it can arrive after the target's own start completed (netd is active long before DHCP finishes) and be retracted while a dependent is still waiting — so the execution graph re-checks it against the service table every time it considers releasing the dependent, and holds the start until the answer is yes.
That check runs even when there is nothing to start. An already-active
target is not part of the start plan, but the condition on it still is:
the graph keeps a level edge to a service it is not starting, which is
what makes Requires = ["netd:routed"] hold when netd is up and merely
not routed yet.
Two events re-open the question. A LEVEL= arriving from the publisher
releases every dependent held on it — this is the only thing that can
open a Requires or BindsTo gate, which take the exact level and
nothing else. And the publisher leaving a state that satisfies dependents
releases Wants waiters: a soft dependency waits only while someone is
running who could still publish the level, and proceeds once nobody is —
which keeps Wants failure-tolerant, the property that defines it.
A held start does not time out. Requires = ["netd:no-such-level"]
holds the dependent indefinitely: the service stays inactive and the
start operation stays pending, visible in svctl status as an operation
that has not completed. That is the declared semantics, not a hang — the
condition was never met, so the start never happened.
7.5.6 What happens on a drop #
Nothing, in this version. If a level falls away after a dependent has already started, the dependent keeps running and is not told.
That is a deliberate limit, not an oversight. The notification channel is one-way by specification (PSPU §4.16), so peinit has no way to tell a running service anything. A service that needs to react to a level changing while it runs should subscribe to the publisher directly — which is what resolvd does with netd today, and what any consumer that cares that much will want anyway, since it will need the detail rather than just the fact.
peinit's contribution is the start gate: the moment when the service does not exist yet and so cannot subscribe to anything at all.
7.5.7 Published levels #
Any service may publish a level; nothing about the mechanism is specific to these two. The vocabulary is the publisher's own, and needs no registration.