The Linux data-plane renice was a silent no-op on every install — RealtimeKit fallback, audio threads boosted at all, nice-limit headroom on every channel #232

Merged
enricobuehler merged 3 commits from worktree-thread-qos-rtkit into main 2026-08-14 19:15:41 +00:00
Owner

Every Linux host to date ran its capture/encode/send threads at nice 0: boost_thread_priority's setpriority() needs CAP_SYS_NICE or a raised RLIMIT_NICE, no channel granted either, and the host binary can never carry a file capability (KWin identification, the 0.26.0-1 incident). The 2026-08-14 field log (Skynet, fc44, GbE client) showed the cost: fresh-launch shader storms descheduled the unprioritized threads → 5 ms audio datagrams left late enough to stutter → the client's OWD signal rose → ABR cut a gigabit session to its 5 Mbps floor at zero loss, while the same box carried 708 Mbps cleanly once the storm passed.

Code (b21b2f6c): the renice now falls back to RealtimeKit (MakeThreadHighPriorityWithPID, one blocking system-bus call per boosted thread) — the same unprivileged broker PipeWire clients use; nothing enters the permitted set. And the audio plane is boosted for the first time: the 5 ms Opus capture→encode→send loop (critical), the PipeWire capture mainloop thread (its process callbacks run there), and the pad-audio streamer. zbus mirrors ashpd's backend choice (tokio + blocking-api, no async-io).

Packaging (52df9c59): new packaging/linux/50-punktfunk-nice.conf (user@.service.d, LimitNICE=-15) shipped by rpm (→ Bazzite sysext via rpm2cpio), Arch, deb; the Steam Deck installer writes it to /etc (read-only /usr). rpm/deb gain Recommends: rtkit, Arch an optdepends hint; the NixOS module sets security.rtkit.enable = mkDefault true. A limit, not a grant — effective from next login; rtkit needs no relogin at all.

Gates (home-nix-1, native x86_64 Linux via the repo devShell): cargo check + clippy --all-targets -D warnings for pf-frame + punktfunk-host green (non-vacuous — both crates re-checked); checks.x86_64-linux.nixos-module green; cargo fmt clean; xcheck windows green (pf-frame). Runtime-proven in the exact deployment shape: a probe of the same zbus call run inside user@1000.service on home-nix-1 → setpriority refused, rtkit granted, final nice=-10. (The same probe from an SSH session gets polkit-denied — expected; the host doesn't run there.)

Every Linux host to date ran its capture/encode/send threads at nice 0: `boost_thread_priority`'s `setpriority()` needs CAP_SYS_NICE or a raised RLIMIT_NICE, no channel granted either, and the host binary can never carry a file capability (KWin identification, the 0.26.0-1 incident). The 2026-08-14 field log (Skynet, fc44, GbE client) showed the cost: fresh-launch shader storms descheduled the unprioritized threads → 5 ms audio datagrams left late enough to stutter → the client's OWD signal rose → ABR cut a gigabit session to its 5 Mbps floor at zero loss, while the same box carried 708 Mbps cleanly once the storm passed. **Code** (`b21b2f6c`): the renice now falls back to RealtimeKit (`MakeThreadHighPriorityWithPID`, one blocking system-bus call per boosted thread) — the same unprivileged broker PipeWire clients use; nothing enters the permitted set. And the audio plane is boosted for the first time: the 5 ms Opus capture→encode→send loop (critical), the PipeWire capture mainloop thread (its `process` callbacks run there), and the pad-audio streamer. zbus mirrors ashpd's backend choice (`tokio` + `blocking-api`, no `async-io`). **Packaging** (`52df9c59`): new `packaging/linux/50-punktfunk-nice.conf` (`user@.service.d`, `LimitNICE=-15`) shipped by rpm (→ Bazzite sysext via rpm2cpio), Arch, deb; the Steam Deck installer writes it to `/etc` (read-only `/usr`). rpm/deb gain `Recommends: rtkit`, Arch an optdepends hint; the NixOS module sets `security.rtkit.enable = mkDefault true`. A limit, not a grant — effective from next login; rtkit needs no relogin at all. **Gates** (home-nix-1, native x86_64 Linux via the repo devShell): `cargo check` + `clippy --all-targets -D warnings` for `pf-frame` + `punktfunk-host` green (non-vacuous — both crates re-checked); `checks.x86_64-linux.nixos-module` green; `cargo fmt` clean; xcheck windows green (pf-frame). Runtime-proven in the exact deployment shape: a probe of the same zbus call run inside `user@1000.service` on home-nix-1 → `setpriority` refused, **rtkit granted, final nice=-10**. (The same probe from an SSH session gets polkit-denied — expected; the host doesn't run there.)
enricobuehler added 3 commits 2026-08-14 19:15:26 +00:00
Every Linux host to date ran its capture/encode/send threads at nice 0:
boost_thread_priority's setpriority() needs CAP_SYS_NICE or a raised
RLIMIT_NICE, no install channel granted either, and the host binary can
never carry a file capability (a capped process's /proc/<pid>/exe is
unreadable to KWin — the 0.26.0-1 incident). A 2026-08-14 field log
showed the cost end to end: a fresh game launch's shader-compile storm
descheduled the unprioritized threads, 5 ms audio datagrams left late
enough to stutter, the client's OWD signal rose, and ABR cut a
gigabit-Ethernet session to its 5 Mbps floor at zero loss — while the
same box carried 708 Mbps cleanly once the storm passed.

