Repository: mozilla/audio_thread_priority (language: rust)

Issue:
Linux support ?
Hi, I am wondering if could it be possible to include support for Linux (if it actually makes sense). I am not an expert in the field but would like to contribute if you give me a clue on where to start looking at.

Best


Discussion / hints:
This is not supported at the moment because doing the classis system calls to switch to `SCHED_RR` require elevated privileges on Linux, and I've been writing this for Firefox, which is not running with elevated privileges.

Ideally we'd use `rtkit`, but it does not work on my machine (stock ubuntu 18.10), I'm missing some some caps probably. Even the program `rtkit-test` provided in `rtkit` does not work immediately. Maybe we should start by figuring out what is needed to have `rtkit` bump a thread to real-time prio.
@padenot thanks for the answer. After reading the `rtkit` [announcement thread](http://lalists.stanford.edu/lad/2009/06/0191.html) and this [article](https://lwn.net/Articles/339316/), I realised how complex the topic is for Linux. Now I understand that it is not just a matter of using a lib like [thread_priority](https://docs.rs/thread-priority/0.1.0/thread_priority/) to request a thread to be RT, but there are all kind of security measures that makes that a non trivial thing for the regular user (I am wondering why isn't this a problem for macOS or Win, perhaps they implement better scheduling policies for RT).

After some search for RealtimeKit, I was not able to find much information or documentation, so it is not really clear to me whether it is something dead or not, although it seems to be included in the repositories of several important distros.

I'll keep an eye for this in the background, and revisit this issue if I find new information.
Meanwhile, I'll keep using this lib for Win and macOS. Thank you very much for the contribution ;-)
I found a way to do this that works on my Ubuntu 18.10, without modification, and without root, as long as `rtkit` is installed, and it's a dep of PulseAudio, so it should be a reasonably common setup.

I'm going to update this crate when I have a minute, as it's going to be very important for us as well. I'll write something up.

Thanks for opening this issue that prompted me to have another look at this, and thanks to @jcristau here in the office that found why the `rtkit` examples weren't working (a nanosecond vs. microsecond unit mismatch) and fixed them. This was in fact already [patched in debian](https://sources.debian.org/src/rtkit/0.11-6/debian/patches/0003-Fix-rtkit-test.patch/), as he pointed out later.

Fix patch:
diff --git a/Cargo.toml b/Cargo.toml
index 119aa15..a0ecd4c 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -22,3 +22,7 @@ libc = "0.2"
 [target.'cfg(target_os = "windows")'.dependencies]
 winapi = { version = "0.3", features = [ "everything" ] }
 kernel32-sys = "0.2"
+
+[target.'cfg(target_os = "linux")'.dependencies]
+libc = "0.2"
+dbus = "0.6.4"
diff --git a/src/lib.rs b/src/lib.rs
index 671b79b..8312796 100644
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -22,6 +22,13 @@ cfg_if! {
         pub use rt_win::promote_current_thread_to_real_time;
         pub use rt_win::demote_current_thread_from_real_time;
         pub use rt_win::RtPriorityHandle;
+    } else if #[cfg(target_os = "linux")] {
+        mod rt_linux;
+        extern crate dbus;
+        extern crate libc;
+        pub use rt_linux::promote_current_thread_to_real_time;
+        pub use rt_linux::demote_current_thread_from_real_time;
+        pub use rt_linux::RtPriorityHandle;
     }
 }
 
@@ -38,7 +45,7 @@ mod tests {
         #[cfg(feature = "terminal-logging")]
         simple_logger::init().unwrap();
         let rt_prio_handle = RtPriorityHandle::new();
-        rt_prio_handle = promote_current_thread_to_real_time(512, 44100).unwrap();
+        let rt_prio_handle = promote_current_thread_to_real_time(512, 44100).unwrap();
         demote_current_thread_from_real_time(rt_prio_handle).unwrap();
     }
 }
