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
3 Commits
Author SHA1 Message Date
enricobuehler 9af894a374 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
2026-08-14 20:59:54 +02:00
enricobuehler 52df9c59af pkg(linux): nice-limit headroom on every channel, so the renice also works without rtkit
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.
2026-08-14 20:59:52 +02:00
enricobuehler b21b2f6ce9 fix(host): the Linux data-plane renice was a silent no-op everywhere — fall back to RealtimeKit, and boost the audio threads at all
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.
2026-08-14 20:59:39 +02:00