The renice now falls back to RealtimeKit (MakeThreadHighPriorityWithPID,
one blocking system-bus call per boosted thread) — the same unprivileged
broker PipeWire clients use, so nothing enters the permitted set and
KWin identification is untouched. Only the nice verb, never
MakeThreadRealtime: the SCHED_RR reservations apply to rtkit-granted RR
too. zbus rides ashpd's exact backend choice (tokio, no async-io) plus
blocking-api, so the resolved graph gains no second I/O backend.

And the audio plane is boosted for the first time: the 5 ms Opus
capture->encode->send loop (critical — a stall there is directly
audible), the PipeWire capture mainloop thread (its process callbacks
run there; PipeWire's own module-rt only covers data loops we don't
use), and the pad-audio streamer (above-normal, like the session send
thread). The first two had no boost call at all; on Windows the
audio_thread boost also engages, via the SetThreadPriority arm.
A new shared drop-in, packaging/linux/50-punktfunk-nice.conf
(user@.service.d, LimitNICE=-15), raises the user-session nice hard
limit so the direct setpriority() path works on rtkit-less boxes — a
limit, not a grant, effective from the next login. Shipped by rpm
(%files + install, flows into the Bazzite sysext via rpm2cpio), Arch,
and deb; the Steam Deck installer writes it to
/etc/systemd/system/user@.service.d instead (SteamOS /usr is
read-only), following its existing sudo-to-/etc pattern.

rpm and deb gain a weak Recommends: rtkit and Arch an optdepends hint —
with rtkit the fix needs no relogin at all. The NixOS module instead
sets security.rtkit.enable = mkDefault true (rtkit is not a given
there; mkDefault keeps it operator-overridable).

It remains true on every channel that the host binary must never carry
a file capability — the spec's no-caps note now names the two fallback
rungs instead of calling the thread nice a best-effort no-op.
docs: changelog for the Linux thread-priority fix
ci / bun-nix (pull_request) Successful in 32s
nix / flake (pull_request) Failing after 44s
ci / rust-arm64 (pull_request) Failing after 51s
android / android (pull_request) Failing after 53s
ci / web (pull_request) Successful in 1m15s
apple / swift (pull_request) Successful in 2m2s
apple / distribute (pull_request) Skipped
apple / screenshots (pull_request) Skipped
windows-client / client (arm64, --no-default-features, aarch64-pc-windows-msvc, C:\t-a64) (pull_request) Successful in 3m19s
ci / docs-site (pull_request) Successful in 4m53s
windows-client / client (x64, , x86_64-pc-windows-msvc, C:\t) (pull_request) Successful in 6m32s
ci / rust (pull_request) Successful in 17m59s
9af894a374
enricobuehler merged commit daabb85373 into main 2026-08-14 19:15:41 +00:00
enricobuehler deleted branch worktree-thread-qos-rtkit 2026-08-14 19:15:43 +00:00
Sign in to join this conversation.
No Reviewers
No labels
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: unom/punktfunk#232