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
enricobuehlermerged 3 commits from worktree-thread-qos-rtkit into main2026-08-14 19:15:41 +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 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.)
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.
Blocking a user prevents them from interacting with repositories, such as opening or commenting on pull requests or issues. Learn more about blocking a user.
Every Linux host to date ran its capture/encode/send threads at nice 0:
boost_thread_priority'ssetpriority()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 (itsprocesscallbacks run there), and the pad-audio streamer. zbus mirrors ashpd's backend choice (tokio+blocking-api, noasync-io).Packaging (
52df9c59): newpackaging/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 gainRecommends: rtkit, Arch an optdepends hint; the NixOS module setssecurity.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 warningsforpf-frame+punktfunk-hostgreen (non-vacuous — both crates re-checked);checks.x86_64-linux.nixos-modulegreen;cargo fmtclean; xcheck windows green (pf-frame). Runtime-proven in the exact deployment shape: a probe of the same zbus call run insideuser@1000.serviceon home-nix-1 →setpriorityrefused, rtkit granted, final nice=-10. (The same probe from an SSH session gets polkit-denied — expected; the host doesn't run there.)