After an evaluation, GNOME has moved from Bugzilla to GitLab. Learn more about GitLab.
No new issues can be reported in GNOME Bugzilla anymore.
To report an issue in a GNOME project, go to GNOME GitLab.
Do not go to GNOME Gitlab for: Bluefish, Doxygen, GnuCash, GStreamer, java-gnome, LDTP, NetworkManager, Tomboy.
Bug 597845 - [St] Call clutter_actor_destroy in dispose vfunc
[St] Call clutter_actor_destroy in dispose vfunc
Status: RESOLVED FIXED
Product: gnome-shell
Classification: Core
Component: st
unspecified
Other All
: Normal normal
: ---
Assigned To: gnome-shell-maint
gnome-shell-maint
: 597844 (view as bug list)
Depends on:
Blocks:
 
 
Reported: 2009-10-08 18:55 UTC by Colin Walters
Modified: 2012-09-06 21:15 UTC
See Also:
GNOME target: ---
GNOME version: ---


Attachments
[St] Call clutter_actor_destroy in dispose vfunc (1.47 KB, patch)
2009-10-08 18:55 UTC, Colin Walters
none Details | Review
[St] Call clutter_actor_destroy in dispose vfunc (1.60 KB, patch)
2009-10-08 19:39 UTC, Colin Walters
needs-work Details | Review
[St] Use clutter_actor_destroy in dispose, add _dispose where needed (5.24 KB, patch)
2009-10-09 15:45 UTC, Colin Walters
committed Details | Review

Description Colin Walters 2009-10-08 18:55:46 UTC
ClutterGroup calls _destroy, but St was just calling _unparent.
This caused problems because the DESTROY signal was not emitted
for child elements after destroying a toplevel.  Also, in a GC'd
binding it would cause unpredictable lifetime of children.

https://bugzilla.gnome.org/show_bug.cgi?id=597844
Comment 1 Colin Walters 2009-10-08 18:55:48 UTC
Created attachment 145055 [details] [review]
[St] Call clutter_actor_destroy in dispose vfunc
Comment 2 Colin Walters 2009-10-08 19:39:05 UTC
Created attachment 145068 [details] [review]
[St] Call clutter_actor_destroy in dispose vfunc

ClutterGroup calls _destroy, but St was just calling _unparent.
This caused problems because the DESTROY signal was not emitted
for child elements after destroying a toplevel.  Also, in a GC'd
binding it would cause unpredictable lifetime of children.
Comment 3 Owen Taylor 2009-10-08 19:55:04 UTC
Review of attachment 145068 [details] [review]:

