utils/rtkit.cpp

utils/rtkit.cpp

utils/rtkit.cpp

Functions

Name
boolvariantToInt64(GVariant * variant, int64_t * value)
boolsetCurrentThreadNiceness(int nice)
boolsetCurrentThreadRealtime(int priority)

Attributes

Name
const char[]RTPORTAL_SERVICE_NAME
const char[]RTPORTAL_OBJECT_PATH
const char[]RTPORTAL_INTERFACE_NAME
const char[]RTKIT_SERVICE_NAME
const char[]RTKIT_OBJECT_PATH
const char[]RTKIT_INTERFACE_NAME

Defines

Name
_GNU_SOURCE
RLIMIT_RTTIME

Functions Documentation

function variantToInt64

static bool variantToInt64(
    GVariant * variant,
    int64_t * value
)

function setCurrentThreadNiceness

bool setCurrentThreadNiceness(
    int nice
)

function setCurrentThreadRealtime

bool setCurrentThreadRealtime(
    int priority
)

Attributes Documentation

variable RTPORTAL_SERVICE_NAME

static const char[] RTPORTAL_SERVICE_NAME = "org.freedesktop.portal.Desktop";

variable RTPORTAL_OBJECT_PATH

static const char[] RTPORTAL_OBJECT_PATH = "/org/freedesktop/portal/desktop";

variable RTPORTAL_INTERFACE_NAME

static const char[] RTPORTAL_INTERFACE_NAME = "org.freedesktop.portal.Realtime";

variable RTKIT_SERVICE_NAME

static const char[] RTKIT_SERVICE_NAME = "org.freedesktop.RealtimeKit1";

variable RTKIT_OBJECT_PATH

static const char[] RTKIT_OBJECT_PATH = "/org/freedesktop/RealtimeKit1";

variable RTKIT_INTERFACE_NAME

static const char[] RTKIT_INTERFACE_NAME = "org.freedesktop.RealtimeKit1";

Macros Documentation

define _GNU_SOURCE

#define _GNU_SOURCE 

define RLIMIT_RTTIME

#define RLIMIT_RTTIME 15

Source code

/*
 * Copyright (C) 2019-2026 Matthias Klumpp <matthias@tenstral.net>
 *
 * Licensed under the GNU Lesser General Public License Version 3
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Lesser General Public License as published by
 * the Free Software Foundation, either version 3 of the license, or
 * (at your option) any later version.
 *
 * This software is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public License
 * along with this software.  If not, see <http://www.gnu.org/licenses/>.
 */

#include "rtkit.h"

#ifndef _GNU_SOURCE
#define _GNU_SOURCE
#endif

#ifdef signals
#define SY_RESTORE_QT_SIGNALS
#undef signals
#endif
#include <gio/gio.h>
#ifdef SY_RESTORE_QT_SIGNALS
#define signals Q_SIGNALS
#undef SY_RESTORE_QT_SIGNALS
#endif

#include <climits>
#include <format>
#include <sys/resource.h>
#include <sys/syscall.h>
#include <sys/types.h>
#include <unistd.h>

#include <QDebug>
#include <pthread.h>
#include <sched.h>

#ifndef RLIMIT_RTTIME
#define RLIMIT_RTTIME 15
#endif

Q_LOGGING_CATEGORY(logRtKit, "rtkit")

static const char RTPORTAL_SERVICE_NAME[] = "org.freedesktop.portal.Desktop";
static const char RTPORTAL_OBJECT_PATH[] = "/org/freedesktop/portal/desktop";
static const char RTPORTAL_INTERFACE_NAME[] = "org.freedesktop.portal.Realtime";

static const char RTKIT_SERVICE_NAME[] = "org.freedesktop.RealtimeKit1";
static const char RTKIT_OBJECT_PATH[] = "/org/freedesktop/RealtimeKit1";
static const char RTKIT_INTERFACE_NAME[] = "org.freedesktop.RealtimeKit1";

static bool variantToInt64(GVariant *variant, int64_t *value)
{
    if (variant == nullptr || value == nullptr)
        return false;

    if (g_variant_is_of_type(variant, G_VARIANT_TYPE_INT64)) {
        *value = g_variant_get_int64(variant);
        return true;
    }
    if (g_variant_is_of_type(variant, G_VARIANT_TYPE_UINT64)) {
        *value = static_cast<long long>(g_variant_get_uint64(variant));
        return true;
    }
    if (g_variant_is_of_type(variant, G_VARIANT_TYPE_INT32)) {
        *value = g_variant_get_int32(variant);
        return true;
    }
    if (g_variant_is_of_type(variant, G_VARIANT_TYPE_UINT32)) {
        *value = g_variant_get_uint32(variant);
        return true;
    }

    return false;
}

