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 #

PublisherLevels
netdlinkaddressedrouted
timedunsynchronised, settling, synchronised, spike

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.

Edit this page