From f3806ad5dfc4f30a026fe07f969f4239113bc41e Mon Sep 17 00:00:00 2001 From: Pauli Virtanen Date: Thu, 13 Jul 2023 17:59:03 +0300 Subject: [PATCH] mixer-control: remove objects properly when reconnecting to PA GvcMixerCards are not removed when reconnecting to PA server, which causes duplicate card entries to appear on PA restart. Moreover, the old GvcMixerCard instances have pointers to the old already freed pa_context, resulting to use-after-free on operations. Duplicate entries are also caused by sink/source removal on reconnect not sending right signals. Make it clean up all Gvc objects as if we got subscribe remove events for them all: Use remove_sink/remove_source to remove sinks/sources so that the right signals are emitted. Remove cards using remove_card, so that also they get cleaned up. Remove also any leftover GvcMixerUIDevices. Move cleanup of streams etc. before pa_context unref, so that we free the objects referring the pa_context before freeing the context. This fixes gnome-control-center crashing when PA server is restarted, and one e.g. tries to do something that ends up in gvc_mixer_card_change_profile such as selecting output device with port in different profile. It also fixes duplicate entries appearing in the device lists on Pipewire restart (they don't appear with Pulseaudio since PA device IDs don't change on restart). It should also fix similar crashes in gnome-shell. --- gvc-mixer-control.c | 40 ++++++++++++++++++++++++++-------------- 1 file changed, 26 insertions(+), 14 deletions(-) diff --git a/gvc-mixer-control.c b/gvc-mixer-control.c index 6218a1b..b8a3f1f 100644 --- a/gvc-mixer-control.c +++ b/gvc-mixer-control.c @@ -3119,6 +3119,9 @@ remove_card (GvcMixerControl *control, g_object_get (G_OBJECT (device), "card", &card, NULL); + if (card == NULL) + continue; + if (gvc_mixer_card_get_index (card) == index) { g_signal_emit (G_OBJECT (control), signals[gvc_mixer_ui_device_is_output (device) ? OUTPUT_REMOVED : INPUT_REMOVED], @@ -3434,15 +3437,22 @@ gvc_mixer_new_pa_context (GvcMixerControl *self) } static void -remove_all_streams (GvcMixerControl *control, GHashTable *hash_table) +remove_all_items (GvcMixerControl *control, + GHashTable *hash_table, + void (*remove_item)(GvcMixerControl *control, guint index)) { GHashTableIter iter; gpointer key, value; g_hash_table_iter_init (&iter, hash_table); while (g_hash_table_iter_next (&iter, &key, &value)) { - remove_stream (control, value); - g_hash_table_iter_remove (&iter); + if (remove_item) { + remove_item (control, GPOINTER_TO_UINT (key)); + g_hash_table_remove (hash_table, key); + g_hash_table_iter_init (&iter, hash_table); + } else { + g_hash_table_iter_remove (&iter); + } } } @@ -3450,11 +3460,22 @@ static gboolean idle_reconnect (gpointer data) { GvcMixerControl *control = GVC_MIXER_CONTROL (data); - GHashTableIter iter; - gpointer key, value; g_return_val_if_fail (control, FALSE); + g_debug ("Reconnect: clean up all objects"); + + remove_all_items (control, control->priv->sinks, remove_sink); + remove_all_items (control, control->priv->sources, remove_source); + remove_all_items (control, control->priv->sink_inputs, remove_sink_input); + remove_all_items (control, control->priv->source_outputs, remove_source_output); + remove_all_items (control, control->priv->cards, remove_card); + remove_all_items (control, control->priv->ui_inputs, NULL); + remove_all_items (control, control->priv->ui_outputs, NULL); + remove_all_items (control, control->priv->clients, remove_client); + + g_debug ("Reconnect: make new connection"); + if (control->priv->pa_context) { pa_context_unref (control->priv->pa_context); control->priv->pa_context = NULL; @@ -3462,15 +3483,6 @@ idle_reconnect (gpointer data) gvc_mixer_new_pa_context (control); } - remove_all_streams (control, control->priv->sinks); - remove_all_streams (control, control->priv->sources); - remove_all_streams (control, control->priv->sink_inputs); - remove_all_streams (control, control->priv->source_outputs); - - g_hash_table_iter_init (&iter, control->priv->clients); - while (g_hash_table_iter_next (&iter, &key, &value)) - g_hash_table_iter_remove (&iter); - gvc_mixer_control_open (control); /* cannot fail */ control->priv->reconnect_id = 0;