3.5 Conditions and Asserts

Both are start-time checks in the same type:argument form, over the same four check types. What differs is the consequence of a failure.

CheckFormPasses when
pathpath:<path>The path exists, of any type.
filefile:<path>A regular file exists there.
directorydirectory:<path>A directory exists there.
registryregistry:<key>The registry key exists.

Conditions describe when a service applies. A failed condition skips the service: it transitions to Skipped, which satisfies its dependents, because a service that does not apply has succeeded by not needing to run.

Asserts describe what a service needs. A failed assert fails the service, with cause AssertionError — the service was expected to run and a precondition it depends on is missing.

All entries of a kind are AND'd. Conditions are evaluated first; asserts only if every condition passed. Both are evaluated before dependency resolution and before any pre-exec hook, and an entry with an empty argument or an unrecognised type is a decode error.

3.5.1 Evaluation without blocking #

peinit is single-threaded PID 1 and its event loop cannot block while a check runs. Two constraints follow, and they are why the check types behave differently from one another.

3.5.1.1 Registry checks are cache-only #

A registry: check is evaluated against the in-memory model, never by a live registry read. Accepted paths are exactly Machine\System\Services, Machine\System\Init, and Machine\System\Services\<name> with one nonempty service-name component. Init descendants, deeper Services paths, and trailing separators are decode errors, caught at load rather than silently failing at start. The same rule applies to Conditions and Asserts.

The two roots resolve as present. A registry:Machine\System\Services\<name> check is true when that service exists in the model; it does not read an arbitrary registry key. A missing service is a valid check whose result is false.

3.5.1.2 Filesystem checks run in a helper #

stat() can block uninterruptibly on hung I/O — a dead NFS mount, a failing disk controller — so peinit does not call it from the event loop. It forks a short-lived helper into a dedicated checks/ cgroup under the service's tree, using the same clone3 path as any other child. The helper stats the paths and reports over a non-blocking pipe; peinit waits on the pipe and the helper's pidfd through epoll, and never blocks.

PreStartCheckTimeout, default 5 seconds, bounds the helper. A check that does not report in time is treated as not satisfied — the fail-safe direction, so a condition skips the service and an assert fails it. peinit then SIGKILLs the helper's cgroup and unregisters the result descriptor, and the event loop is never held up by a hung check.

3.5.2 When results are computed #

Checks are evaluated once, before the service's dependencies start, and the result is cached for the rest of that activation. A service that waits a long time for a dependency starts on the answer that was true when the wait began, not on a fresh one.

Edit this page