RtKit::RtKit()
    : m_rtkitProxy(nullptr),
      m_rtPortalProxy(nullptr)
{
    g_autoptr(GError) error = nullptr;

    // Create proxy for realtime portal (session bus)
    m_rtPortalProxy = g_dbus_proxy_new_for_bus_sync(
        G_BUS_TYPE_SESSION,
        G_DBUS_PROXY_FLAGS_NONE,
        nullptr,
        RTPORTAL_SERVICE_NAME,
        RTPORTAL_OBJECT_PATH,
        RTPORTAL_INTERFACE_NAME,
        nullptr,
        &error);

    if (error != nullptr) {
        m_lastError = std::format("Failed to create Realtime Portal proxy: {}", error->message);
        g_clear_error(&error);
    }

    // Create proxy for RtKit (system bus)
    m_rtkitProxy = g_dbus_proxy_new_for_bus_sync(
        G_BUS_TYPE_SYSTEM,
        G_DBUS_PROXY_FLAGS_NONE,
        nullptr,
        RTKIT_SERVICE_NAME,
        RTKIT_OBJECT_PATH,
        RTKIT_INTERFACE_NAME,
        nullptr,
        &error);

    if (error != nullptr) {
        m_lastError = std::format("Failed to create RtKit proxy: {}", error->message);
        g_clear_error(&error);
    }
}

RtKit::~RtKit()
{
    if (m_rtkitProxy != nullptr)
        g_object_unref(m_rtkitProxy);
    if (m_rtPortalProxy != nullptr)
        g_object_unref(m_rtPortalProxy);
}

std::string RtKit::lastError() const
{
    return m_lastError;
}

int RtKit::queryMaxRealtimePriority(bool *ok)
{
    return getIntProperty("MaxRealtimePriority", ok);
}

int RtKit::queryMinNiceLevel(bool *ok)
{
    return getIntProperty("MinNiceLevel", ok);
}

long long RtKit::queryRTTimeUSecMax(bool *ok)
{
    return getIntProperty("RTTimeUSecMax", ok);
}

bool RtKit::makeHighPriority(pid_t thread, int niceLevel)
{
    if (thread == 0)
        thread = gettid();

    // Try using the realtime portal first
    if (m_rtPortalProxy != nullptr) {
        g_autoptr(GError) error = nullptr;
        g_autoptr(GVariant) result = g_dbus_proxy_call_sync(
            m_rtPortalProxy,
            "MakeThreadHighPriorityWithPID",
            g_variant_new("(tti)", (guint64)getpid(), (guint64)thread, (gint32)niceLevel),
            G_DBUS_CALL_FLAGS_NONE,
            -1,
            nullptr,
            &error);

        if (result != nullptr)
            return true;
    }

    // Fallback to using RtKit directly
    if (m_rtkitProxy != nullptr) {
        g_autoptr(GError) error = nullptr;
        g_autoptr(GVariant) result = g_dbus_proxy_call_sync(
            m_rtkitProxy,
            "MakeThreadHighPriority",
            g_variant_new("(ti)", (guint64)thread, (gint32)niceLevel),
            G_DBUS_CALL_FLAGS_NONE,
            -1,
            nullptr,
            &error);

        if (result != nullptr)
            return true;

        if (error != nullptr) {
            m_lastError = std::format(
                "Unable to change thread priority to high: {}: {}",
                error->domain == G_DBUS_ERROR ? error->code : 0,
                error->message);

            // rtkit maps EBUSY to org.freedesktop.DBus.Error.AccessDenied when the per-user
            // MaxConcurrentThreads limit is exceeded. The error name looks like a plain
            // permission problem but is actually resource exhaustion, so disambiguate.
            if (error->domain == G_DBUS_ERROR && error->code == G_DBUS_ERROR_ACCESS_DENIED
                && g_strrstr(error->message, "busy") != nullptr) {
                m_lastError =
                    "Unable to change thread priority to high: RtKit's per-user "
                    "concurrent-thread limit has been reached - too many worker threads "
                    "are already elevated simultaneously.";
            }
        }
    }

    return false;
}

bool RtKit::makeRealtime(pid_t thread, uint priority)
{
    if (thread == 0) {
        struct sched_param sp = {};
        sp.sched_priority = priority;

        if (pthread_setschedparam(pthread_self(), SCHED_RR | SCHED_RESET_ON_FORK, &sp) == 0) {
            qCDebug(logRtKit).noquote() << "Realtime priority obtained via SCHED_RR | SCHED_RESET_ON_FORK directly";
            return true;
        }
        thread = gettid();
    }

    // Try using the realtime portal first
    if (m_rtPortalProxy != nullptr) {
        g_autoptr(GError) error = nullptr;
        g_autoptr(GVariant) result = g_dbus_proxy_call_sync(
            m_rtPortalProxy,
            "MakeThreadRealtimeWithPID",
            g_variant_new("(ttu)", (guint64)getpid(), (guint64)thread, (guint32)priority),
            G_DBUS_CALL_FLAGS_NONE,
            -1,
            nullptr,
            &error);

        if (result != nullptr)
            return true;
    }

    // Fallback to RtKit directly
    if (m_rtkitProxy != nullptr) {
        g_autoptr(GError) error = nullptr;
        g_autoptr(GVariant) result = g_dbus_proxy_call_sync(
            m_rtkitProxy,
            "MakeThreadRealtime",
            g_variant_new("(tu)", (guint64)thread, (guint32)priority),
            G_DBUS_CALL_FLAGS_NONE,
            -1,
            nullptr,
            &error);

        if (result != nullptr)
            return true;

        if (error != nullptr) {
            m_lastError = std::format(
                "Unable to change thread priority to realtime: {}: {}",
                error->domain == G_DBUS_ERROR ? error->code : 0,
                error->message);
        }
    }

    return false;
}

