From 9c37971cf57f9b6bab44a247ebc7f610cf8186bd Mon Sep 17 00:00:00 2001 From: Paul Adenot Date: Mon, 14 Sep 2026 17:39:21 +0200 Subject: [PATCH 1/2] Fix the build with musl, where pthread_t is a pointer musl defines pthread_t as *mut c_void instead of an integer, so the to_ne_bytes/from_ne_bytes serialization didn't compile. Go through usize, which is as wide as pthread_t on every Linux target, keeping the serialized layout unchanged. Also check the musl targets in CI. Fixes #49 --- .github/workflows/rust.yml | 36 ++++++++++++++++++++++++++++++++++++ src/rt_linux.rs | 6 ++++-- src/rt_linux_native.rs | 6 ++++-- 3 files changed, 44 insertions(+), 4 deletions(-) diff --git a/.github/workflows/rust.yml b/.github/workflows/rust.yml index 8c71d5a..916a27c 100644 --- a/.github/workflows/rust.yml +++ b/.github/workflows/rust.yml @@ -91,3 +91,39 @@ jobs: sudo prlimit --pid $$ --rtprio=10:10 echo "RLIMIT_RTPRIO soft=$(ulimit -Sr) hard=$(ulimit -Hr)" rustup run ${{ matrix.rust }} cargo test --no-default-features + + # musl defines pthread_t as a pointer instead of an integer, unlike glibc. + build-musl: + runs-on: ubuntu-24.04 + strategy: + matrix: + rust: [stable, nightly] + + steps: + - uses: actions/checkout@v2 + + - name: Install Rust (musl target) + run: | + rustup toolchain install ${{ matrix.rust }} --profile minimal + rustup target add x86_64-unknown-linux-musl --toolchain ${{ matrix.rust }} + + - name: Install Dependencies + run: sudo apt-get update && sudo apt-get install libdbus-1-dev + + - name: Build (musl, with dbus) + shell: bash + # The crate is a staticlib/rlib, so this doesn't link, and hence needs no musl libdbus: only + # the headers, which pkg-config finds for the host. + run: PKG_CONFIG_ALLOW_CROSS=1 rustup run ${{ matrix.rust }} cargo build --target x86_64-unknown-linux-musl --features dbus + + - name: Build (musl, without dbus) + shell: bash + run: rustup run ${{ matrix.rust }} cargo build --target x86_64-unknown-linux-musl --no-default-features + + - name: Test (musl, without dbus) + shell: bash + # Statically linked musl test binaries run fine on the glibc runner. As in the fallback job, + # the native promotion path needs permission to request real-time scheduling. + run: | + sudo prlimit --pid $$ --rtprio=10:10 + rustup run ${{ matrix.rust }} cargo test --target x86_64-unknown-linux-musl --no-default-features -- --nocapture diff --git a/src/rt_linux.rs b/src/rt_linux.rs index 982803c..82f9bff 100644 --- a/src/rt_linux.rs +++ b/src/rt_linux.rs @@ -62,7 +62,9 @@ impl RtPriorityThreadInfoInternal { /// struct, so no uninitialized padding bytes are ever read. Any trailing padding stays zero. pub fn serialize(&self) -> [u8; std::mem::size_of::()] { let thread_id = self.thread_id.to_ne_bytes(); - let pthread_id = self.pthread_id.to_ne_bytes(); + // `pthread_t` is an integer with glibc but a pointer with musl: go through `usize`, which + // is as wide as both. + let pthread_id = (self.pthread_id as usize).to_ne_bytes(); let pid = self.pid.to_ne_bytes(); let policy = self.policy.to_ne_bytes(); let priority = self.priority.to_ne_bytes(); @@ -91,7 +93,7 @@ impl RtPriorityThreadInfoInternal { let mut src = bytes.iter().copied(); RtPriorityThreadInfoInternal { thread_id: kernel_pid_t::from_ne_bytes(take(&mut src)), - pthread_id: libc::pthread_t::from_ne_bytes(take(&mut src)), + pthread_id: usize::from_ne_bytes(take(&mut src)) as libc::pthread_t, pid: libc::pid_t::from_ne_bytes(take(&mut src)), policy: libc::c_int::from_ne_bytes(take(&mut src)), priority: libc::c_int::from_ne_bytes(take(&mut src)), diff --git a/src/rt_linux_native.rs b/src/rt_linux_native.rs index def33b0..f166eb0 100644 --- a/src/rt_linux_native.rs +++ b/src/rt_linux_native.rs @@ -78,7 +78,9 @@ impl RtPriorityThreadInfoInternal { /// struct, so no uninitialized padding bytes are ever read. Any trailing padding stays zero. pub fn serialize(&self) -> [u8; std::mem::size_of::()] { let thread_id = self.thread_id.to_ne_bytes(); - let pthread_id = self.pthread_id.to_ne_bytes(); + // `pthread_t` is an integer with glibc but a pointer with musl: go through `usize`, which + // is as wide as both. + let pthread_id = (self.pthread_id as usize).to_ne_bytes(); let pid = self.pid.to_ne_bytes(); let policy = self.policy.to_ne_bytes(); let priority = self.priority.to_ne_bytes(); @@ -107,7 +109,7 @@ impl RtPriorityThreadInfoInternal { let mut src = bytes.iter().copied(); RtPriorityThreadInfoInternal { thread_id: kernel_pid_t::from_ne_bytes(take(&mut src)), - pthread_id: libc::pthread_t::from_ne_bytes(take(&mut src)), + pthread_id: usize::from_ne_bytes(take(&mut src)) as libc::pthread_t, pid: libc::pid_t::from_ne_bytes(take(&mut src)), policy: libc::c_int::from_ne_bytes(take(&mut src)), priority: libc::c_int::from_ne_bytes(take(&mut src)), From 920fcfd7603d34608d95d59ab80cfb31bcb05c2b Mon Sep 17 00:00:00 2001 From: Paul Adenot Date: Mon, 14 Sep 2026 17:46:59 +0200 Subject: [PATCH 2/2] Promote threads by tid with the syscall, not musl's stub musl's sched_setscheduler always fails with ENOSYS: POSIX specifies it as process-scoped, which Linux doesn't provide. The syscall is per-thread, which is what promoting a thread by tid needs. --- src/rt_linux_native.rs | 26 ++++++++++++++++++++++---- 1 file changed, 22 insertions(+), 4 deletions(-) diff --git a/src/rt_linux_native.rs b/src/rt_linux_native.rs index f166eb0..9420e89 100644 --- a/src/rt_linux_native.rs +++ b/src/rt_linux_native.rs @@ -142,6 +142,26 @@ fn sched_error(context: &str) -> AudioThreadPriorityError { AudioThreadPriorityError::new(&format!("{}: {}", context, OSError::last_os_error())) } +/// Set the scheduling policy and priority of the thread with tid `tid`. +/// +/// This goes through the syscall directly because musl's `sched_setscheduler` is a stub that always +/// fails with `ENOSYS`: POSIX specifies it as process-scoped, which Linux does not provide. The +/// syscall is per-thread, which is what is needed here. +fn sched_setscheduler( + tid: libc::pid_t, + policy: libc::c_int, + param: &libc::sched_param, +) -> libc::c_long { + unsafe { + libc::syscall( + libc::SYS_sched_setscheduler, + tid, + policy, + param as *const libc::sched_param, + ) + } +} + /// A thread's system-wide tid narrowed to `pid_t` for the scheduler syscalls. A tid always fits in /// `pid_t` (it is a pid), but convert defensively rather than truncating. fn scheduler_tid(thread_id: kernel_pid_t) -> Result { @@ -238,8 +258,7 @@ pub fn promote_thread_to_real_time_internal( let mut param = unsafe { std::mem::zeroed::() }; param.sched_priority = requested_priority(); - let rc = - unsafe { libc::sched_setscheduler(tid, libc::SCHED_FIFO | SCHED_RESET_ON_FORK, ¶m) }; + let rc = sched_setscheduler(tid, libc::SCHED_FIFO | SCHED_RESET_ON_FORK, ¶m); if rc < 0 { return Err(sched_error("could not promote thread")); } @@ -256,8 +275,7 @@ pub fn demote_thread_from_real_time_internal( let tid = scheduler_tid(thread_info.thread_id)?; let mut param = unsafe { std::mem::zeroed::() }; param.sched_priority = thread_info.priority; - let rc = - unsafe { libc::sched_setscheduler(tid, thread_info.policy | SCHED_RESET_ON_FORK, ¶m) }; + let rc = sched_setscheduler(tid, thread_info.policy | SCHED_RESET_ON_FORK, ¶m); if rc < 0 { return Err(sched_error("could not demote thread")); }