extern crate libc;
use std::convert::TryFrom;
use std::io::Error as OSError;
use std::sync::atomic::{AtomicU8, Ordering};
use crate::AudioThreadPriorityError;
const RT_PRIO_DEFAULT: libc::c_int = 10;
static RT_PRIORITY: AtomicU8 = AtomicU8::new(0);
pub fn set_rt_priority(priority: Option<u8>) {
match priority {
Some(priority) if (1..=99).contains(&priority) => {
RT_PRIORITY.store(priority, Ordering::Relaxed)
}
Some(priority) => {
log::warn!("Ignoring invalid real-time priority {priority}, expected an integer 1-99")
}
None => RT_PRIORITY.store(0, Ordering::Relaxed),
}
}
fn requested_priority() -> libc::c_int {
match RT_PRIORITY.load(Ordering::Relaxed) {
0 => RT_PRIO_DEFAULT,
priority => priority as libc::c_int,
}
}
const SCHED_RESET_ON_FORK: libc::c_int = 0x4000_0000;
#[allow(non_camel_case_types)]
type kernel_pid_t = libc::c_long;
#[repr(C)]
#[derive(Clone, Copy)]
pub struct RtPriorityThreadInfoInternal {
thread_id: kernel_pid_t,
pthread_id: libc::pthread_t,
pid: libc::pid_t,
policy: libc::c_int,
priority: libc::c_int,
}
impl RtPriorityThreadInfoInternal {
pub fn serialize(&self) -> [u8; std::mem::size_of::<Self>()] {
let thread_id = self.thread_id.to_ne_bytes();
let pthread_id = self.pthread_id.to_ne_bytes();
let pid = self.pid.to_ne_bytes();
let policy = self.policy.to_ne_bytes();
let priority = self.priority.to_ne_bytes();
let mut bytes = [0u8; std::mem::size_of::<Self>()];
let fields = thread_id
.iter()
.chain(&pthread_id)
.chain(&pid)
.chain(&policy)
.chain(&priority);
for (dst, &src) in bytes.iter_mut().zip(fields) {
*dst = src;
}
bytes
}
pub fn deserialize(bytes: [u8; std::mem::size_of::<Self>()]) -> Self {
fn take<const N: usize>(src: &mut impl Iterator<Item = u8>) -> [u8; N] {
let mut chunk = [0u8; N];
for slot in &mut chunk {
*slot = src.next().unwrap();
}
chunk
}
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)),
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)),
}
}
pub fn pid(&self) -> libc::pid_t {
self.pid
}
}
impl PartialEq for RtPriorityThreadInfoInternal {
fn eq(&self, other: &Self) -> bool {
self.thread_id == other.thread_id && self.pthread_id == other.pthread_id
}
}
pub struct RtPriorityHandleInternal {
thread_info: RtPriorityThreadInfoInternal,
}
fn pthread_error(context: &str, rc: libc::c_int) -> AudioThreadPriorityError {
AudioThreadPriorityError::new(&format!("{}: {}", context, OSError::from_raw_os_error(rc)))
}
fn sched_error(context: &str) -> AudioThreadPriorityError {
AudioThreadPriorityError::new(&format!("{}: {}", context, OSError::last_os_error()))
}
fn scheduler_tid(thread_id: kernel_pid_t) -> Result<libc::pid_t, AudioThreadPriorityError> {
libc::pid_t::try_from(thread_id)
.map_err(|_| AudioThreadPriorityError::new("thread id does not fit in pid_t"))
}
pub fn get_current_thread_info_internal(
) -> Result<RtPriorityThreadInfoInternal, AudioThreadPriorityError> {
let thread_id = unsafe { libc::syscall(libc::SYS_gettid) };
let pthread_id = unsafe { libc::pthread_self() };
let pid = unsafe { libc::getpid() };
let mut policy = 0;
let mut param = unsafe { std::mem::zeroed::<libc::sched_param>() };
let rc = unsafe { libc::pthread_getschedparam(pthread_id, &mut policy, &mut param) };
if rc != 0 {
return Err(pthread_error("pthread_getschedparam", rc));
}
Ok(RtPriorityThreadInfoInternal {
thread_id,
pthread_id,
pid,
policy,
priority: param.sched_priority,
})
}
pub fn promote_current_thread_to_real_time_internal(
_audio_buffer_frames: u32,
_audio_samplerate_hz: u32,
) -> Result<RtPriorityHandleInternal, AudioThreadPriorityError> {
let thread_info = get_current_thread_info_internal()?;
let mut param = unsafe { std::mem::zeroed::<libc::sched_param>() };
param.sched_priority = requested_priority();
let rc = unsafe {
libc::pthread_setschedparam(
thread_info.pthread_id,
libc::SCHED_FIFO | SCHED_RESET_ON_FORK,
¶m,
)
};
if rc != 0 {
return Err(pthread_error("could not promote thread", rc));
}
Ok(RtPriorityHandleInternal { thread_info })
}
pub fn demote_current_thread_from_real_time_internal(
rt_priority_handle: RtPriorityHandleInternal,
) -> Result<(), AudioThreadPriorityError> {
let RtPriorityThreadInfoInternal {
pthread_id,
policy,
priority,
..
} = rt_priority_handle.thread_info;
let mut param = unsafe { std::mem::zeroed::<libc::sched_param>() };
param.sched_priority = priority;
let rc =
unsafe { libc::pthread_setschedparam(pthread_id, policy | SCHED_RESET_ON_FORK, ¶m) };
if rc != 0 {
return Err(pthread_error("could not demote thread", rc));
}
Ok(())
}
pub fn promote_thread_to_real_time_internal(
thread_info: RtPriorityThreadInfoInternal,
_audio_buffer_frames: u32,
_audio_samplerate_hz: u32,
) -> Result<RtPriorityHandleInternal, AudioThreadPriorityError> {
let tid = scheduler_tid(thread_info.thread_id)?;
let mut param = unsafe { std::mem::zeroed::<libc::sched_param>() };
param.sched_priority = requested_priority();
let rc =
unsafe { libc::sched_setscheduler(tid, libc::SCHED_FIFO | SCHED_RESET_ON_FORK, ¶m) };
if rc < 0 {
return Err(sched_error("could not promote thread"));
}
Ok(RtPriorityHandleInternal { thread_info })
}
pub fn demote_thread_from_real_time_internal(
thread_info: RtPriorityThreadInfoInternal,
) -> Result<(), AudioThreadPriorityError> {
let tid = scheduler_tid(thread_info.thread_id)?;
let mut param = unsafe { std::mem::zeroed::<libc::sched_param>() };
param.sched_priority = thread_info.priority;
let rc =
unsafe { libc::sched_setscheduler(tid, thread_info.policy | SCHED_RESET_ON_FORK, ¶m) };
if rc < 0 {
return Err(sched_error("could not demote thread"));
}
Ok(())
}
pub fn set_real_time_hard_limit_internal(
_audio_buffer_frames: u32,
_audio_samplerate_hz: u32,
) -> Result<(), AudioThreadPriorityError> {
Ok(())
}