fix: stop paying systemd 1-minute timer accuracy slop #447

Merged
joshtronic merged 1 commit from fix/441-timer-accuracy-and-gap into master 2026-07-28 16:14:27 +00:00
Collaborator

What this PR does

  • AccuracySec=1s on agent.timer (was systemd's default: 1min)
  • OnUnitInactiveSec 1min -> 15s

Half of #441 — the two-line half. The cascade-starvation fairness work is a separate change.

Why

AccuracySec defaults to one minute because systemd batches timer wakeups so a sleeping laptop can coalesce them. This is a server that never sleeps: that default buys nothing and costs up to 60s of latency on every fire, stacked on top of the inter-tick gap.

OnUnitInactiveSec is measured from the previous tick's completion, and the tick holds a global flock, so a shorter gap cannot overlap ticks. It just stops idling between them.

Measured over a 4h19m window on 2026-07-27:

74 ticks | 58% utilization | 109 minutes idle
median tick 79s (p90 231s, max 855s)

The mandated 1-minute gaps account for roughly 74 of those idle minutes; the accuracy window for much of the remainder.

Merging this alone changes nothing

The live unit is a symlink to this file:

~/.config/systemd/user/agent.timer -> ~/.local/share/agent/systemd/agent.timer

So the content updates on the harness self-pull, but systemd keeps the old values until a daemon-reload. The self-update path re-execs tick.sh; it does not reload units. After merging, run bin/install.sh (which does daemon-reload + enable --now) or reload by hand.

Flagging it because this is exactly the kind of change that lands, looks done, and quietly has no effect.

Test plan

  • make test passes
  • make lint passes
  • Unit file syntax checked
  • Confirmed the live unit is a symlink and that AccuracyUSec=1min is currently in effect, so the before/after is real

Part of #441

## What this PR does - [x] `AccuracySec=1s` on `agent.timer` (was systemd's default: **1min**) - [x] `OnUnitInactiveSec` `1min` -> `15s` Half of #441 — the two-line half. The cascade-starvation fairness work is a separate change. ## Why `AccuracySec` defaults to one minute because systemd batches timer wakeups so a sleeping laptop can coalesce them. This is a server that never sleeps: that default buys nothing and costs **up to 60s of latency on every fire**, stacked on top of the inter-tick gap. `OnUnitInactiveSec` is measured from the previous tick's *completion*, and the tick holds a global flock, so a shorter gap cannot overlap ticks. It just stops idling between them. Measured over a 4h19m window on 2026-07-27: ``` 74 ticks | 58% utilization | 109 minutes idle median tick 79s (p90 231s, max 855s) ``` The mandated 1-minute gaps account for roughly 74 of those idle minutes; the accuracy window for much of the remainder. ## Merging this alone changes nothing The live unit is a **symlink** to this file: ``` ~/.config/systemd/user/agent.timer -> ~/.local/share/agent/systemd/agent.timer ``` So the content updates on the harness self-pull, but **systemd keeps the old values until a `daemon-reload`**. The self-update path re-execs `tick.sh`; it does not reload units. After merging, run `bin/install.sh` (which does `daemon-reload` + `enable --now`) or reload by hand. Flagging it because this is exactly the kind of change that lands, looks done, and quietly has no effect. ## Test plan - [x] `make test` passes - [x] `make lint` passes - [x] Unit file syntax checked - [x] Confirmed the live unit is a symlink and that `AccuracyUSec=1min` is currently in effect, so the before/after is real Part of #441
fix: stop paying systemd's 1-minute timer accuracy slop
All checks were successful
Lint / check-sync (push) Successful in 5s
Lint / check-sync (pull_request) Successful in 5s
ce416eb0c9
Two values on agent.timer, measured rather than guessed.

AccuracySec was systemd's default of 1min. That default exists so a
sleeping laptop can coalesce wakeups; this is a server that never sleeps,
so it buys nothing and costs up to 60s of latency on EVERY fire, on top of
the inter-tick gap. Now 1s.

OnUnitInactiveSec 1min -> 15s. The gap is measured from the previous
tick's COMPLETION and the tick holds a global flock, so a shorter gap
cannot overlap ticks -- it just stops idling between them.

Measured over a 4h19m window on 2026-07-27: 74 ticks, 58% utilization,
109 minutes idle. The mandated 1-minute gaps account for ~74 of those
minutes and the accuracy window for much of the rest.

NOTE FOR MERGE: the live unit is a SYMLINK to this file, so the content
updates on the harness self-pull but systemd keeps the old values until a
`systemctl --user daemon-reload`. Merging this alone changes nothing --
run bin/install.sh (which reloads and re-enables) or reload by hand.

Does not touch the cascade-starvation half of igor#441; that is its own
change.
Author
Collaborator

🤖 Review — COMMENT (automated)

CI for ce416eb0: success

Two-line systemd timer tuning: OnUnitInactiveSec 1min→15s and a new AccuracySec=1s. In scope for #441, honestly described, CI green. Nothing here blocks, but two things are worth a human's glance before merge.

Checklist audit — clean. Both checked "what this PR does" items correspond exactly to the diff (systemd/agent.timer lines for OnUnitInactiveSec and AccuracySec). No claimed work is missing. The description also proactively flags that merging alone is a no-op until daemon-reload — that's the kind of caveat that usually goes unsaid, and it's accurate: a symlinked unit's content change doesn't re-arm the timer.

Things to weigh (non-blocking):

  • Fast-failing ticks now retry 4x faster. The idle-time math in the description is based on the median tick (79s), where the cycle goes ~139s → ~94s — a ~1.5x rate increase, fine. But the pathological case is a tick that exits almost immediately (auth failure, missing credential, no work + early return): the re-arm gap is the whole cycle, so error loops go from 1/min to 4/min against whatever upstream the tick calls. If there's no backoff on the tick side, this multiplies the blast radius of a stuck state by 4. Worth confirming the tick has its own failure backoff, or that a hot loop at 4/min is acceptable.
  • Possible stale comment immediately above the changed line. The hunk context starts mid-sentence ("…if a tick runs long (the tick also holds a global flock…"), so at least one comment line preceding it is outside the diff window. If that leading line names the interval (e.g. "re-arm one minute after the previous run finishes"), it's now wrong and should be updated in this PR. Please confirm — I can't see it from the diff.

Tests: none added, and for a two-value unit-file change that's defensible; make test/make lint green per CI success. If the repo has any assertion pinning the old 1min value, CI passing suggests there isn't one — no action needed.

Security / scope: nothing. No workflow files touched, no secrets, no behavior outside the timer unit.

Handing to the human on the retry-rate question and the possibly-stale comment line rather than approving outright; if both are fine, this is a straightforward merge (followed by bin/install.sh, per the description).


Independent review by the harness on claude-opus-5 (effort: high). The human reviewer is requested once Igor has reviewed; a human still merges.

### 🤖 Review — `COMMENT` _(automated)_ CI for `ce416eb0`: **success** Two-line systemd timer tuning: `OnUnitInactiveSec` 1min→15s and a new `AccuracySec=1s`. In scope for #441, honestly described, CI green. Nothing here blocks, but two things are worth a human's glance before merge. **Checklist audit** — clean. Both checked "what this PR does" items correspond exactly to the diff (`systemd/agent.timer` lines for `OnUnitInactiveSec` and `AccuracySec`). No claimed work is missing. The description also proactively flags that merging alone is a no-op until `daemon-reload` — that's the kind of caveat that usually goes unsaid, and it's accurate: a symlinked unit's content change doesn't re-arm the timer. **Things to weigh (non-blocking):** - **Fast-failing ticks now retry 4x faster.** The idle-time math in the description is based on the *median* tick (79s), where the cycle goes ~139s → ~94s — a ~1.5x rate increase, fine. But the pathological case is a tick that exits almost immediately (auth failure, missing credential, no work + early return): the re-arm gap is the whole cycle, so error loops go from 1/min to 4/min against whatever upstream the tick calls. If there's no backoff on the tick side, this multiplies the blast radius of a stuck state by 4. Worth confirming the tick has its own failure backoff, or that a hot loop at 4/min is acceptable. - **Possible stale comment immediately above the changed line.** The hunk context starts mid-sentence ("…if a tick runs long (the tick also holds a global flock…"), so at least one comment line preceding it is outside the diff window. If that leading line names the interval (e.g. "re-arm one minute after the previous run finishes"), it's now wrong and should be updated in this PR. Please confirm — I can't see it from the diff. **Tests:** none added, and for a two-value unit-file change that's defensible; `make test`/`make lint` green per CI `success`. If the repo has any assertion pinning the old `1min` value, CI passing suggests there isn't one — no action needed. **Security / scope:** nothing. No workflow files touched, no secrets, no behavior outside the timer unit. Handing to the human on the retry-rate question and the possibly-stale comment line rather than approving outright; if both are fine, this is a straightforward merge (followed by `bin/install.sh`, per the description). --- <sub>Independent review by the harness on `claude-opus-5` (effort: high). The human reviewer is requested once Igor has reviewed; a human still merges.</sub> <!-- review sha=ce416eb0c9620fb66dac11e5ac70841f70142841 verdict=COMMENT ci=success -->
igor added spent time 2026-07-28 16:05:20 +00:00
42 seconds
joshtronic approved these changes 2026-07-28 16:14:20 +00:00
joshtronic deleted branch fix/441-timer-accuracy-and-gap 2026-07-28 16:14:27 +00:00
Sign in to join this conversation.
No reviewers
No milestone
No assignees
2 participants
Notifications
Total time spent: 42 seconds
igor
42 seconds
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set

Reference
joshtronic/igor!447
No description provided.