fix(power): coalesce charge-limit reapply events
All checks were successful
Check / eval (push) Successful in 3m16s

USB-C power event bursts could repeatedly restart and SIGTERM the settling oneshot until systemd reached its start limit. Let successful runs return inactive and use coalescing start jobs for immediate and delayed reapplication.

Verified: V0 nix flake check --no-build, module parse, and git diff --check; V2 focused battery-charge-limit KVM test with a 12-event active-run burst. V3 hardware item #101 remains queued.
This commit is contained in:
2026-07-13 14:09:05 +01:00
parent 2a34c7398b
commit 7bfe1af5b1
6 changed files with 102 additions and 19 deletions

View File

@@ -688,7 +688,7 @@
};
# Runtime VM check for the battery-charge-limit re-apply: the udev
# rule must restart the oneshot on an AC state change (firmware can
# rule must start the oneshot on an AC state change (firmware can
# clear the threshold on unplug). The `test_power` module fakes a
# Mains adapter we can toggle to emit a real power_supply uevent —
# so this exercises the trigger, not just the config wiring. (The
@@ -697,7 +697,7 @@
# options live) + power.nix, so the VM stays minimal.
battery-charge-limit = pkgs.testers.runNixOSTest {
name = "nomarchy-battery-charge-limit";
nodes.machine = { ... }: {
nodes.machine = { pkgs, ... }: {
imports = [ ./modules/nixos/options.nix ./modules/nixos/power.nix ];
boot.kernelModules = [ "test_power" ]; # fake Mains + battery
nomarchy.system.power = {
@@ -705,6 +705,10 @@
laptop = true;
batteryChargeLimit = 80;
};
# Keep the oneshot active long enough to prove that a burst of
# Mains events coalesces instead of interrupting it.
systemd.services.nomarchy-battery-charge-limit.serviceConfig.ExecStartPost =
"${pkgs.coreutils}/bin/sleep 3";
};
testScript = ''
machine.wait_for_unit("multi-user.target")
@@ -714,23 +718,64 @@
machine.succeed("udevadm settle")
machine.succeed("grep -lx Mains /sys/class/power_supply/*/type")
# Let any add-event restart settle, then sample the invocation.
machine.succeed("sleep 2")
# Let boot/add-event starts and their delayed passes settle,
# then sample the last successful invocation.
machine.succeed("sleep 5")
machine.wait_until_succeeds(
'test "$(systemctl show -p ActiveState --value '
'nomarchy-battery-charge-limit.service)" = inactive',
timeout=30,
)
before = machine.succeed(
"systemctl show -p InvocationID --value "
"nomarchy-battery-charge-limit.service"
).strip()
# Simulate an AC unplug 'change' uevent on the Mains device.
# Simulate an AC unplug, wait until the oneshot is in its
# three-second active window, then emit a dock-like burst of
# real power_supply change events during that same invocation.
machine.succeed("echo off > /sys/module/test_power/parameters/ac_online")
machine.succeed("udevadm settle")
# The udev rule must have restarted the oneshot (new InvocationID).
machine.wait_until_succeeds(
'test "$(systemctl show -p InvocationID --value '
'nomarchy-battery-charge-limit.service)" != "' + before + '"',
'test "$(systemctl show -p ActiveState --value '
'nomarchy-battery-charge-limit.service)" = activating',
timeout=10,
)
mains = machine.succeed(
"grep -lx Mains /sys/class/power_supply/*/type"
).strip().removesuffix("/type")
machine.succeed(
f"for i in $(seq 1 12); do "
f"udevadm trigger --action=change {mains}; done; udevadm settle"
)
# The delayed starts have all fired, the service returned to a
# clean inactive state, and at least one new invocation ran.
machine.succeed("sleep 8")
machine.wait_until_succeeds(
'test "$(systemctl show -p ActiveState --value '
'nomarchy-battery-charge-limit.service)" = inactive',
timeout=30,
)
machine.fail(
"systemctl is-failed --quiet nomarchy-battery-charge-limit.service"
)
machine.succeed(
'test "$(systemctl show -p Result --value '
'nomarchy-battery-charge-limit.service)" = success'
)
after = machine.succeed(
"systemctl show -p InvocationID --value "
"nomarchy-battery-charge-limit.service"
).strip()
assert after != before, (
f"battery charge limit was not re-invoked: {before}"
)
service_log = machine.succeed(
"journalctl -b -u nomarchy-battery-charge-limit.service --no-pager"
)
assert "start-limit-hit" not in service_log, service_log
assert "Failed with result" not in service_log, service_log
'';
};