Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ version = "0.61.2"
features = [
"Win32_Foundation",
"Win32_System_LibraryLoader",
"Win32_System_Threading",
]

[target.'cfg(target_os = "linux")'.dependencies]
Expand Down
22 changes: 21 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -33,11 +33,31 @@ match promote_current_thread_to_real_time(512, 44100) {

```

A thread other than the calling one can also be promoted or demoted. This is
useful when the thread that needs to become real-time cannot promote itself
directly, for example because it is sandboxed:

```rust
use audio_thread_priority::{get_current_thread_info, promote_thread_to_real_time, demote_thread_from_real_time};

// ... on the thread that will compute audio and has to be real-time, gather
// serializable information about it:
let thread_info = get_current_thread_info().unwrap();
// ... send `thread_info` to another thread, or another process, via any IPC
// mechanism, then:
let handle = promote_thread_to_real_time(thread_info, 512, 44100).unwrap();
// ... and later, from anywhere:
demote_thread_from_real_time(thread_info).unwrap();
```

Promoting a thread other than the caller's, may require elevated privileges
depending on the platform (for example, Linux uses a privileged rtkit D-Bus
service, and macOS/iOS require `task_for_pid` rights).

This library can also be used from C or C++ using the included header and
compiling the rust code in the application. By default, a `.a` is compiled to
ease linking.

# License

MPL-2

22 changes: 17 additions & 5 deletions atp_test.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -3,25 +3,37 @@
#include <string.h>
#include <stdlib.h>
#include <assert.h>
#include <vector>
#include "audio_thread_priority.h"

int main() {
#ifdef __linux__
atp_thread_info* info = atp_get_current_thread_info();
atp_thread_info* info2 = nullptr;

uint8_t buffer[ATP_THREAD_INFO_SIZE];
atp_serialize_thread_info(info, buffer);
// ATP_THREAD_INFO_SIZE is a runtime `extern size_t`, not a compile-time constant, so it can't
// size a stack array portably (a VLA is a GCC/Clang extension MSVC doesn't support). Heap-
// allocate instead.
std::vector<uint8_t> buffer(ATP_THREAD_INFO_SIZE);
atp_serialize_thread_info(info, buffer.data());

info2 = atp_deserialize_thread_info(buffer);
info2 = atp_deserialize_thread_info(buffer.data());

int rv = memcmp(info, info2, ATP_THREAD_INFO_SIZE);
// Compare the two structs via a second serialization round-trip rather than memcmp'ing their
// raw in-memory representations: the platform-specific atp_thread_info can contain padding
// bytes between fields (e.g. on macOS) that aren't guaranteed to be identical between two
// independently-constructed instances even when every actual field is equal.
// atp_serialize_thread_info packs fields explicitly with no padding, so comparing its output is
// well-defined.
std::vector<uint8_t> buffer2(ATP_THREAD_INFO_SIZE);
atp_serialize_thread_info(info2, buffer2.data());
int rv = memcmp(buffer.data(), buffer2.data(), ATP_THREAD_INFO_SIZE);

assert(!rv);

atp_free_thread_info(info);
atp_free_thread_info(info2);

#ifdef __linux__
rv = atp_set_real_time_limit(0, 44100);
assert(!rv);
#endif
Expand Down
47 changes: 22 additions & 25 deletions audio_thread_priority.h
Original file line number Diff line number Diff line change
Expand Up @@ -53,26 +53,28 @@ int32_t atp_demote_current_thread_from_real_time(atp_handle *handle);
int32_t atp_free_handle(atp_handle *handle);

/*
* Linux-only API.
* Promoting/demoting a thread other than the calling one.
*
* This set of functions promotes a thread from another process or thread, for
* cases where the thread to promote cannot do so itself (for example because it
* is sandboxed). With the default build the actual promotion goes through the
* rtkit D-Bus service; when the library is built without the `dbus` feature it
* is done directly, and requires the promoting process to be privileged.
* This is useful when the thread that needs to become real-time cannot
* promote itself directly (for example because it is sandboxed), possibly
* because it lives in another process.
*
* To do so:
* - Set the real-time limit from within the process where a
* thread will be promoted. This is a `setrlimit` call, that can be done
* before the sandbox lockdown.
* - Then, gather information on the thread that will be promoted.
* - Gather information on the thread that will be promoted, by calling
* `atp_get_current_thread_info` on the thread itself.
* - Serialize this info.
* - Send over the serialized data via an IPC mechanism.
* - Send over the serialized data via an IPC mechanism, if the thread that
* will do the promotion is in another process.
* - Deserialize the info.
* - Call `atp_promote_thread_to_real_time`.
*
* Promoting a thread other than the caller's, may require elevated privileges
* depending on the platform (for example, Linux uses a privileged rtkit D-Bus
* service, or requires the promoting process to be privileged when built
* without the `dbus` feature; macOS/iOS require `task_for_pid` rights when the
* thread lives in another process).
*/

#ifdef __linux__
/**
* Promotes a thread, possibly in another process, to real-time priority.
*
Expand All @@ -83,23 +85,21 @@ int32_t atp_free_handle(atp_handle *handle);
* or an upper bound.
* audio_samplerate_hz: sample-rate for this audio stream, in Hz
*
* Returns an opaque handle in case of success, NULL otherwise.
*
* This is useful on Linux only, to promote a thread from another process or
* thread when the thread to promote cannot do so itself (for example because it
* is sandboxed).
* Returns an opaque handle in case of success, NULL otherwise. Demote the thread with
* `atp_demote_thread_from_real_time` (using `thread_info`, not this handle). The returned handle
* is not needed for that call, but it is still heap-allocated and must be freed with
* `atp_free_handle` to avoid leaking it.
*/
atp_handle *atp_promote_thread_to_real_time(atp_thread_info *thread_info);

/**
* Demotes a thread, promoted to real-time priority via
* `atp_promote_thread_to_real_time`, back to its previous priority.
*
* Returns 0 in case of success, non-zero otherwise.
* This takes the same `thread_info` passed to `atp_promote_thread_to_real_time`, not the
* `atp_handle` it returned -- free that handle separately with `atp_free_handle`.
*
* This is useful on Linux only, to promote a thread from another process or
* thread when the thread to promote cannot do so itself (for example because it
* is sandboxed).
* Returns 0 in case of success, non-zero otherwise.
*/
int32_t atp_demote_thread_from_real_time(atp_thread_info* thread_info);

Expand All @@ -109,10 +109,6 @@ int32_t atp_demote_thread_from_real_time(atp_thread_info* thread_info);
*
* Returns a non-null pointer to an `atp_thread_info` structure in case of
* success, to be freed later with `atp_free_thread_info`, and NULL otherwise.
*
* This is useful on Linux only, to promote a thread from another process or
* thread when the thread to promote cannot do so itself (for example because it
* is sandboxed).
*/
atp_thread_info *atp_get_current_thread_info();

Expand All @@ -136,6 +132,7 @@ void atp_serialize_thread_info(atp_thread_info *thread_info, uint8_t *bytes);
* */
atp_thread_info* atp_deserialize_thread_info(uint8_t *bytes);

#ifdef __linux__
/**
* Set the real-time computation limit (RLIMIT_RTTIME) for the calling process.
*
Expand Down
Loading
Loading