datactl/priv/rtkit.cpp

datactl/priv/rtkit.cpp

datactl/priv/rtkit.cpp

Functions

Name
SY_DEFINE_LOG_CATEGORY(logRtKit , “rtkit” )
boolrunningInSandbox()
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
const intRTKIT_DBUS_TIMEOUT_MSEC

Defines

Name
_GNU_SOURCE
RLIMIT_RTTIME

Functions Documentation

function SY_DEFINE_LOG_CATEGORY

SY_DEFINE_LOG_CATEGORY(
    logRtKit ,
    "rtkit" 
)

function runningInSandbox

static bool runningInSandbox()

Check whether we are running inside an application sandbox (Flatpak or Snap).

Only sandboxed applications need the XDG Realtime portal, since they can not talk to rtkit-daemon on the system bus directly. Outside of a sandbox we skip the portal entirely: xdg-desktop-portal (at least up to 1.22) does not cope with concurrent requests to its Realtime interface and may never answer one of them, which would stall the calling thread until the D-Bus timeout expires.

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";

variable RTKIT_DBUS_TIMEOUT_MSEC

static const int RTKIT_DBUS_TIMEOUT_MSEC = 5000;

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 <cstdlib>
#include <format>
#include <sys/resource.h>
#include <sys/syscall.h>
#include <sys/types.h>
#include <unistd.h>

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

#include "../loginternal.h"

#ifndef RLIMIT_RTTIME
#define RLIMIT_RTTIME 15
#endif

SY_DEFINE_LOG_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";

// Upper bound for any synchronous D-Bus request we make. These calls are issued from
// threads that are about to start processing data, so a stuck request must not stall
// them for GDBus' default 25 s (see runningInSandbox() for why that can happen).
static const int RTKIT_DBUS_TIMEOUT_MSEC = 5000;

static bool runningInSandbox()
{
    const char *container = std::getenv("container");
    if (container != nullptr && std::string(container) == "flatpak")
        return true;
    const char *flatpakId = std::getenv("FLATPAK_ID");
    if (flatpakId != nullptr && flatpakId[0] != '\0')
        return true;
    const char *snap = std::getenv("SNAP");
    if (snap != nullptr && snap[0] != '\0')
        return true;
    return false;
}

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), but only if we actually need it
    if (runningInSandbox()) {
        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,
            RTKIT_DBUS_TIMEOUT_MSEC,
            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,
            RTKIT_DBUS_TIMEOUT_MSEC,
            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) {
            SY_LOG_DEBUG(logRtKit, "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,
            RTKIT_DBUS_TIMEOUT_MSEC,
            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,
            RTKIT_DBUS_TIMEOUT_MSEC,
            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,
            RTKIT_DBUS_TIMEOUT_MSEC,
            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,
            RTKIT_DBUS_TIMEOUT_MSEC,
            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)
        SY_LOG_WARNING(logRtKit, "{}", 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) {
            SY_LOG_INFO(logRtKit, "Unable to set thread niceness to {}, clamped to min value {}", nice, minNice);
            nice = minNice;
        }
    }

    if (!rtkit.makeHighPriority(0, nice)) {
        SY_LOG_WARNING(logRtKit, "{}", rtkit.lastError());
        return false;
    }

    return true;
}

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

    RtKit rtkit;
    const auto maxRTTimeUsec = rtkit.queryRTTimeUSecMax();
    if (maxRTTimeUsec < (100 * 1000)) {
        SY_LOG_WARNING(logRtKit, "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) {
        SY_LOG_WARNING(logRtKit, "Failed to set RLIMIT_RTTIME: {}", strerror(errno));
        return false;
    }

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

    return rtkit.makeRealtime(0, priority);
}

Updated on 2026-09-04 at 09:45:34 +0000