undocumented_unsafe_blocks joins unsafe_op_in_unsafe_fn in
[workspace.lints], and the ~100 scattered per-file #![deny(...)] attributes
(85 files) are deleted — a new crate, or a new module in an old one, is now
covered on creation rather than on remembering. The per-file form is how
pf-vkhdr-layer, wdk-probe and half of pf-clipboard stayed uncovered.
There are THREE workspaces, so the claim is made three times: the main
Cargo.toml, packaging/windows/drivers (workspace table + [lints]
workspace = true in all seven members), and packaging/windows/pf-vkhdr-layer
(its [lints] table, previous commit). pf-update now opts into workspace
lints; the two vendored member snapshots (cros-codecs, usbip-sim) stay out
deliberately and now both say so.
Newly-covered fallout was two link-sanity tests (pyrowave-sys, libvpl-sys)
— proofs written. Stale prose that claimed the workspace held
unsafe_op_in_unsafe_fn at "warn" (it has been deny) or pointed at the
deleted attributes is corrected.
nvenc_core.rs is carved OUT of the unsafe_op_in_unsafe_fn fence: its
exemption rationale ("raw entry-table calls almost line for line") was
false — the file makes zero FFI calls. Its unsafe surface is C-union writes
whose soundness hangs on which codec arm is active, and its own 4:4:4 note
records the shipped bug (hevcConfig bytes stamped onto an AV1 config) that
per-operation blocks make visible. It now runs the strictest discipline in
the crate: clippy::multiple_unsafe_ops_per_block at deny, one union access
per block, each naming its codec guard.
Verified here: cargo fmt clean in all three workspaces; native clippy
-D warnings clean for everything that compiles on macOS (the three
pre-existing mac-native failures — pf-client-core wol.rs, pf-encode
dead-code/closure-call, probe mic_burst — reproduce on the clean tree).
Linux/Windows legs ride the .25/.133 gate.
71 lines
4.1 KiB
Rust
71 lines
4.1 KiB
Rust
//! Per-thread OS scheduling QoS for the data plane (plan §W1/§W6 — now in the shared `pf-frame`
|
|
//! leaf). The capture/encode and send threads raise their own priority so a CPU-saturating game
|
|
//! can't deschedule them; the native, GameStream, and direct-NVENC send threads all reach this the
|
|
//! same way (`pf_frame::thread_qos::boost_thread_priority`).
|
|
|
|
/// Raise the current thread's OS scheduling priority so a CPU-heavy game can't deschedule our
|
|
/// capture/encode/send threads. This matters even though our GPU work is already HIGH priority: the
|
|
/// GPU scheduler can only favour commands we've actually SUBMITTED, so if a normal-priority thread is
|
|
/// descheduled by the game it submits the convert/encode late and the GPU priority never bites. Apollo
|
|
/// does the same (capture thread CRITICAL, encoder ABOVE_NORMAL). The Linux host needs this too: an
|
|
/// uncapped GPU-saturating title (e.g. CS2 direct on a virtual output, not capped by gamescope) is
|
|
/// also a CPU hog and can deschedule our submit threads. `critical` → highest non-realtime class
|
|
/// (the capture+encode loop); otherwise above-normal (the send/relay thread).
|
|
pub fn boost_thread_priority(critical: bool) {
|
|
// Windows host-process/thread session tuning (timer 1ms, DWM MMCSS, HIGH class once; MMCSS +
|
|
// keep-display-awake per thread). No-op off Windows. Both stream threads call us, so this covers
|
|
// capture/encode (critical) and send (non-critical).
|
|
crate::session_tuning::on_hot_thread();
|
|
#[cfg(target_os = "windows")]
|
|
// SAFETY: `GetCurrentThread()` returns the constant pseudo-handle for the calling thread — always
|
|
// valid, thread-local in meaning, and never closed (no leak/double-close). `SetThreadPriority`
|
|
// takes that handle plus a `THREAD_PRIORITY_*` value the windows crate defines (HIGHEST or
|
|
// ABOVE_NORMAL here); it only reprioritizes this OS thread, borrows no Rust memory, and its
|
|
// `Result` is matched (a failure is logged, never UB). No pointers, lifetimes, or aliasing.
|
|
unsafe {
|
|
use windows::Win32::System::Threading::{
|
|
GetCurrentThread, SetThreadPriority, THREAD_PRIORITY_ABOVE_NORMAL,
|
|
THREAD_PRIORITY_HIGHEST,
|
|
};
|
|
let prio = if critical {
|
|
THREAD_PRIORITY_HIGHEST
|
|
} else {
|
|
THREAD_PRIORITY_ABOVE_NORMAL
|
|
};
|
|
match SetThreadPriority(GetCurrentThread(), prio) {
|
|
Ok(()) => tracing::debug!(critical, "thread priority raised"),
|
|
Err(e) => {
|
|
tracing::debug!(critical, error = ?e, "SetThreadPriority failed")
|
|
}
|
|
}
|
|
}
|
|
#[cfg(target_os = "linux")]
|
|
{
|
|
// Best-effort nice of the CALLING thread. On Linux `setpriority(PRIO_PROCESS, 0, …)` acts on
|
|
// the calling thread (the kernel resolves who==0 to the current task/tid), and both call
|
|
// sites run inside their worker thread — so this nices exactly the capture/encode (critical)
|
|
// and send (non-critical) threads, nothing else. Silently no-ops without CAP_SYS_NICE / a
|
|
// raised RLIMIT_NICE, which is fine. We deliberately do NOT use SCHED_RR/FIFO by default: a
|
|
// realtime CPU class can preempt the compositor AND the game's own render thread, adding the
|
|
// very frame-time we refuse to add (opt-in only — see PUNKTFUNK_SCHED_RR).
|
|
let nice = if critical { -10 } else { -5 };
|
|
// SAFETY: `setpriority` takes three by-value integers and no pointers, so there is nothing to
|
|
// alias or outlive. `PRIO_PROCESS` with `who == 0` targets the calling task on Linux and
|
|
// `nice` is in range; the call only adjusts this thread's scheduling nice value and returns an
|
|
// `int` we inspect. No memory is touched.
|
|
let rc = unsafe { libc::setpriority(libc::PRIO_PROCESS, 0, nice) };
|
|
if rc == 0 {
|
|
tracing::debug!(critical, nice, "thread nice raised");
|
|
} else {
|
|
tracing::debug!(
|
|
critical,
|
|
"setpriority(nice) no-op (needs CAP_SYS_NICE / RLIMIT_NICE)"
|
|
);
|
|
}
|
|
}
|
|
#[cfg(not(any(target_os = "windows", target_os = "linux")))]
|
|
{
|
|
let _ = critical;
|
|
}
|
|
}
|