StTable isn't handled here
StScrollView needs to destroy, not remove, its scrollbars
StLabel needs to destroy the ClutterText (it doesn't even unparent it!)
StTooltip needs to destroy the label (it doesn't even unparent it!)

StScrollBar is probably OK since the steppers are really internal so unparenting will work.
Note that StWidget can't destroy the background since it will cause problems for the StButton background animation hack; since the background is really internal it's probably OK to just unparent it.

You might want to double-check the above by doing a grep for clutter_actor_set_parent() in case I missed something.

::: src/st/st-bin.c
@@ +357,1 @@
       priv->child = NULL;

I think this probably would be better as:
 
 assert (priv->child == NULL);

So we catch the problem not leak if for some reason the destroy doesn't cause the child to be removed.

::: src/st/st-box-layout.c
@@ +405,3 @@
+  g_list_foreach (priv->children, (GFunc) clutter_actor_destroy, NULL);
+  /* We rely on unparenting in _destroy */
+  g_assert (priv->children == NULL);

I think this is better as:

 while (priv->children)
   clutter_actor_destroy (priv->children->data);

More robust against weird side effects.
Comment 4 Owen Taylor 2009-10-08 19:56:03 UTC
*** Bug 597844 has been marked as a duplicate of this bug. ***
Comment 5 Colin Walters 2009-10-09 15:45:03 UTC
Created attachment 145144 [details] [review]
[St] Use clutter_actor_destroy in dispose, add _dispose where needed

ClutterGroup calls _destroy, but most of St was just calling _unparent.
This caused problems because the DESTROY signal was not emitted
for child elements after destroying a toplevel.  Also, in a GC'd
binding it would cause unpredictable lifetime of children.

Some St widgets simply didn't have _dispose at all; implement it.

Note because of the usage of the background_image in StButton,
we can't cleanly destroy it inside the StWidget.
Comment 6 Owen Taylor 2009-10-09 17:50:50 UTC
Review of attachment 145144 [details] [review]:

Looks good to me with two small fixes noted below.

::: src/st/st-label.c
@@ +188,3 @@
+{
+  StLabelPrivate *priv = ST_LABEL (actor)->priv;
+  clutter_actor_destroy (priv->label);

dispose can be called more than once, so this should be like st-entry.c

 if (priv->label)
   {
      clutter_actor_destroy (priv->label);
      priv->label = NULL';
   }

(Don't worry about the fact that other code in the widget doesn't handle priv->label == NULL -  resurrecting actors would take a ton more work. The most common case for double dispose is that at dispose time the object doesn't get freed because there is a refcount to it and then it will get disposed again as finalized - this happens, e.g., any time you call label.destroy() in JS since JS will have a reference to the label until GC time.)

::: src/st/st-scroll-view.c
@@ +133,1 @@
       priv->vscroll = NULL;

Hmm, little bit weird because StScrollView does have a remove(), it just doesn't handle the hscroll/vscroll, but I think it's OK.

::: src/st/st-tooltip.c
@@ +361,3 @@
+  StTooltipPrivate *priv = ST_TOOLTIP (self)->priv;
+
+  clutter_actor_destroy (priv->label);

Needs the if (priv->label) { ... ; priv->label = NULL } as described for st-label.c
Comment 7 Owen Taylor 2009-10-09 18:41:03 UTC
Review of attachment 145144 [details] [review]:

Your patch on bug 597919 points out that StScrollBar should call g_object_run_dispose() on the adjustment to disconnect signals there, since otherwise the innocuous looking:

  let vAdjust = scrollview.vscroll.adjustment;
  vAdjust.connect('changed', Lang.bind(this, function () { this._onAdjustScopeChanged(tabData); }));

creates an uncollectable loop - the callback referenes the scope, the scope references the vAdjust, the vAdjust references the callback.
Comment 8 Colin Walters 2009-10-09 20:08:14 UTC
(In reply to comment #7)
> Review of attachment 145144 [details] [review]:
> 
> Your patch on bug 597919 points out that StScrollBar should call
> g_object_run_dispose() on the adjustment to disconnect signals there, since
> otherwise the innocuous looking:
> 
>   let vAdjust = scrollview.vscroll.adjustment;
>   vAdjust.connect('changed', Lang.bind(this, function () {
> this._onAdjustScopeChanged(tabData); }));
> 
> creates an uncollectable loop - the callback referenes the scope, the scope
> references the vAdjust, the vAdjust references the callback.

Hmm...the ScrollBar doesn't necessarily "own" the adjustments since someone could call st_scroll_bar_set_adjustment.  So is it still correct to dispose there?
Comment 9 Colin Walters 2009-10-09 20:11:18 UTC
(In reply to comment #7)
> Review of attachment 145144 [details] [review]:
> 
> Your patch on bug 597919 points out that StScrollBar should call
> g_object_run_dispose() on the adjustment to disconnect signals there, since
> otherwise the innocuous looking:
> 
>   let vAdjust = scrollview.vscroll.adjustment;
>   vAdjust.connect('changed', Lang.bind(this, function () {
> this._onAdjustScopeChanged(tabData); }));
> 
> creates an uncollectable loop - the callback referenes the scope, the scope
> references the vAdjust, the vAdjust references the callback.

An alternative we can say here is that it should be slaved to the lifecycle of the main actor, in other words if we had:

Signals.connect_bounded ({ source: scrollview.vscroll.adjustment,
                           signal: 'changed',
                           destroyable: scrollview,
                           callback: Lang.bind(this, function () { this._onAdjustScopeChanged(tabData); }))

where "destroyable" is an object that has a "destroy" signal.
Comment 10 Owen Taylor 2009-10-09 20:22:26 UTC
(In reply to comment #8)
> (In reply to comment #7)
> > Review of attachment 145144 [details] [review] [details]:
> > 
> > Your patch on bug 597919 points out that StScrollBar should call
> > g_object_run_dispose() on the adjustment to disconnect signals there, since
> > otherwise the innocuous looking:
> > 
> >   let vAdjust = scrollview.vscroll.adjustment;
> >   vAdjust.connect('changed', Lang.bind(this, function () {
> > this._onAdjustScopeChanged(tabData); }));
> > 
> > creates an uncollectable loop - the callback referenes the scope, the scope
> > references the vAdjust, the vAdjust references the callback.
> 
> Hmm...the ScrollBar doesn't necessarily "own" the adjustments since someone
> could call st_scroll_bar_set_adjustment.  So is it still correct to dispose
> there?

I think we should consider st_scroll_bar_set_adjustment() like clutter_container_add_actor() ... as handing ownership of the adjustment to the scrollbar.

Someone has to be the ownership, and i if I look at the scroll view code, the scrollbar is the most natural candidate to "own" the adjustment - the scrollview gets the adjustements from the scrollbars and sets them on the child.

I don't like a "connect specially this way to prevent memory leaks" way of doing things, because then we're restricting not leaking to the domain of programmers who have some idea of what references and garbage collection are.

If we destroy too aggressively, it will lead to obvious bugs, and someone can always pull the adjustment out of the line of fire with scrollbar.set_adjustment(null); it can also be documented in the docs for st_scrollbar_set_adjustment().
Comment 11 Colin Walters 2009-10-09 20:39:13 UTC
(In reply to comment #10)
>
> I think we should consider st_scroll_bar_set_adjustment() like
> clutter_container_add_actor() ... as handing ownership of the adjustment to the
> scrollbar.
> 
> Someone has to be the ownership, and i if I look at the scroll view code, the
> scrollbar is the most natural candidate to "own" the adjustment - the
> scrollview gets the adjustements from the scrollbars and sets them on the
> child.

Fair enough, I'll attach a patch.

> I don't like a "connect specially this way to prevent memory leaks" way of
> doing things, because then we're restricting not leaking to the domain of
> programmers who have some idea of what references and garbage collection are.

Well, there are various different cases in this domain.  Often in the shell we can have four things involved; a ClutterActor subclass, some other GObject (might be a ClutterActor, might not), and a proxy object for each one.  Then we typically create a signal handler on the GObject.  

In the case where the lifecycle of one doesn't have any expected relation to the other (the AppIcon and MetaWindow case), we really do want some way to express that the signal handler should go away if either half does.  Dispose on the GObject will break the connection if the MetaWindow goes away earlier, but nothing by default will if the Actor is destroyed.

Actually rather than in Signals, I could imagine the Gjs-bound .connect having this functionality.

I guess it doesn't come up often, but when it does it's fairly insidious since one is used to just typing .connect everywhere.
Comment 12 Thomas Wood 2009-10-12 14:51:02 UTC
There is another issue with using clutter_actor_destroy(). It will run dispose on the actor, which will run the following code:

  /* avoid recursing when called from clutter_actor_destroy() */
  if (priv->parent_actor != NULL)
    {
      ClutterActor *parent = priv->parent_actor;

      if (CLUTTER_IS_CONTAINER (parent))
        clutter_container_remove_actor (CLUTTER_CONTAINER (parent), self);
      else
        priv->parent_actor = NULL;
    }

  /* parent should be gone */
  g_assert (priv->parent_actor == NULL);


Therefore, Widget cannot destroy the background and border image actors, because a sub class that implements ClutterContainer (such as Button) will cause the assertion to be reached, since the background and border image actors cannot be removed using clutter_container_remove_actor().
Comment 13 Colin Walters 2009-10-13 17:34:32 UTC
Well, I think the expected contract here is that for "implementation detail" widgets, you can get away with just _run_dispose () and unparent ().  

But for say a generic Box widget, we really need to call _destroy () on children.
Comment 14 Emmanuele Bassi (:ebassi) 2011-07-21 15:04:01 UTC
for internal children of actors implementing the Container interface, we have clutter_actor_push_internal()/pop_internal() since Clutter 1.2.
Comment 15 Jasper St. Pierre (not reading bugmail) 2011-12-04 20:35:21 UTC
Is there anything left to be done here?
Comment 16 Giovanni Campagna 2012-09-06 21:15:04 UTC
All St actors consistently call destroy on their children inside dispose (or better, have Clutter do it), so I don't think so.