From b527376985e3dcdd304e389a389ffde118913ce4 Mon Sep 17 00:00:00 2001 From: Alex Date: Sun, 5 Jul 2026 10:24:37 +0200 Subject: [PATCH] fix(audio_backend): release leaked pa_operation handles Every pa_operation* returned by the PulseAudio context introspection, subscribe and volume/mute calls was discarded without pa_operation_unref, leaking one operation object per call. Over a long session the periodic subscription events accumulate an unbounded number of these handles. Capture each returned handle and unref it (guarded against NULL) at every discard site. Callback behavior is unchanged; these calls already run under the threaded-mainloop lock, where unref is safe. --- src/util/audio_backend.cpp | 93 +++++++++++++++++++++++--------------- 1 file changed, 57 insertions(+), 36 deletions(-) diff --git a/src/util/audio_backend.cpp b/src/util/audio_backend.cpp index 25ccde37..19d8109f 100644 --- a/src/util/audio_backend.cpp +++ b/src/util/audio_backend.cpp @@ -4,6 +4,7 @@ #include #include #include +#include #include #include #include @@ -104,16 +105,17 @@ void AudioBackend::contextStateCb(pa_context* c, void* data) { } break; case PA_CONTEXT_READY: - pa_context_get_server_info(c, serverInfoCb, data); + if (auto* o = pa_context_get_server_info(c, serverInfoCb, data)) pa_operation_unref(o); pa_context_set_subscribe_callback(c, subscribeCb, data); - pa_context_subscribe(c, - static_cast( - static_cast(PA_SUBSCRIPTION_MASK_SERVER) | - static_cast(PA_SUBSCRIPTION_MASK_SINK) | - static_cast(PA_SUBSCRIPTION_MASK_SINK_INPUT) | - static_cast(PA_SUBSCRIPTION_MASK_SOURCE) | - static_cast(PA_SUBSCRIPTION_MASK_SOURCE_OUTPUT)), - nullptr, nullptr); + if (auto* o = pa_context_subscribe(c, + static_cast( + static_cast(PA_SUBSCRIPTION_MASK_SERVER) | + static_cast(PA_SUBSCRIPTION_MASK_SINK) | + static_cast(PA_SUBSCRIPTION_MASK_SINK_INPUT) | + static_cast(PA_SUBSCRIPTION_MASK_SOURCE) | + static_cast(PA_SUBSCRIPTION_MASK_SOURCE_OUTPUT)), + nullptr, nullptr)) + pa_operation_unref(o); break; case PA_CONTEXT_FAILED: if (pa_context_errno(c) != PA_ERR_CONNECTIONREFUSED) { @@ -164,15 +166,18 @@ void AudioBackend::subscribeCb(pa_context* context, pa_subscription_event_type_t return; } if (facility == PA_SUBSCRIPTION_EVENT_SERVER) { - pa_context_get_server_info(context, serverInfoCb, data); + if (auto* o = pa_context_get_server_info(context, serverInfoCb, data)) pa_operation_unref(o); } else if (facility == PA_SUBSCRIPTION_EVENT_SINK) { - pa_context_get_sink_info_by_index(context, idx, sinkInfoCb, data); + if (auto* o = pa_context_get_sink_info_by_index(context, idx, sinkInfoCb, data)) + pa_operation_unref(o); } else if (facility == PA_SUBSCRIPTION_EVENT_SINK_INPUT) { - pa_context_get_sink_info_list(context, sinkInfoCb, data); + if (auto* o = pa_context_get_sink_info_list(context, sinkInfoCb, data)) pa_operation_unref(o); } else if (facility == PA_SUBSCRIPTION_EVENT_SOURCE) { - pa_context_get_source_info_by_index(context, idx, sourceInfoCb, data); + if (auto* o = pa_context_get_source_info_by_index(context, idx, sourceInfoCb, data)) + pa_operation_unref(o); } else if (facility == PA_SUBSCRIPTION_EVENT_SOURCE_OUTPUT) { - pa_context_get_source_info_list(context, sourceInfoCb, data); + if (auto* o = pa_context_get_source_info_list(context, sourceInfoCb, data)) + pa_operation_unref(o); } } @@ -184,7 +189,9 @@ void AudioBackend::volumeModifyCb(pa_context* c, int success, void* data) { if (success != 0) { if ((backend->context_ != nullptr) && pa_context_get_state(backend->context_) == PA_CONTEXT_READY) { - pa_context_get_sink_info_by_index(backend->context_, backend->sink_idx_, sinkInfoCb, data); + if (auto* o = pa_context_get_sink_info_by_index(backend->context_, backend->sink_idx_, + sinkInfoCb, data)) + pa_operation_unref(o); } } else { spdlog::debug("Volume modification failed"); @@ -196,8 +203,9 @@ void AudioBackend::sourceVolumeModifyCb(pa_context* c, int success, void* data) if (success != 0) { if ((backend->context_ != nullptr) && pa_context_get_state(backend->context_) == PA_CONTEXT_READY) { - pa_context_get_source_info_by_index(backend->context_, backend->source_idx_, sourceInfoCb, - data); + if (auto* o = pa_context_get_source_info_by_index(backend->context_, backend->source_idx_, + sourceInfoCb, data)) + pa_operation_unref(o); } } else { spdlog::debug("Source volume modification failed"); @@ -317,8 +325,8 @@ void AudioBackend::serverInfoCb(pa_context* context, const pa_server_info* i, vo backend->default_sink_name = i->default_sink_name ? i->default_sink_name : ""; backend->default_source_name_ = i->default_source_name ? i->default_source_name : ""; - pa_context_get_sink_info_list(context, sinkInfoCb, data); - pa_context_get_source_info_list(context, sourceInfoCb, data); + if (auto* o = pa_context_get_sink_info_list(context, sinkInfoCb, data)) pa_operation_unref(o); + if (auto* o = pa_context_get_source_info_list(context, sourceInfoCb, data)) pa_operation_unref(o); } uint16_t AudioBackend::getVolume(PulseaudioTarget target) const { @@ -368,10 +376,13 @@ void AudioBackend::changeVolume(uint16_t volume, uint16_t min_volume, uint16_t m // Apply the volume change pa_threaded_mainloop_lock(mainloop_); if (is_source) { - pa_context_set_source_volume_by_index(context_, source_idx_, &pa_volume, sourceVolumeModifyCb, - this); + if (auto* o = pa_context_set_source_volume_by_index(context_, source_idx_, &pa_volume, + sourceVolumeModifyCb, this)) + pa_operation_unref(o); } else { - pa_context_set_sink_volume_by_index(context_, sink_idx_, &pa_volume, volumeModifyCb, this); + if (auto* o = pa_context_set_sink_volume_by_index(context_, sink_idx_, &pa_volume, + volumeModifyCb, this)) + pa_operation_unref(o); } pa_threaded_mainloop_unlock(mainloop_); } @@ -412,10 +423,13 @@ void AudioBackend::changeVolume(ChangeType change_type, double step, uint16_t ma // No need to continue with volume change if we had to create a new structure pa_threaded_mainloop_lock(mainloop_); if (is_source) { - pa_context_set_source_volume_by_index(context_, source_idx_, &pa_volume, sourceVolumeModifyCb, - this); + if (auto* o = pa_context_set_source_volume_by_index(context_, source_idx_, &pa_volume, + sourceVolumeModifyCb, this)) + pa_operation_unref(o); } else { - pa_context_set_sink_volume_by_index(context_, sink_idx_, &pa_volume, volumeModifyCb, this); + if (auto* o = pa_context_set_sink_volume_by_index(context_, sink_idx_, &pa_volume, + volumeModifyCb, this)) + pa_operation_unref(o); } pa_threaded_mainloop_unlock(mainloop_); return; @@ -458,10 +472,13 @@ void AudioBackend::changeVolume(ChangeType change_type, double step, uint16_t ma // Apply the volume change pa_threaded_mainloop_lock(mainloop_); if (is_source) { - pa_context_set_source_volume_by_index(context_, source_idx_, &pa_volume, sourceVolumeModifyCb, - this); + if (auto* o = pa_context_set_source_volume_by_index(context_, source_idx_, &pa_volume, + sourceVolumeModifyCb, this)) + pa_operation_unref(o); } else { - pa_context_set_sink_volume_by_index(context_, sink_idx_, &pa_volume, volumeModifyCb, this); + if (auto* o = pa_context_set_sink_volume_by_index(context_, sink_idx_, &pa_volume, + volumeModifyCb, this)) + pa_operation_unref(o); } pa_threaded_mainloop_unlock(mainloop_); } @@ -478,8 +495,9 @@ void AudioBackend::toggleSinkMute() { if (context_ == nullptr || pa_context_get_state(context_) != PA_CONTEXT_READY) return; muted_ = !muted_; pa_threaded_mainloop_lock(mainloop_); - pa_context_set_sink_mute_by_index(context_, sink_idx_, static_cast(muted_), nullptr, - nullptr); + if (auto* o = pa_context_set_sink_mute_by_index(context_, sink_idx_, static_cast(muted_), + nullptr, nullptr)) + pa_operation_unref(o); pa_threaded_mainloop_unlock(mainloop_); } @@ -487,8 +505,9 @@ void AudioBackend::toggleSinkMute(bool mute) { if (context_ == nullptr || pa_context_get_state(context_) != PA_CONTEXT_READY) return; muted_ = mute; pa_threaded_mainloop_lock(mainloop_); - pa_context_set_sink_mute_by_index(context_, sink_idx_, static_cast(muted_), nullptr, - nullptr); + if (auto* o = pa_context_set_sink_mute_by_index(context_, sink_idx_, static_cast(muted_), + nullptr, nullptr)) + pa_operation_unref(o); pa_threaded_mainloop_unlock(mainloop_); } @@ -496,8 +515,9 @@ void AudioBackend::toggleSourceMute() { if (context_ == nullptr || pa_context_get_state(context_) != PA_CONTEXT_READY) return; source_muted_ = !source_muted_; pa_threaded_mainloop_lock(mainloop_); - pa_context_set_source_mute_by_index(context_, source_idx_, static_cast(source_muted_), - nullptr, nullptr); + if (auto* o = pa_context_set_source_mute_by_index( + context_, source_idx_, static_cast(source_muted_), nullptr, nullptr)) + pa_operation_unref(o); pa_threaded_mainloop_unlock(mainloop_); } @@ -505,8 +525,9 @@ void AudioBackend::toggleSourceMute(bool mute) { if (context_ == nullptr || pa_context_get_state(context_) != PA_CONTEXT_READY) return; source_muted_ = mute; pa_threaded_mainloop_lock(mainloop_); - pa_context_set_source_mute_by_index(context_, source_idx_, static_cast(source_muted_), - nullptr, nullptr); + if (auto* o = pa_context_set_source_mute_by_index( + context_, source_idx_, static_cast(source_muted_), nullptr, nullptr)) + pa_operation_unref(o); pa_threaded_mainloop_unlock(mainloop_); }