int64_t RtKit::getIntProperty(const std::string &propName, bool *ok)
{
    // Try to get the property from the realtime portal first
    if (m_rtPortalProxy != nullptr) {
        g_autoptr(GError) error = nullptr;
        g_autoptr(GVariant) result = g_dbus_proxy_call_sync(
            m_rtPortalProxy,
            "org.freedesktop.DBus.Properties.Get",
            g_variant_new("(ss)", RTPORTAL_INTERFACE_NAME, propName.c_str()),
            G_DBUS_CALL_FLAGS_NONE,
            -1,
            nullptr,
            &error);

        if (result != nullptr) {
            g_autoptr(GVariant) variant = nullptr;
            g_variant_get(result, "(v)", &variant);
            if (variant != nullptr) {
                int64_t value = 0;
                if (variantToInt64(variant, &value)) {
                    if (ok != nullptr)
                        (*ok) = true;
                    return value;
                }

                m_lastError = std::format(
                    "Realtime Portal property '{}' has unsupported type '{}'.",
                    propName,
                    g_variant_get_type_string(variant));
                if (ok != nullptr)
                    (*ok) = false;
            }
        } else if (error != nullptr) {
            m_lastError = std::format(
                "Realtime Portal property DBus request for '{}' failed: {}", propName, error->message);
        }
    }

    // Fallback to using RtKit directly
    if (m_rtkitProxy != nullptr) {
        g_autoptr(GError) error = nullptr;
        g_autoptr(GVariant) result = g_dbus_proxy_call_sync(
            m_rtkitProxy,
            "org.freedesktop.DBus.Properties.Get",
            g_variant_new("(ss)", RTKIT_INTERFACE_NAME, propName.c_str()),
            G_DBUS_CALL_FLAGS_NONE,
            -1,
            nullptr,
            &error);

        if (result != nullptr) {
            g_autoptr(GVariant) variant = nullptr;
            g_variant_get(result, "(v)", &variant);
            if (variant != nullptr) {
                int64_t value = 0;
                if (variantToInt64(variant, &value)) {
                    if (ok != nullptr)
                        (*ok) = true;
                    return value;
                }

                m_lastError = std::format(
                    "RtKit property '{}' has unsupported type '{}'.", propName, g_variant_get_type_string(variant));
                if (ok != nullptr)
                    (*ok) = false;
            } else {
                m_lastError = std::format("Reply to RtKit property request for '{}' was empty.", propName);
            }
        } else if (error != nullptr) {
            m_lastError = std::format("RtKit property DBus request for '{}' failed: {}", propName, error->message);
        }
    }

    if (ok == nullptr)
        qCWarning(logRtKit).noquote() << m_lastError;
    else
        (*ok) = false;

    return LLONG_MAX;
}

bool setCurrentThreadNiceness(int nice)
{
    RtKit rtkit;
    const auto minNice = rtkit.queryMinNiceLevel();
    if (minNice < 0) {
        if (nice < minNice) {
            qCDebug(logRtKit).noquote().nospace()
                << "Unable to set thread niceness to " << nice << ", clamped to min value " << minNice;
            nice = minNice;
        }
    }

    if (!rtkit.makeHighPriority(0, nice)) {
        qCWarning(logRtKit).noquote().nospace() << rtkit.lastError();
        return false;
    }

    return true;
}

bool setCurrentThreadRealtime(int priority)
{
    struct rlimit rlim = {};

    RtKit rtkit;
    const auto maxRTTimeUsec = rtkit.queryRTTimeUSecMax();
    if (maxRTTimeUsec < (100 * 1000)) {
        qCWarning(logRtKit).noquote()
            << "Unable to set realtime priority: Permitted RLIMIT_RTTIME is too low (< 100ms)";
        return false;
    }

    rlim.rlim_cur = rlim.rlim_max = maxRTTimeUsec;
    if (setrlimit(RLIMIT_RTTIME, &rlim) < 0) {
        qCWarning(logRtKit).noquote() << "Failed to set RLIMIT_RTTIME:" << strerror(errno);
        return false;
    }

    const auto maxRTPrio = rtkit.queryMaxRealtimePriority();
    if (priority > maxRTPrio) {
        qCDebug(logRtKit).noquote().nospace()
            << "Unable to set thread realtime priority to " << priority << ", clamped to max value " << maxRTPrio;
        priority = maxRTPrio;
    }

    return rtkit.makeRealtime(0, priority);
}

Updated on 2026-04-15 at 08:24:00 +0000