gtk incorrectly unref's a popover that is still a child of a widget
I have a custom widget InspectorPortWidget that shows a popover PortConnectionsPopoverWidget on double click and this popover contains a menu button. There is another popover PortSelectorPopoverWidget attached to that menu button. Somewhere during closing one of those popovers GTK unref's the PortConnectionsPopoverWidget even though it's still a child of my custom widget InspectorPortWidget.
My workaround is to ref popover PortConnectionsPopoverWidget on every double click but I don't know how many times to unref it during dispose so there is a memory leak.
TODO: attempt to write a reproducer
Steps to reproduce
These are relevant to my app but might be useful for attempting to write a reproducer:
- Double click on InspectorPortWidget -> do
gtk_popover_popup()
on PortConnectionsPopoverWidget - Click on the menu button to bring up the other popover PortSelectorPopoverWidget, then click outside of it to close the popover
- Error message below shows up
Current behavior
Somewhere during closing one of those popovers GTK unref's the popover PortConnectionsPopoverWidget even though it's still a child of my custom widget InspectorPortWidget. I get a warning saying PortConnectionsPopoverWidget 0x55558fe14d30 has a parent InspectorPortWidget 0x5555908d08c0 during dispose. Parents hold a reference, so this should not happen
.
Expected outcome
GTK should not unref my popover because it's still a child of my custom widget A.
Version information
GTK 4.6.0 on Arch Linux as a subproject.
Additional information
Backtrace
(zrythm:51314): Gtk-CRITICAL **: 23:17:42.652: (gtk_widget_dispose:7403): PortConnectionsPopoverWidget 0x55558fe14d30 has a parent InspectorPortWidget 0x5555908d08c0 during dispose. Parents hold a reference, so this should not happen.
Did you call g_object_unref() instead of gtk_widget_unparent()?
Thread 1 "zrythm" received signal SIGTRAP, Trace/breakpoint trap.
_log_abort (breakpoint=1) at ../src/utils/log.c:167
167 }
(gdb) bt full
#0 _log_abort(gboolean) (breakpoint=1) at ../src/utils/log.c:167
debugger_present = 1
#1 0x0000555555f8f674 in log_writer_default_custom(GLogLevelFlags, GLogField const*, gsize, gpointer) (log_level=10, fields=0x7fffffffcda0, n_fields=6, user_data=0x555556f945f0) at ../src/utils/log.c:911
self = 0x555556f945f0
stderr_is_journal = 0
initialized = 1
__func__ = "log_writer_default_custom"
#2 0x0000555555f8f975 in log_writer(GLogLevelFlags, GLogField const*, gsize, Log*) (log_level=G_LOG_LEVEL_CRITICAL, fields=0x7fffffffcda0, n_fields=6, self=0x555556f945f0) at ../src/utils/log.c:1023
str = 0x55559139a440 "\n(zrythm:51314): Gtk-CRITICAL **: 23:17:42.652: (gtk_widget_dispose:7403): PortConnectionsPopoverWidget 0x55558fe14d30 has a parent InspectorPortWidget 0x5555908d08c0 during dispose. Parents hold a re"...
#3 0x00007ffff71bc215 in g_log_structured_array () at /usr/lib/libglib-2.0.so.0
#4 0x00007ffff71bc411 in g_log_structured_standard () at /usr/lib/libglib-2.0.so.0
#5 0x00007ffff78ce4ee in gtk_widget_dispose (object=0x55558fe14d30) at ../subprojects/gtk-4.6.0/gtk/gtkwidget.c:7403
widget = 0x55558fe14d30
priv = 0x55558fe14be0
sizegroups = 0x5555908d4e40 = {0x0}
at_context = 0x1
__func__ = "gtk_widget_dispose"
#6 0x00007ffff777c57c in gtk_popover_dispose (object=0x55558fe14d30) at ../subprojects/gtk-4.6.0/gtk/gtkpopover.c:1141
popover = 0x55558fe14d30
priv = 0x55558fe14b40
#7 0x00007ffff72b6301 in g_object_unref () at /usr/lib/libgobject-2.0.so.0
#8 0x00007ffff7739c05 in gtk_main_do_event (event=0x555592b85cd0) at ../subprojects/gtk-4.6.0/gtk/gtkmain.c:1685
event_widget = 0x55558fe14d30
target_widget = 0x55558fe14d30
grab_widget = 0x55558fe14d30
window_group = 0x555582c79310
rewritten_event = 0x0
tmp_list = Python Exception <class 'gdb.MemoryError'>: Cannot access memory at address 0x6f6c63007463656a
#9 0x00007ffff777b751 in surface_event (surface=0x5555909f59a0, event=0x555592b85cd0, widget=0x55558fe14d30) at ../subprojects/gtk-4.6.0/gtk/gtkpopover.c:833
#10 0x00007ffff7a35535 in _gdk_marshal_BOOLEAN__POINTER (closure=0x5555836c4490, return_value=0x7fffffffd720, n_param_values=2, param_values=0x7fffffffd780, invocation_hint=0x7fffffffd700, marshal_data=0x0)
at subprojects/gtk-4.6.0/gdk/gdkmarshalers.c:258
cc = 0x5555836c4490
data1 = 0x5555909f59a0
data2 = 0x55558fe14d30
callback = 0x7ffff777b731 <surface_event>
v_return = 21845
__func__ = "_gdk_marshal_BOOLEAN__POINTER"
#11 0x00007ffff7a73b70 in gdk_surface_event_marshaller (closure=0x5555836c4490, return_value=0x7fffffffd720, n_param_values=2, param_values=0x7fffffffd780, invocation_hint=0x7fffffffd700, marshal_data=0x0)
at ../subprojects/gtk-4.6.0/gdk/gdksurface.c:435
event = 0x555592b85cd0
#12 0x00007ffff72a9d8f in g_closure_invoke () at /usr/lib/libgobject-2.0.so.0
#13 0x00007ffff72c5718 in () at /usr/lib/libgobject-2.0.so.0
#14 0x00007ffff72c640b in g_signal_emit_valist () at /usr/lib/libgobject-2.0.so.0
#15 0x00007ffff72c7330 in g_signal_emit () at /usr/lib/libgobject-2.0.so.0
#16 0x00007ffff7a78c6e in gdk_surface_handle_event (event=0x555592b85cd0) at ../subprojects/gtk-4.6.0/gdk/gdksurface.c:2953
surface = 0x5555909f59a0
begin_time = 0
handled = 0
#17 0x00007ffff7a53b18 in _gdk_event_emit (event=0x555592b85cd0) at ../subprojects/gtk-4.6.0/gdk/gdkevents.c:490
#18 0x00007ffff7adee5f in gdk_event_source_dispatch (source=0x555556fdd050, callback=0x0, user_data=0x0) at ../subprojects/gtk-4.6.0/gdk/x11/gdkeventsource.c:425
display = 0x555582870060
event = 0x555592b85cd0
#19 0x00007ffff71b552c in g_main_context_dispatch () at /usr/lib/libglib-2.0.so.0
#20 0x00007ffff72097b9 in () at /usr/lib/libglib-2.0.so.0
#21 0x00007ffff71b2c11 in g_main_context_iteration () at /usr/lib/libglib-2.0.so.0
#22 0x00007ffff73c82fe in g_application_run () at /usr/lib/libgio-2.0.so.0
--Type <RET> for more, q to quit, c to continue without paging--c
#23 0x0000555555e964bc in main(int, char**) (argc=1, argv=0x7fffffffdd48) at ../src/main.c:42
ret = 0
Here is how the popover PortConnectionsPopoverWidget (inherits from GtkPopover) is added to my widget InspectorPortWidget (inherits from GtkWidget and has a GTK_TYPE_BIN_LAYOUT):
static void
inspector_port_widget_init (
InspectorPortWidget * self)
{
self->connections_popover =
port_connections_popover_widget_new (
GTK_WIDGET (self));
gtk_widget_set_parent (
GTK_WIDGET (self->connections_popover),
GTK_WIDGET (self));
}
The popover stays as a child until I explicitly remove it on dispose:
static void
dispose (
InspectorPortWidget * self)
{
gtk_widget_unparent (
GTK_WIDGET (self->connections_popover));
G_OBJECT_CLASS (
inspector_port_widget_parent_class)->
dispose (G_OBJECT (self));
}
Here is how the secondary popover is set:
static void
port_connections_popover_widget_init (
PortConnectionsPopoverWidget * self)
{
self->port_selector_popover =
port_selector_popover_widget_new (self);
gtk_popover_set_position (
GTK_POPOVER (self->port_selector_popover),
GTK_POS_RIGHT);
g_signal_connect (
G_OBJECT (self->port_selector_popover), "closed",
G_CALLBACK (on_selector_popover_closed), self);
gtk_menu_button_set_popover (
self->add,
GTK_WIDGET (self->port_selector_popover));
}
Here's what happens when the secondary popover (PortSelectorPopoverWidget) closes:
static void
on_selector_popover_closed (
GtkPopover * popover,
PortConnectionsPopoverWidget * self)
{
/* hide and re-popup the parent popover (PortSelectorPopoverWidget)
* because otherwise it cannot be closed when clicking outside of it */
gtk_widget_set_visible (GTK_WIDGET (self), false);
gtk_popover_popup (GTK_POPOVER (self));
}