Drop the never-ran reading: a start timestamp of 0 does not mean a one-shot never ran (issue 1126)
mesh/delivery merged: source.main moved to novox/mesh-host@b4fd2cc210bf
mesh/check being carried to 1a021052fe07 on b4fd2cc210bf | from 7a2af2376405

Review round 1 measured it on systemd 262.1: a finished one-shot nothing references is
unloaded, and loaded again it reads inactive, ExecMainStatus=0 and a start timestamp of
0, as one that never ran does. The previous commit's rule read such a unit stopped, so
a one-shot declared running would be started, read stopped and fail on every apply, and
the hold, the take-over and the giving back would judge it wrongly.

ServiceState keeps main's reading. ServiceAtRest and declaredState stay: they alone
satisfy a stopped declaration for a one-shot at rest, which is the kernel cleanup's
case. A one-shot declared running that must be seen to have run lingers
(RemainAfterExit) or is enabled at boot. Unit tests: the arch table has 5 rows, the
apply test 1; 1262 in all.
This commit is contained in:
2026-10-28 14:29:16 +01:00
parent cf738cdb90
commit 1a021052fe
3 changed files with 20 additions and 38 deletions
+6 -8
View File
@@ -351,14 +351,12 @@ func TestAnUnknownServiceStateIsRefusedNotGuessed(t *testing.T) {
}
// A boot-time one-shot a package ships, declared stopped and enabled at boot, is satisfied while
// nothing of it runs: before its first boot run and after it (novox/hq issue 1126). The kernel
// module's linux-modules-cleanup.service failed on ace with "was asked to be stopped and is
// running" for a unit that had never run, and every apply after a reboot would have failed the
// same way for one that had.
// nothing of it runs, whatever its last run did (novox/hq issue 1126). The kernel module's
// linux-modules-cleanup.service failed on ace with "was asked to be stopped and is running": an
// inactive one-shot with ExecMainStatus=0 reads "running", and stopping it changes nothing.
func TestABootOneShotDeclaredStoppedIsSatisfiedAtRest(t *testing.T) {
for _, c := range []struct{ name, started, status string }{
{"never ran", "0", "0"},
{"ran at boot", "5123456", "0"},
for _, c := range []struct{ name, status string }{
{"exited 0: never ran, ran at boot, or unloaded", "0"},
} {
t.Run(c.name, func(t *testing.T) {
var asked []string
@@ -367,7 +365,7 @@ func TestABootOneShotDeclaredStoppedIsSatisfiedAtRest(t *testing.T) {
switch args[0] {
case "show":
return "LoadState=loaded\nActiveState=inactive\nType=oneshot\nRemainAfterExit=no\n" +
"ExecMainStatus=" + c.status + "\nExecMainStartTimestampMonotonic=" + c.started + "\n", nil
"ExecMainStatus=" + c.status + "\n", nil
case "is-enabled":
return "enabled\n", nil
}
+7 -14
View File
@@ -217,16 +217,12 @@ func (arch) ServiceState(ctx context.Context, run Runner, unit string) (string,
//
// So for that shape, what "running" means is "it ran, and it worked".
//
// **And one that never ran is not one that worked** (novox/hq issue 1126). systemd says
// ExecMainStatus=0 for a unit that has not run since the manager loaded it exactly as for one
// that succeeded; only its start timestamp, 0, separates them. Read as "running", a package's
// boot-time one-shot declared stopped could never be satisfied — stopping an inactive unit
// changes nothing, and the read back said running again — and one declared running was never
// started. A timestamp the manager did not say keeps the reading it had before.
// It cannot be told from one that never ran: systemd says ExecMainStatus=0 and a start timestamp
// of 0 for a one-shot that never ran, and also for one that ran and was unloaded because nothing
// referenced it, then loaded again. So a one-shot declared running that must be seen to have run
// lingers (RemainAfterExit) or is enabled at boot; one declared stopped is judged at rest by the
// apply (ServiceAtRest, novox/hq issue 1126).
if u.oneShotAtRest() {
if u.started == "0" {
return "stopped", nil
}
if u.exited == "0" || u.exited == "" {
return "running", nil
}
@@ -259,7 +255,7 @@ func (arch) ServiceAtRest(ctx context.Context, run Runner, unit string) (bool, e
// shownUnit is what `systemctl show` says of a unit, as far as its state is read.
type shownUnit struct {
load, active, kind, remains, exited, started string
load, active, kind, remains, exited string
}
// oneShotAtRest is a one-shot that does not linger and is not running now.
@@ -273,8 +269,7 @@ func (u shownUnit) oneShotAtRest() bool {
func showUnit(ctx context.Context, run Runner, unit string) (shownUnit, error) {
out, asked := run(ctx, "systemctl", "show",
"--property=LoadState", "--property=ActiveState", "--property=Type",
"--property=RemainAfterExit", "--property=ExecMainStatus",
"--property=ExecMainStartTimestampMonotonic", "--", unit)
"--property=RemainAfterExit", "--property=ExecMainStatus", "--", unit)
var u shownUnit
for _, line := range strings.Split(out, "\n") {
key, value, found := strings.Cut(strings.TrimSpace(line), "=")
@@ -292,8 +287,6 @@ func showUnit(ctx context.Context, run Runner, unit string) (shownUnit, error) {
u.remains = value
case "ExecMainStatus":
u.exited = value
case "ExecMainStartTimestampMonotonic":
u.started = value
}
}
if u.load == "" {
+7 -16
View File
@@ -14,28 +14,19 @@ import (
// **One that failed is still stopped**, or the host would report success for work that did not
// happen — the opposite mistake and the worse one.
//
// **One that never ran is stopped** (novox/hq issue 1126, the kernel's boot-time cleanup on ace):
// systemd says ExecMainStatus=0 for a unit that never ran exactly as for one that succeeded, and
// only its start timestamp separates them. Read as "running", a package's one-shot declared
// running was never started, and one declared stopped could never be.
//
// **At rest** is whether a one-shot that does not linger has nothing running now: a declaration
// **At rest** (novox/hq issue 1126, the kernel's boot-time cleanup on ace) is whether a one-shot that does not linger has nothing running now: a declaration
// that it be stopped is then satisfied, whatever it last did, because there is nothing to stop.
func TestAOneShotIsReadByWhatItDid(t *testing.T) {
for _, c := range []struct {
name, show, want string
atRest bool
}{
{"finished", "ActiveState=inactive\nType=oneshot\nRemainAfterExit=no\nExecMainStatus=0\n" +
"ExecMainStartTimestampMonotonic=5123456\n", "running", true},
{"finished, no timestamp read", "ActiveState=inactive\nType=oneshot\nRemainAfterExit=no\n" +
"ExecMainStatus=0\n", "running", true},
{"failed", "ActiveState=inactive\nType=oneshot\nRemainAfterExit=no\nExecMainStatus=1\n" +
"ExecMainStartTimestampMonotonic=5123456\n", "stopped", true},
{"never ran", "ActiveState=inactive\nType=oneshot\nRemainAfterExit=no\nExecMainStatus=0\n" +
"ExecMainStartTimestampMonotonic=0\n", "stopped", true},
{"running now", "ActiveState=activating\nType=oneshot\nRemainAfterExit=no\nExecMainStatus=0\n" +
"ExecMainStartTimestampMonotonic=5123456\n", "running", false},
{"finished", "ActiveState=inactive\nType=oneshot\nRemainAfterExit=no\nExecMainStatus=0\n",
"running", true},
{"failed", "ActiveState=inactive\nType=oneshot\nRemainAfterExit=no\nExecMainStatus=1\n",
"stopped", true},
{"running now", "ActiveState=activating\nType=oneshot\nRemainAfterExit=no\nExecMainStatus=0\n",
"running", false},
{"lingers", "ActiveState=active\nType=oneshot\nRemainAfterExit=yes\nExecMainStatus=0\n",
"running", false},
{"a daemon, stopped", "ActiveState=inactive\nType=simple\nRemainAfterExit=no\nExecMainStatus=0\n",