On this page

One process per service

Every service is exactly one process: the unit's own process, with no helper, sidecar or child process beside it. The rule is the operator's, and Stado checks it as a property of the fleet rather than as a one-off cleanup. Two readings say a service breaks it, and either one is a violation.

What the host sees: child processes

On every beacon pass the host Stado process reads the kernel's process table inside its own process (libproc on macOS, /proc on Linux; no ps child), and for each declared unit with a pid it publishes the processes below that pid as the unit's children in the beacon: pid, parent pid, the executable's path and whether it has exited unreaped. A table the kernel will not list is published as children_unread with the reason, never as no children.

stado service status <name> and stado service list print one line for a unit that has any:

violation: one process per service is broken on example-host: com.wisent.forks runs 1 child process(es) under pid 4120: /bin/sleep (pid 4121); fold each spawned program into forks's own binary, then `stado service status com.wisent.forks` reads one process

--json carries the same reading as one_process_violation (host, unit, service, pid, children, debt, detail), and a beacon whose children cannot be read as one_process_error. A beacon older than its own promised next publication describes no children.

What the catalog records: helper processes

The product catalog (catalog/products.yml) refuses a service declaration that is not one process:

  • a declared helper (helpers, helper_processes, sidecars, children, launcher) is refused naming the field;
  • a program that is not the product's own binary — its id, or a binary its installation recipes name — is a launcher or interpreter that starts the service as a child, and is refused naming the file and why.

A service that cannot be fixed in the same change records what it still spawns under service.one_process_debt: a reason, and spawns, each a command and the site (<repository>/<path>:<line>) of the call that starts it. A spawn without a source line is refused. The record is never an exemption: stado product catalog, stado service catalog, stado service status, stado service list, stado doctor and Stado Desktop print every record as a violation, naming each spawn site.

Doctor

stado doctor has a one-process row. It fails naming every unit that has child processes in its host's latest beacon and every catalog service with a debt record; it reads as unmeasured when the beacons' children could not be read, and passes only when no unit is more than one process. It is not part of --deployment-preflight: a service that is more than one process is fixed in its own product and does not block installing an unrelated Stado release.

Stado Desktop

The Managed services table prints Not one process: … under a violating row, its inspector carries a not one process badge and a panel with the same sentence, and a product whose catalog service records debt shows the reason and every spawn site in its inspector.

Fixing a violation

Fold the spawned program into the service's binary and release it. Film and Obraz were started through a /bin/sh launcher that sourced the service environment file and execed the binary; each binary now reads FILM_SERVICE_ENV_FILE/OBRAZ_SERVICE_ENV_FILE itself and serves on the assigned port when started with no command, so the release stages the binary alone and names it as its launcher. Stado's host process held its power assertions through caffeinate children (one per job, never reaped) and read memory through sysctl and vm_stat; the power assertions are now IOKit calls in the process, and hw.memsize and kern.memorystatus_level are kernel calls (sysctlbyname), while the full memory reading still runs sysctl -n vm.swapusage and vm_stat for swap and the compressor.

Real tests

  • kompilator test --manifest-path stado-rs/Cargo.toml --test one-process-catalog: a fixture service that runs its binary is accepted; one whose unit runs a launcher, one that declares a sidecar and a debt record without source lines are refused by the real stado product catalog; a recorded debt prints as a violation; the shipped catalog prints Brama's, Weles's and Stado's records.
  • kompilator test --manifest-path stado-rs/Cargo.toml --test host-health -- one_process:: (with STADO_TEST_REQUEST_LIMITS declared): one isolated deployment and two init-system jobs of its own (launchd jobs on macOS, transient systemd user units on Linux), one /bin/sh that starts sleep and one sleep. The beacon lists the child under the first only, stado service status prints the violation for it and none for the other, and stado doctor --json fails its one-process row naming the first.

Source: this website