module-rt: allow building without RTKit

This commit is contained in:
Théo Lebrun 2022-03-10 11:19:12 +01:00 committed by Wim Taymans
parent 377bc16eb5
commit 6798b9a933
2 changed files with 246 additions and 138 deletions

View file

@ -131,8 +131,8 @@ pipewire_module_profiler = shared_library('pipewire-module-profiler',
dependencies : [spa_dep, mathlib, dl_lib, pipewire_dep], dependencies : [spa_dep, mathlib, dl_lib, pipewire_dep],
) )
build_module_rt = dbus_dep.found()
if build_module_rt
pipewire_module_rt = shared_library('pipewire-module-rt', [ 'module-rt.c' ], pipewire_module_rt = shared_library('pipewire-module-rt', [ 'module-rt.c' ],
include_directories : [configinc], include_directories : [configinc],
install : true, install : true,
@ -140,6 +140,9 @@ pipewire_module_rt = shared_library('pipewire-module-rt', [ 'module-rt.c' ],
install_rpath: modules_install_dir, install_rpath: modules_install_dir,
dependencies : [dbus_dep, mathlib, dl_lib, pipewire_dep], dependencies : [dbus_dep, mathlib, dl_lib, pipewire_dep],
) )
build_module_rtkit = dbus_dep.found()
if build_module_rtkit
# TODO: This serves as a temporary alias to prevent breaking existing setups # TODO: This serves as a temporary alias to prevent breaking existing setups
# while `module-rtkit` is being migrated to `module-rt` # while `module-rtkit` is being migrated to `module-rt`
pipewire_module_rtkit = shared_library('pipewire-module-rtkit', [ 'module-rt.c' ], pipewire_module_rtkit = shared_library('pipewire-module-rtkit', [ 'module-rt.c' ],
@ -150,7 +153,7 @@ pipewire_module_rtkit = shared_library('pipewire-module-rtkit', [ 'module-rt.c'
dependencies : [dbus_dep, mathlib, dl_lib, pipewire_dep], dependencies : [dbus_dep, mathlib, dl_lib, pipewire_dep],
) )
endif endif
summary({'rt': build_module_rt}, bool_yn: true, section: 'Optional Modules') summary({'rtkit': build_module_rtkit}, bool_yn: true, section: 'Optional Modules')
build_module_portal = dbus_dep.found() build_module_portal = dbus_dep.found()
if build_module_portal if build_module_portal

View file

@ -1,6 +1,6 @@
/* PipeWire /* PipeWire
* *
* Copyright © 2018 Wim Taymans * Copyright © 2022 Wim Taymans
* *
* Permission is hereby granted, free of charge, to any person obtaining a * Permission is hereby granted, free of charge, to any person obtaining a
* copy of this software and associated documentation files (the "Software"), * copy of this software and associated documentation files (the "Software"),
@ -21,6 +21,30 @@
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
* DEALINGS IN THE SOFTWARE. * DEALINGS IN THE SOFTWARE.
*/ */
/***
Copyright 2009 Lennart Poettering
Copyright 2010 David Henningsson <diwic@ubuntu.com>
Permission is hereby granted, free of charge, to any person
obtaining a copy of this software and associated documentation files
(the "Software"), to deal in the Software without restriction,
including without limitation the rights to use, copy, modify, merge,
publish, distribute, sublicense, and/or sell copies of the Software,
and to permit persons to whom the Software is furnished to do so,
subject to the following conditions:
The above copyright notice and this permission notice shall be
included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
***/
#include <stdlib.h> #include <stdlib.h>
#include <stdbool.h> #include <stdbool.h>
@ -35,16 +59,21 @@
#include <unistd.h> #include <unistd.h>
#include <pthread.h> #include <pthread.h>
#include <sys/resource.h> #include <sys/resource.h>
#include <sys/syscall.h>
#include "config.h" #include "config.h"
#include <spa/support/dbus.h>
#include <spa/utils/result.h> #include <spa/utils/result.h>
#include <spa/utils/string.h> #include <spa/utils/string.h>
#include <pipewire/impl.h> #include <pipewire/impl.h>
#include <pipewire/thread.h> #include <pipewire/thread.h>
#ifdef HAVE_DBUS
#include <spa/support/dbus.h>
#include <dbus/dbus.h>
#endif
/** \page page_module_rt PipeWire Module: RT /** \page page_module_rt PipeWire Module: RT
* *
* The `rt` module uses the operating system's scheduler to enable realtime * The `rt` module uses the operating system's scheduler to enable realtime
@ -87,7 +116,15 @@ static const struct spa_dict_item module_props[] = {
{ PW_KEY_MODULE_VERSION, PACKAGE_VERSION }, { PW_KEY_MODULE_VERSION, PACKAGE_VERSION },
}; };
struct pw_rtkit_bus; #ifdef HAVE_DBUS
#define RTKIT_SERVICE_NAME "org.freedesktop.RealtimeKit1"
#define RTKIT_OBJECT_PATH "/org/freedesktop/RealtimeKit1"
/** \cond */
struct pw_rtkit_bus {
DBusConnection *bus;
};
/** \endcond */
struct thread { struct thread {
struct impl *impl; struct impl *impl;
@ -97,6 +134,7 @@ struct thread {
void *(*start)(void*); void *(*start)(void*);
void *arg; void *arg;
}; };
#endif /* HAVE_DBUS */
struct impl { struct impl {
struct pw_context *context; struct pw_context *context;
@ -110,6 +148,7 @@ struct impl {
struct spa_hook module_listener; struct spa_hook module_listener;
#ifdef HAVE_DBUS
bool use_rtkit; bool use_rtkit;
struct pw_rtkit_bus *system_bus; struct pw_rtkit_bus *system_bus;
@ -119,52 +158,29 @@ struct impl {
pthread_mutex_t lock; pthread_mutex_t lock;
pthread_cond_t cond; pthread_cond_t cond;
struct spa_list threads_list; struct spa_list threads_list;
#endif
}; };
/***
Copyright 2009 Lennart Poettering
Copyright 2010 David Henningsson <diwic@ubuntu.com>
Permission is hereby granted, free of charge, to any person
obtaining a copy of this software and associated documentation files
(the "Software"), to deal in the Software without restriction,
including without limitation the rights to use, copy, modify, merge,
publish, distribute, sublicense, and/or sell copies of the Software,
and to permit persons to whom the Software is furnished to do so,
subject to the following conditions:
The above copyright notice and this permission notice shall be
included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
***/
#include <dbus/dbus.h>
#include "config.h"
#include <sys/syscall.h>
#define RTKIT_SERVICE_NAME "org.freedesktop.RealtimeKit1"
#define RTKIT_OBJECT_PATH "/org/freedesktop/RealtimeKit1"
#ifndef RLIMIT_RTTIME #ifndef RLIMIT_RTTIME
#define RLIMIT_RTTIME 15 #define RLIMIT_RTTIME 15
#endif #endif
/** \cond */ static pid_t _gettid(void)
struct pw_rtkit_bus { {
DBusConnection *bus; #if defined(HAVE_GETTID)
}; return (pid_t) gettid();
/** \endcond */ #elif defined(__linux__)
return syscall(SYS_gettid);
#elif defined(__FreeBSD__)
long pid;
thr_self(&pid);
return (pid_t)pid;
#else
#error "No gettid impl"
#endif
}
#ifdef HAVE_DBUS
struct pw_rtkit_bus *pw_rtkit_bus_get_system(void) struct pw_rtkit_bus *pw_rtkit_bus_get_system(void)
{ {
struct pw_rtkit_bus *bus; struct pw_rtkit_bus *bus;
@ -204,21 +220,6 @@ void pw_rtkit_bus_free(struct pw_rtkit_bus *system_bus)
free(system_bus); free(system_bus);
} }
static pid_t _gettid(void)
{
#if defined(HAVE_GETTID)
return (pid_t) gettid();
#elif defined(__linux__)
return syscall(SYS_gettid);
#elif defined(__FreeBSD__)
long pid;
thr_self(&pid);
return (pid_t)pid;
#else
#error "No gettid impl"
#endif
}
static int translate_error(const char *name) static int translate_error(const char *name)
{ {
pw_log_warn("RTKit error: %s", name); pw_log_warn("RTKit error: %s", name);
@ -458,6 +459,7 @@ finish:
return ret; return ret;
} }
#endif /* HAVE_DBUS */
static void module_destroy(void *data) static void module_destroy(void *data)
{ {
@ -466,8 +468,11 @@ static void module_destroy(void *data)
pw_thread_utils_set(NULL); pw_thread_utils_set(NULL);
spa_hook_remove(&impl->module_listener); spa_hook_remove(&impl->module_listener);
#ifdef HAVE_DBUS
if (impl->system_bus) if (impl->system_bus)
pw_rtkit_bus_free(impl->system_bus); pw_rtkit_bus_free(impl->system_bus);
#endif
free(impl); free(impl);
} }
@ -480,7 +485,7 @@ static const struct pw_impl_module_events module_events = {
* Check if the current user has permissions to use realtime scheduling at the * Check if the current user has permissions to use realtime scheduling at the
* specified priority. * specified priority.
*/ */
static bool check_realtime_priviliges(rlim_t priority) static bool check_realtime_privileges(rlim_t priority)
{ {
int old_policy; int old_policy;
struct sched_param old_sched_params; struct sched_param old_sched_params;
@ -516,28 +521,33 @@ static bool check_realtime_priviliges(rlim_t priority)
} }
} }
static int sched_set_nice(int nice_level)
{
if (setpriority(PRIO_PROCESS, _gettid(), nice_level) == 0)
return 0;
else
return -errno;
}
static int set_nice(struct impl *impl, int nice_level) static int set_nice(struct impl *impl, int nice_level)
{ {
int res = 0; int res = 0;
pid_t tid;
if (impl->use_rtkit) { #ifdef HAVE_DBUS
if ((res = pw_rtkit_make_high_priority(impl->system_bus, 0, nice_level)) < 0) { if (impl->use_rtkit)
pw_log_warn("could not set nice-level to %d: %s", res = pw_rtkit_make_high_priority(impl->system_bus, 0, nice_level);
nice_level, spa_strerror(res)); else
} else { res = sched_set_nice(nice_level);
pw_log_info("main thread nice level set to %d", nice_level); #else
} res = sched_set_nice(nice_level);
#endif
if (res < 0) {
pw_log_warn("could not set nice-level to %d: %s",
nice_level, spa_strerror(res));
} else { } else {
tid = _gettid(); pw_log_info("main thread nice level set to %d",
if (setpriority(PRIO_PROCESS, tid, nice_level) == 0) { nice_level);
pw_log_info("main thread nice level set to %d",
nice_level);
} else {
res = -errno;
pw_log_warn("could not set nice-level to %d: %s",
nice_level, spa_strerror(res));
}
} }
return res; return res;
@ -546,13 +556,16 @@ static int set_nice(struct impl *impl, int nice_level)
static int set_rlimit(struct impl *impl) static int set_rlimit(struct impl *impl)
{ {
struct rlimit rl; struct rlimit rl;
long long rttime;
int res = 0; int res = 0;
#ifdef HAVE_DBUS
long long rttime;
#endif
spa_zero(rl); spa_zero(rl);
rl.rlim_cur = impl->rt_time_soft; rl.rlim_cur = impl->rt_time_soft;
rl.rlim_max = impl->rt_time_hard; rl.rlim_max = impl->rt_time_hard;
#ifdef HAVE_DBUS
if (impl->use_rtkit) { if (impl->use_rtkit) {
rttime = pw_rtkit_get_rttime_usec_max(impl->system_bus); rttime = pw_rtkit_get_rttime_usec_max(impl->system_bus);
if (rttime >= 0) { if (rttime >= 0) {
@ -565,6 +578,7 @@ static int set_rlimit(struct impl *impl)
rl.rlim_max = SPA_MIN(rl.rlim_max, (rlim_t)rttime); rl.rlim_max = SPA_MIN(rl.rlim_max, (rlim_t)rttime);
} }
} }
#endif
if (setrlimit(RLIMIT_RTTIME, &rl) < 0) if (setrlimit(RLIMIT_RTTIME, &rl) < 0)
res = -errno; res = -errno;
@ -578,6 +592,46 @@ static int set_rlimit(struct impl *impl)
return res; return res;
} }
static int impl_acquire_rt_sched(struct spa_thread *thread, int priority)
{
int err;
struct sched_param sp;
pthread_t pt = (pthread_t)thread;
if (priority < sched_get_priority_min(REALTIME_POLICY) ||
priority > sched_get_priority_max(REALTIME_POLICY)) {
pw_log_warn("invalid priority %d for policy %d", priority, REALTIME_POLICY);
return -EINVAL;
}
spa_zero(sp);
sp.sched_priority = priority;
if ((err = pthread_setschedparam(pt, REALTIME_POLICY | PW_SCHED_RESET_ON_FORK, &sp)) != 0) {
pw_log_warn("could not make thread %p realtime: %s", thread, strerror(err));
return -err;
}
pw_log_info("acquired realtime priority %d for thread %p", priority, thread);
return 0;
}
static int impl_drop_rt_generic(void *data, struct spa_thread *thread)
{
struct sched_param sp;
pthread_t pt = (pthread_t)thread;
int err;
spa_zero(sp);
if ((err = pthread_setschedparam(pt, SCHED_OTHER | PW_SCHED_RESET_ON_FORK, &sp)) != 0) {
pw_log_debug("thread %p: SCHED_OTHER|SCHED_RESET_ON_FORK failed: %s",
thread, strerror(err));
return -err;
}
pw_log_info("thread %p dropped realtime priority", thread);
return 0;
}
#ifdef HAVE_DBUS
static struct thread *find_thread_by_pt(struct impl *impl, pthread_t pt) static struct thread *find_thread_by_pt(struct impl *impl, pthread_t pt)
{ {
struct thread *t; struct thread *t;
@ -714,42 +768,14 @@ static int impl_acquire_rt(void *data, struct spa_thread *thread, int priority)
if ((err = pw_rtkit_make_realtime(impl->system_bus, pid, priority)) < 0) { if ((err = pw_rtkit_make_realtime(impl->system_bus, pid, priority)) < 0) {
pw_log_warn("could not make thread %d realtime using RTKit: %s", pid, spa_strerror(err)); pw_log_warn("could not make thread %d realtime using RTKit: %s", pid, spa_strerror(err));
} else { return err;
pw_log_info("acquired realtime priority %d for thread %d using RTKit", priority, pid);
} }
pw_log_info("acquired realtime priority %d for thread %d using RTKit", priority, pid);
return 0;
} else { } else {
if (priority < sched_get_priority_min(REALTIME_POLICY) || return impl_acquire_rt_sched(thread, priority);
priority > sched_get_priority_max(REALTIME_POLICY)) {
pw_log_warn("invalid priority %d for policy %d", priority, REALTIME_POLICY);
return -EINVAL;
}
spa_zero(sp);
sp.sched_priority = priority;
if ((err = pthread_setschedparam(pt, REALTIME_POLICY | PW_SCHED_RESET_ON_FORK, &sp)) != 0) {
pw_log_warn("could not make thread %p realtime: %s", thread, strerror(err));
return -err;
}
pw_log_info("acquired realtime priority %d for thread %p", priority, thread);
} }
return 0;
}
static int impl_drop_rt(void *data, struct spa_thread *thread)
{
struct sched_param sp;
pthread_t pt = (pthread_t)thread;
int err;
spa_zero(sp);
if ((err = pthread_setschedparam(pt, SCHED_OTHER | PW_SCHED_RESET_ON_FORK, &sp)) != 0) {
pw_log_debug("thread %p: SCHED_OTHER|SCHED_RESET_ON_FORK failed: %s",
thread, strerror(err));
return -err;
}
pw_log_info("thread %p dropped realtime priority", thread);
return 0;
} }
static const struct spa_thread_utils_methods impl_thread_utils = { static const struct spa_thread_utils_methods impl_thread_utils = {
@ -758,36 +784,108 @@ static const struct spa_thread_utils_methods impl_thread_utils = {
.join = impl_join, .join = impl_join,
.get_rt_range = impl_get_rt_range, .get_rt_range = impl_get_rt_range,
.acquire_rt = impl_acquire_rt, .acquire_rt = impl_acquire_rt,
.drop_rt = impl_drop_rt, .drop_rt = impl_drop_rt_generic,
}; };
#else /* HAVE_DBUS */
static struct spa_thread *impl_create(void *data, const struct spa_dict *props,
void *(*start_routine)(void*), void *arg)
{
pthread_t pt;
int err;
err = pthread_create(&pt, NULL, start_routine, arg);
if (err != 0) {
errno = err;
return NULL;
}
return (struct spa_thread*)pt;
}
static int impl_join(void *data, struct spa_thread *thread, void **retval)
{
return pthread_join((pthread_t)thread, retval);
}
static int impl_get_rt_range(void *data, const struct spa_dict *props,
int *min, int *max)
{
if (min)
*min = sched_get_priority_min(REALTIME_POLICY);
if (max)
*max = sched_get_priority_max(REALTIME_POLICY);
return 0;
}
static int impl_acquire_rt(void *data, struct spa_thread *thread, int priority)
{
struct impl *impl = data;
/* See the docstring on `spa_thread_utils_methods::acquire_rt` */
if (priority == -1) {
priority = impl->rt_prio;
}
return impl_acquire_rt_sched(thread, priority);
}
static const struct spa_thread_utils_methods impl_thread_utils = {
SPA_VERSION_THREAD_UTILS_METHODS,
.create = impl_create,
.join = impl_join,
.get_rt_range = impl_get_rt_range,
.acquire_rt = impl_acquire_rt,
.drop_rt = impl_drop_rt_generic,
};
#endif /* HAVE_DBUS */
#ifdef HAVE_DBUS
static int should_use_rtkit(struct impl *impl, struct pw_context *context, bool *use_rtkit)
{
const struct pw_properties *context_props;
const char *str;
*use_rtkit = true;
if ((context_props = pw_context_get_properties(context)) != NULL &&
(str = pw_properties_get(context_props, "support.dbus")) != NULL &&
!pw_properties_parse_bool(str))
*use_rtkit = false;
/* If the user has permissions to use regular realtime scheduling, then
* we'll use that instead of RTKit */
if (check_realtime_privileges(impl->rt_prio)) {
*use_rtkit = false;
} else {
if (!(*use_rtkit)) {
pw_log_warn("neither regular realtime scheduling nor RTKit are available");
return -ENOTSUP;
}
/* TODO: Should this be pw_log_warn or pw_log_debug instead? */
pw_log_info("could not use realtime scheduling, falling back to using RTKit instead");
}
return 0;
}
#endif /* HAVE_DBUS */
SPA_EXPORT SPA_EXPORT
int pipewire__module_init(struct pw_impl_module *module, const char *args) int pipewire__module_init(struct pw_impl_module *module, const char *args)
{ {
struct pw_context *context = pw_impl_module_get_context(module); struct pw_context *context = pw_impl_module_get_context(module);
struct impl *impl; struct impl *impl;
const struct pw_properties *context_props;
struct pw_properties *props; struct pw_properties *props;
const char *str;
bool use_rtkit = true;
int res = 0; int res = 0;
PW_LOG_TOPIC_INIT(mod_topic); PW_LOG_TOPIC_INIT(mod_topic);
if ((context_props = pw_context_get_properties(context)) != NULL &&
(str = pw_properties_get(context_props, "support.dbus")) != NULL &&
!pw_properties_parse_bool(str))
use_rtkit = false;
impl = calloc(1, sizeof(struct impl)); impl = calloc(1, sizeof(struct impl));
if (impl == NULL) if (impl == NULL)
return -ENOMEM; return -ENOMEM;
spa_list_init(&impl->threads_list);
pthread_mutex_init(&impl->lock, NULL);
pthread_cond_init(&impl->cond, NULL);
pw_log_debug("module %p: new", impl); pw_log_debug("module %p: new", impl);
props = args ? pw_properties_new_string(args) : pw_properties_new(NULL, NULL); props = args ? pw_properties_new_string(args) : pw_properties_new(NULL, NULL);
@ -802,22 +900,15 @@ int pipewire__module_init(struct pw_impl_module *module, const char *args)
impl->rt_time_soft = pw_properties_get_int32(props, "rt.time.soft", DEFAULT_RT_TIME_SOFT); impl->rt_time_soft = pw_properties_get_int32(props, "rt.time.soft", DEFAULT_RT_TIME_SOFT);
impl->rt_time_hard = pw_properties_get_int32(props, "rt.time.hard", DEFAULT_RT_TIME_HARD); impl->rt_time_hard = pw_properties_get_int32(props, "rt.time.hard", DEFAULT_RT_TIME_HARD);
/* If the user has permissions to use regular realtime scheduling, then #ifdef HAVE_DBUS
* we'll use that instead of RTKit */ spa_list_init(&impl->threads_list);
if (check_realtime_priviliges(impl->rt_prio)) { pthread_mutex_init(&impl->lock, NULL);
use_rtkit = false; pthread_cond_init(&impl->cond, NULL);
} else {
if (!use_rtkit) {
res = -ENOTSUP;
pw_log_warn("neither regular realtime scheduling nor RTKit are available");
goto error;
}
/* TODO: Should this be pw_log_warn or pw_log_debug instead? */ if ((res = should_use_rtkit(impl, context, &impl->use_rtkit)) < 0) {
pw_log_info("could not use realtime scheduling, falling back to using RTKit instead"); goto error;
} }
impl->use_rtkit = use_rtkit;
if (impl->use_rtkit) { if (impl->use_rtkit) {
impl->system_bus = pw_rtkit_bus_get_system(); impl->system_bus = pw_rtkit_bus_get_system();
if (impl->system_bus == NULL) { if (impl->system_bus == NULL) {
@ -826,6 +917,13 @@ int pipewire__module_init(struct pw_impl_module *module, const char *args)
goto error; goto error;
} }
} }
#else
if (!check_realtime_privileges(impl->rt_prio)) {
res = -ENOTSUP;
pw_log_warn("regular realtime scheduling not available (RTKit fallback disabled)");
goto error;
}
#endif
if (IS_VALID_NICE_LEVEL(impl->nice_level)) if (IS_VALID_NICE_LEVEL(impl->nice_level))
set_nice(impl, impl->nice_level); set_nice(impl, impl->nice_level);
@ -842,16 +940,23 @@ int pipewire__module_init(struct pw_impl_module *module, const char *args)
pw_impl_module_update_properties(module, &SPA_DICT_INIT_ARRAY(module_props)); pw_impl_module_update_properties(module, &SPA_DICT_INIT_ARRAY(module_props));
pw_impl_module_update_properties(module, &props->dict); pw_impl_module_update_properties(module, &props->dict);
#ifdef HAVE_DBUS
if (impl->use_rtkit) { if (impl->use_rtkit) {
pw_log_debug("initialized using RTKit"); pw_log_debug("initialized using RTKit");
} else { } else {
pw_log_debug("initialized using regular realtime scheduling"); pw_log_debug("initialized using regular realtime scheduling");
} }
#else
pw_log_debug("initialized using regular realtime scheduling");
#endif
goto done; goto done;
error: error:
#ifdef HAVE_DBUS
if (impl->system_bus) if (impl->system_bus)
pw_rtkit_bus_free(impl->system_bus); pw_rtkit_bus_free(impl->system_bus);
#endif
free(impl); free(impl);
done: done:
pw_properties_free(props); pw_properties_free(props);