Hi,

This topic has been on the mailing list before, but I think I traced it.

It has to do with Python objects which are not referenced on a Python level, but 
indirectly by GObject's (such as gtk.Container).  It only occurs when there is some 
sort of cyclic reference, since that's where the GC comes into play. The GC identifies 
the cycle and (since the Python objects reference the GObjecs and not visa versa) 
concludes that no-one refers to the objects in the cycle. The it calls "tp_clear" on 
the objects order to break the cycle. It does that on the PyGObject (which causes all 
signals to be invalidated), on their __dict__ objects (which cause the dicts to be 
cleared), etc.

You can read the documentation in the "attachment". It describes the problem pretty 
well IMHO.

I think that the solution would be to not traverse the elements as long as the 
refcount of the GObject > 1 (a bit like tp_dealloc works). NB. this assumes that there 
will be no cyclic references on the GObject level.

Regards,

Arjan

(I'm using a nasty web-mail client, so I add the attachment here)

# vim:sw=4
"""
Demonstrate problem with PyGObject and the Python GC.

The problem can be described as:
- we have a GObject 'win' and a GObject 'l', and their Python wrappers.
- 'win' and 'l' are connected to each other on a GObject level
  (such as gtk_container_add() for example).
- Now, we create a cyclic reference which includes 'l' (the GC is used to
  unwrap cyclic references). In this example this is done with the simple
  Python instance 'a' of class A.
- Now we can remove our references to 'l' and 'a'. Since 'l' references 'a'
  and visa versa, there is no need to need to add an extra reference on 'l'..
- The garbage collector examines our objects and sees 'l' and 'a' being
  connected to each other and cyclic. No relation is found with class 'win'..
- 'l', 'a' and their dictionaries (l.__dict__) are set for garbage collection.
  Their 'tp_clear' functions are invoked:
  - 'l' clears its connected signals
  - 'a' has nothing to do
  - the dictionaries of both 'a' and 'l' are cleared (causing 'a' to be
    destroyed)

Case 1 (l refs a, a refs l):
                     +------------+  (our cycle)
Python:              V            |
  ,-----.         ,-----.      ,-----.
  | win |         |  l  |----->|  a  |
  '-----'         '-----'      '-----'
-----|---------------|------------------
GTK: V               V
  ,-----.         ,-----.
  | win |-------->|  l  | (GtkLabel)
  '-----'         '-----'

Case 2 (l refs itself):
                     +------+  (our cycle)
Python:              V      |
  ,-----.         ,-----.   |
  | win |         |  l  |---+
  '-----'         '-----'
-----|---------------|------------------
GTK: V               V
  ,-----.         ,-----.
  | win |-------->|  l  | (GtkLabel)
  '-----'         '-----'

Case 3 (l is ref'ed by the GObject and itself, this is not a problem):
                     +------+  (our cycle)
Python:              V      |
  ,-----.         ,-----.   |
  | win |         |  l  |---+
  '-----'         '-----'
-----|---------------^------------------
GTK: V               |
  ,-----.         ,-----.
  | win |-------->|  l  | (GtkLabel)
  '-----'         '-----'

Possible solutions:
1. Create a special dict class that does not clear if the GObject has more than
   one reference.
2. Change the tp_traverse function
3. Change the tp_clear function (implies 1.)
"""
import pygtk
pygtk.require('2.0')

import gtk, weakref, gc, sys

class A:
    pass

# Case 1:
print 'Case 1'

win = gtk.Window()

a = A()
l = gtk.Label('Hello')
assert sys.getrefcount(l) == 2
assert l.__grefcount__ == 1
win.add(l)
assert sys.getrefcount(l) == 2
assert l.__grefcount__ == 2

# Make the reference cyclic:
l.a = a
a.l = l
assert sys.getrefcount(a) == 3
assert sys.getrefcount(l) == 3
assert l.__grefcount__ == 2

weak_l = weakref.ref(l)
weak_a = weakref.ref(a)

assert len(l.__dict__.values()) > 0
del l, a

assert sys.getrefcount(weak_a()) == 2
assert sys.getrefcount(weak_l()) == 2
assert weak_l().__grefcount__ == 2

gc.collect()

assert weak_a() == None
# Gobject.tp_dealloc saves our 'l',
assert weak_l() != None
assert sys.getrefcount(weak_l()) == 2
assert weak_l().__grefcount__ == 1
# ...the __dict__ is cleared though. (shouldn't happen)
assert len(weak_l().__dict__.values()) == 0


# Case 2:
print 'Case 2'

win = gtk.Window()
l = gtk.Label('Hello')
win.add(l)

# create a cyclic reference:
l.l = l

assert sys.getrefcount(l) == 3
assert l.__grefcount__ == 2

weak_l = weakref.ref(l)
assert len(l.__dict__.values()) > 0
del l

gc.collect()

# Gobject.tp_dealloc saves our 'l',
assert weak_l() != None
assert sys.getrefcount(weak_l()) == 2
assert weak_l().__grefcount__ == 1
# ...the __dict__ is cleared though. (shouldn't happen!)
assert len(weak_l().__dict__.values()) == 0


# Case 3:
print 'Case 3'

win = gtk.Window()
l = gtk.Label('Hello')
win.add(l)

assert sys.getrefcount(l) == 2
assert l.__grefcount__ == 2

weak_l = weakref.ref(l)
assert len(l.__dict__.values()) == 0
del l

assert sys.getrefcount(weak_l()) == 2
assert weak_l().__grefcount__ == 1

# create a cyclic reference:
weak_l().l = weak_l()

gc.collect()

# Gobject.tp_dealloc saves our 'l',
assert weak_l() != None
assert sys.getrefcount(weak_l()) == 3
assert weak_l().__grefcount__ == 1
# ...the __dict__ is not cleared.
assert len(weak_l().__dict__.values()) > 0

print 'done'






_______________________________________________
pygtk mailing list   [EMAIL PROTECTED]
http://www.daa.com.au/mailman/listinfo/pygtk
Read the PyGTK FAQ: http://www.async.com.br/faq/pygtk/

Reply via email to