diff --git a/src/rt_linux.rs b/src/rt_linux.rs
new file mode 100644
index 0000000..3c721a1
--- /dev/null
+++ b/src/rt_linux.rs
@@ -0,0 +1,96 @@
+/* Widely copied from dbus-rs/dbus/examples/rtkit.rs */
+
+extern crate dbus;
+extern crate libc;
+
+use std::cmp;
+
+use dbus::{Connection, BusType, Props, MessageItem, Message};
+
+/*#[derive(Debug)]*/
+pub struct RtPriorityHandle {
+    policy: libc::c_int,
+    param: libc::sched_param,
+}
+
+impl RtPriorityHandle {
+    pub fn new() -> RtPriorityHandle {
+        return RtPriorityHandle {
+            policy: 0 as libc::c_int,
+            param: libc::sched_param { sched_priority: 0 },
+        }
+    }
+}
+
+fn item_as_i64(i: MessageItem) -> Result<i64, Box<std::error::Error>> {
+    match i {
+        MessageItem::Int32(i) => Ok(i as i64),
+        MessageItem::Int64(i) => Ok(i),
+        _ => Err(Box::from(&*format!("Property is not integer ({:?})", i)))
+    }
+}
+
+fn rtkit_set_realtime(c: &Connection, thread: u64, prio: u32) -> Result<(), ::dbus::Error> {
+    let mut m = Message::new_method_call("org.freedesktop.RealtimeKit1", "/org/freedesktop/RealtimeKit1",
+        "org.freedesktop.RealtimeKit1", "MakeThreadRealtime").unwrap();
+    m.append_items(&[thread.into(), prio.into()]);
+    let mut r = try!(c.send_with_reply_and_block(m, 10000));
+    r.as_result().map(|_| ())
+}
+
+fn make_realtime(prio: u32) -> Result<u32, Box<std::error::Error>> {
+    let c = try!(Connection::get_private(BusType::System));
+
+    let p = Props::new(&c, "org.freedesktop.RealtimeKit1", "/org/freedesktop/RealtimeKit1",
+        "org.freedesktop.RealtimeKit1", 10000);
+
+    // Make sure we don't fail by wanting too much
+    let max_prio = try!(item_as_i64(try!(p.get("MaxRealtimePriority")))) as u32;
+    let prio = cmp::min(prio, max_prio);
+
+    // Enforce RLIMIT_RTPRIO, also a must before asking rtkit for rtprio
+    let max_rttime = try!(item_as_i64(try!(p.get("RTTimeUSecMax")))) as u64;
+    let new_limit = libc::rlimit64 { rlim_cur: max_rttime, rlim_max: max_rttime };
+    let mut old_limit = new_limit;
+    if unsafe { libc::getrlimit64(libc::RLIMIT_RTTIME, &mut old_limit) } < 0 {
+        return Err(Box::from("getrlimit failed"));
+    }
+    if unsafe { libc::setrlimit64(libc::RLIMIT_RTTIME, &new_limit) } < 0 {
+        return Err(Box::from("setrlimit failed"));
+    }
+
+    // Finally, let's ask rtkit to make us realtime
+    let thread_id = unsafe { libc::syscall(libc::SYS_gettid) };
+    let r = rtkit_set_realtime(&c, thread_id as u64, prio);
+
+    if r.is_err() {
+        unsafe { libc::setrlimit64(libc::RLIMIT_RTTIME, &old_limit) };
+    }
+
+    try!(r);
+    Ok(prio)
+}
+
+pub fn promote_current_thread_to_real_time(_audio_buffer_frames: u32,
+                                           _audio_samplerate_hz: u32)
+                                           -> Result<RtPriorityHandle, ()> {
+    let mut policy = 0;
+    let mut param = libc::sched_param { sched_priority: 0 };
+    if unsafe { libc::pthread_getschedparam(libc::pthread_self(), &mut policy, &mut param) } < 0 {
+        return Err(())
+    }
+    let handle = RtPriorityHandle {policy: policy, param: param};
+    let r = make_realtime(5);
+    if r.is_err() {
+        return Err(())
+    }
+    return Ok(handle);
+}
+
+pub fn demote_current_thread_from_real_time(rt_priority_handle: RtPriorityHandle)
+                                            -> Result<(), ()> {
+    if unsafe { libc::pthread_setschedparam(libc::pthread_self(), rt_priority_handle.policy, &rt_priority_handle.param) } < 0 {
+        return Err(());
+    }
+    return Ok(());
+}


Test patch: