garbage collection / reference cycles (cont.)

2 views
Skip to first unread message

Aaron Brady

unread,
Mar 25, 2009, 1:11:03 AM3/25/09
to
Hello,

I am posting the code I mentioned on Saturday that collects garbage
and cyclic garbage in a flattened two-step process. The code takes
122 lines incl. comments, with 100 in tests. It should be in a reply
to this.

My aim is a buffer-like object which can contain reference-counted
objects. This is a preliminary Python version of the cycle detector.
I expect to port it to C++, but the buffer object as well as object
proxies are Python objects. The memory management strategy,
synchronization, etc., are other modules. It is similar in principle
to Python's own 'gc'. If it's sound, it may have some educational and
explanatory value also.

Anyway, since I received a little interest in it, I wanted to follow
up. It is free to play with. If there's a better group to ask about
this, or there are more scholarly, widely-used, or thorough treatments
or implementations, I'm interested.

Aaron Brady

unread,
Mar 25, 2009, 1:12:30 AM3/25/09
to

from collections import deque

class Globals:
to_collect= deque() # FIFO of garbage that has been decref'ed;
# Queue them instead of nested 'gc' calls

to_collect_set= set() # hash lookup of the same information
ser_gc_running= False # bool flag if the GC is running

def schedule_collect( ob ):
''' Add to FIFO- no gc call '''
if ob in Globals.to_collect_set:
return
Globals.to_collect.append( ob )
Globals.to_collect_set.add( ob )

def serial_gc( ):
''' Visit objects which have been decref'ed. If they
have left reachability, enqueue the entire cycle they
are in; this as opposed to nested 'final' calls. '''
if Globals.ser_gc_running:
return
Globals.ser_gc_running= True

while Globals.to_collect:
ob= Globals.to_collect.popleft( )
Globals.to_collect_set.remove( ob )
if ob.ref_ct== 0:
ob.final( )
else:
incycle= Globals.cycle_detect( ob )
if incycle:
# Request object to drop its referenecs;
# re-queue the object. (Potential
# infinite loop, if objects do not comply.)
for k, v in list( ob.__dict__.items( ) ):
if not isinstance( v, ManagedOb ):
continue
ob.final_attr( k )
Globals.schedule_collect( ob )


Globals.ser_gc_running= False

def cycle_detect( ob ):
''' Detect an unreachable reference cycle in the
descendants of 'ob'. Return True if so, False if
still reachable. Only called when walking the
'to_collect' queue. '''
parents= { } # disjunction( ancestors, descendants )
bfs= deque( [ ob ] )
refct_copy= { ob: ob.ref_ct }
# copy the ref_ct's to a map;
# decrement the copies on visit (breadth-first)
while bfs:
x= bfs.popleft( )
for k, v in x.__dict__.items( ):
if not isinstance( v, ManagedOb ):
continue
if v not in refct_copy:
refct_copy[ v ]= v.ref_ct
bfs.append( v )
if v not in parents:
parents[ v ]= set( )
refct_copy[ v ]-= 1
parents[ v ].add( ( x, k ) )
print( 'parents', parents )
print( 'refct_copy', refct_copy )

# any extra-cyclic references?
if refct_copy[ ob ]:
return False

# (ancestors && descendants) all zero?
# --(breadth-first)
bfs= deque( [ ob ] )
visited= set( [ ob ] )
while bfs:
x= bfs.popleft( )
for n, _ in parents[ x ]:
if n in visited:
continue
if refct_copy[ n ]:
return False
visited.add( n )
bfs.append( n )
print( 'cycle of', ob, 'found' )
return True

class ManagedOb:
def __init__( self, name ):
self.ref_ct= 1
self.name= name
def assign( self, attr, other ):
''' setattr function (basically) '''
if hasattr( self, attr ):
getattr( self, attr ).decref( )
other.incref( )
setattr( self, attr, other )
def incref( self ):
self.ref_ct+= 1
def decref( self ):
print( self, 'decref' )
self.ref_ct-= 1
# check for cycles and poss. delete
Globals.schedule_collect( self )
Globals.serial_gc( ) # trip the collector
def final_attr( self, attr ):
''' magic function. your object has left
reachability and is requested to drop its
reference to 'attr'. '''
ob= getattr( self, attr )
delattr( self, attr )
ob.decref( )
def final( self ):
for _, v in self.__dict__.items( ):
if not isinstance( v, ManagedOb ):
continue
v.decref( )
print( 'finalizing', self )
def __repr__( self ):
return '<%s,%i>'% ( self.name, self.ref_ct )

def run( externals ):
''' create six global variables, which refer
to eachother in different shapes. only keep
references to those in the 'externals' string. '''
global p, q, r, s, t, u
print( )
p= ManagedOb( 'p' ) # created with reference count 1
q= ManagedOb( 'q' )
r= ManagedOb( 'r' )
s= ManagedOb( 's' )
t= ManagedOb( 't' )
u= ManagedOb( 'u' )

p.assign( 'at', q )
q.assign( 'at', p )
q.assign( 'at1', q )
q.assign( 'at2', r )
r.assign( 'at', q )
s.assign( 'at', p )
q.assign( 'at3', t )
u.assign( 'at', t )

# release references not in 'externals'
for x in p, q, r, s, t, u:
if x.name not in externals:
x.decref()

print( 'p: (q), q: (pqrt), r: (q), s: (p), t: (), u: (t)' )

def assert_exist( *args ):
for arg in args:
print( arg, 'exists' )
assert arg.ref_ct> 0

def assert_destroyed( *args ):
for arg in args:
print( arg, 'destroyed' )
assert arg.ref_ct== 0

run( 'psu' ) # external references to 'p', 's', and 'u'

p.decref() # decref 'p'. should not free any.

assert_exist( p, q, r, s, t, u )

run( 'psu' ) # start over

p.decref()

s.decref() # decref 'p' and 's'. should decref 'q', 'r',
# and 't'. should finalize 's', 'p', 'r', 'q'.

assert_exist( t, u )
assert_destroyed( p, q, r, s )

run( 'psu' )

s.decref()

p.decref() # same result, different order

assert_exist( t, u )
assert_destroyed( p, q, r, s )

run( 'psu' )

s.decref() # should finalize 's'.

assert_exist( p, q, r, t, u )
assert_destroyed( s )

run( 'qsu' ) # more.

q.decref()

assert_exist( p, q, r, s, t, u )

run( 'qsu' )

q.decref()

s.decref()

assert_exist( t, u )
assert_destroyed( p, q, r, s )

run( 'qsu' )

s.decref()

q.decref()

assert_exist( t, u )
assert_destroyed( p, q, r, s )

run( 'qsu' )

s.decref()

assert_exist( p, q, r, t, u )
assert_destroyed( s )

Aaron Brady

unread,
Mar 29, 2009, 3:06:53 AM3/29/09
to
On Mar 25, 12:11 am, Aaron Brady <castiro...@gmail.com> wrote:
> Hello,
>
> I am posting the code I mentioned on Saturday that collects garbage
> and cyclic garbage in a flattened two-step process.  The code takes
> 122 lines incl. comments, with 100 in tests.  It should be in a reply
> to this.
>
> My aim is a buffer-like object which can contain reference-counted
> objects.  This is a preliminary Python version of the cycle detector.

snip formality

Someone suggested that it wasn't clear to them what my goal was in
this post. I created a garbage collector that has an extra method
that user-defined objects don't have in Python's. It is 'final_attr',
which requests the objects to drop their reference to the specified
attr. After it returns, the object is moved to the back of the
collection queue. This means that it knows what references of its own
it is losing; they are still valid at the time 'final_attr' is called;
and other objects' references to /it/ are still valid too.

I want a technical discussion of its strengths and weaknesses.

Aahz suggested to try python-ideas:
http://mail.python.org/pipermail/python-ideas/2009-March/003774.html

Aaron Brady

unread,
Mar 29, 2009, 3:14:34 AM3/29/09
to
On Mar 25, 12:12 am, Aaron Brady <castiro...@gmail.com> wrote:
> On Mar 25, 12:11 am, Aaron Brady <castiro...@gmail.com> wrote:
> > Hello,
>
> > I am posting the code I mentioned on Saturday that collects garbage
> > and cyclic garbage in a flattened two-step process.  The code takes
> > 122 lines incl. comments, with 100 in tests.  It should be in a reply
> > to this.
snip

Here is the output. Someone suggested I add it. It may or may not be
utterly unintelligible. It's quite long, 367 lines.

>>> run( 'psu' ) # external references to 'p', 's', and 'u'

<q,4> decref
parents {<r,2>: {(<q,3>, 'at2')}, <p,3>: {(<q,3>, 'at')}, <q
,3>: {(<p,3>, 'at'), (<r,2>, 'at'), (<q,3>, 'at1')}, <t,3>:
{(<q,3>, 'at3')}}
refct_copy {<q,3>: 0, <r,2>: 1, <p,3>: 2, <t,3>: 2}
<r,2> decref
parents {<q,3>: {(<p,3>, 'at'), (<r,1>, 'at'), (<q,3>, 'at1'
)}, <r,1>: {(<q,3>, 'at2')}, <p,3>: {(<q,3>, 'at')}, <t,3>:
{(<q,3>, 'at3')}}
refct_copy {<r,1>: 0, <q,3>: 0, <p,3>: 2, <t,3>: 2}
<t,3> decref
parents {}
refct_copy {<t,2>: 2}


p: (q), q: (pqrt), r: (q), s: (p), t: (), u: (t)
>>>

>>> p.decref() # decref 'p'. should not free any.

<p,3> decref
parents {<q,3>: {(<r,1>, 'at'), (<p,2>, 'at'), (<q,3>, 'at1'
)}, <r,1>: {(<q,3>, 'at2')}, <p,2>: {(<q,3>, 'at')}, <t,2>:
{(<q,3>, 'at3')}}
refct_copy {<p,2>: 1, <q,3>: 0, <r,1>: 0, <t,2>: 1}


>>>
>>> assert_exist( p, q, r, s, t, u )

<p,2> exists
<q,3> exists
<r,1> exists
<s,1> exists
<t,2> exists
<u,1> exists


>>>
>>> run( 'psu' ) # start over

<q,4> decref
parents {<r,2>: {(<q,3>, 'at2')}, <p,3>: {(<q,3>, 'at')}, <t
,3>: {(<q,3>, 'at3')}, <q,3>: {(<p,3>, 'at'), (<r,2>, 'at'),
(<q,3>, 'at1')}}
refct_copy {<q,3>: 0, <r,2>: 1, <p,3>: 2, <t,3>: 2}
<r,2> decref
parents {<q,3>: {(<p,3>, 'at'), (<r,1>, 'at'), (<q,3>, 'at1'
)}, <r,1>: {(<q,3>, 'at2')}, <p,3>: {(<q,3>, 'at')}, <t,3>:
{(<q,3>, 'at3')}}
refct_copy {<r,1>: 0, <q,3>: 0, <p,3>: 2, <t,3>: 2}
<t,3> decref
parents {}
refct_copy {<t,2>: 2}


p: (q), q: (pqrt), r: (q), s: (p), t: (), u: (t)
>>>

>>> p.decref()
<p,3> decref
parents {<q,3>: {(<r,1>, 'at'), (<p,2>, 'at'), (<q,3>, 'at1'
)}, <r,1>: {(<q,3>, 'at2')}, <p,2>: {(<q,3>, 'at')}, <t,2>:
{(<q,3>, 'at3')}}
refct_copy {<p,2>: 1, <q,3>: 0, <t,2>: 1, <r,1>: 0}


>>>
>>> s.decref() # decref 'p' and 's'. should decref 'q', 'r'
,

<s,1> decref
<p,2> decref
finalizing <s,0>
parents {<q,3>: {(<r,1>, 'at'), (<p,1>, 'at'), (<q,3>, 'at1'
)}, <r,1>: {(<q,3>, 'at2')}, <p,1>: {(<q,3>, 'at')}, <t,2>:
{(<q,3>, 'at3')}}
refct_copy {<p,1>: 0, <q,3>: 0, <t,2>: 1, <r,1>: 0}
cycle of <p,1> found
<q,3> decref
parents {<r,1>: {(<q,2>, 'at2')}, <p,1>: {(<q,2>, 'at')}, <t
,2>: {(<q,2>, 'at3')}, <q,2>: {(<r,1>, 'at'), (<q,2>, 'at1')
}}
refct_copy {<q,2>: 0, <r,1>: 0, <p,1>: 0, <t,2>: 1}
cycle of <q,2> found
<r,1> decref
<p,1> decref
<q,2> decref
<t,2> decref
finalizing <p,0>
<q,1> decref
finalizing <r,0>
finalizing <q,0>
parents {}
refct_copy {<t,1>: 1}


>>> # and 't'. should finalize 's', 'p', 'r', 'q
'.

...
>>> assert_exist( t, u )
<t,1> exists
<u,1> exists


>>> assert_destroyed( p, q, r, s )

<p,0> destroyed
<q,0> destroyed
<r,0> destroyed
<s,0> destroyed
>>>
>>> run( 'psu' )

<q,4> decref
parents {<r,2>: {(<q,3>, 'at2')}, <p,3>: {(<q,3>, 'at')}, <q
,3>: {(<p,3>, 'at'), (<r,2>, 'at'), (<q,3>, 'at1')}, <t,3>:
{(<q,3>, 'at3')}}
refct_copy {<q,3>: 0, <r,2>: 1, <t,3>: 2, <p,3>: 2}
<r,2> decref
parents {<q,3>: {(<p,3>, 'at'), (<r,1>, 'at'), (<q,3>, 'at1'
)}, <r,1>: {(<q,3>, 'at2')}, <t,3>: {(<q,3>, 'at3')}, <p,3>:
{(<q,3>, 'at')}}
refct_copy {<r,1>: 0, <q,3>: 0, <t,3>: 2, <p,3>: 2}
<t,3> decref
parents {}
refct_copy {<t,2>: 2}


p: (q), q: (pqrt), r: (q), s: (p), t: (), u: (t)
>>>

>>> s.decref()
<s,1> decref
<p,3> decref
finalizing <s,0>
parents {<q,3>: {(<r,1>, 'at'), (<p,2>, 'at'), (<q,3>, 'at1'
)}, <r,1>: {(<q,3>, 'at2')}, <t,2>: {(<q,3>, 'at3')}, <p,2>:
{(<q,3>, 'at')}}
refct_copy {<p,2>: 1, <q,3>: 0, <r,1>: 0, <t,2>: 1}


>>>
>>> p.decref() # same result, different order

<p,2> decref
parents {<q,3>: {(<r,1>, 'at'), (<p,1>, 'at'), (<q,3>, 'at1'
)}, <r,1>: {(<q,3>, 'at2')}, <t,2>: {(<q,3>, 'at3')}, <p,1>:
{(<q,3>, 'at')}}
refct_copy {<p,1>: 0, <q,3>: 0, <r,1>: 0, <t,2>: 1}
cycle of <p,1> found
<q,3> decref
parents {<r,1>: {(<q,2>, 'at2')}, <p,1>: {(<q,2>, 'at')}, <q
,2>: {(<r,1>, 'at'), (<q,2>, 'at1')}, <t,2>: {(<q,2>, 'at3')
}}
refct_copy {<q,2>: 0, <r,1>: 0, <t,2>: 1, <p,1>: 0}
cycle of <q,2> found
<r,1> decref
<p,1> decref
<q,2> decref
<t,2> decref
finalizing <p,0>
<q,1> decref
finalizing <r,0>
finalizing <q,0>
parents {}
refct_copy {<t,1>: 1}
>>>
>>> assert_exist( t, u )
<t,1> exists
<u,1> exists


>>> assert_destroyed( p, q, r, s )

<p,0> destroyed
<q,0> destroyed
<r,0> destroyed
<s,0> destroyed
>>>
>>> run( 'psu' )

<q,4> decref
parents {<r,2>: {(<q,3>, 'at2')}, <p,3>: {(<q,3>, 'at')}, <q
,3>: {(<p,3>, 'at'), (<r,2>, 'at'), (<q,3>, 'at1')}, <t,3>:
{(<q,3>, 'at3')}}
refct_copy {<q,3>: 0, <r,2>: 1, <t,3>: 2, <p,3>: 2}
<r,2> decref
parents {<q,3>: {(<p,3>, 'at'), (<r,1>, 'at'), (<q,3>, 'at1'
)}, <r,1>: {(<q,3>, 'at2')}, <t,3>: {(<q,3>, 'at3')}, <p,3>:
{(<q,3>, 'at')}}
refct_copy {<r,1>: 0, <q,3>: 0, <t,3>: 2, <p,3>: 2}
<t,3> decref
parents {}
refct_copy {<t,2>: 2}


p: (q), q: (pqrt), r: (q), s: (p), t: (), u: (t)
>>>

>>> s.decref() # should finalize 's'.

<s,1> decref
<p,3> decref
finalizing <s,0>
parents {<q,3>: {(<r,1>, 'at'), (<p,2>, 'at'), (<q,3>, 'at1'
)}, <r,1>: {(<q,3>, 'at2')}, <t,2>: {(<q,3>, 'at3')}, <p,2>:
{(<q,3>, 'at')}}
refct_copy {<p,2>: 1, <q,3>: 0, <r,1>: 0, <t,2>: 1}


>>>
>>> assert_exist( p, q, r, t, u )

<p,2> exists
<q,3> exists
<r,1> exists
<t,2> exists
<u,1> exists
>>> assert_destroyed( s )
<s,0> destroyed


>>>
>>> run( 'qsu' ) # more.

<p,3> decref
parents {<q,4>: {(<r,2>, 'at'), (<p,2>, 'at'), (<q,4>, 'at1'
)}, <r,2>: {(<q,4>, 'at2')}, <p,2>: {(<q,4>, 'at')}, <t,3>:
{(<q,4>, 'at3')}}
refct_copy {<p,2>: 1, <q,4>: 1, <r,2>: 1, <t,3>: 2}
<r,2> decref
parents {<q,4>: {(<p,2>, 'at'), (<r,1>, 'at'), (<q,4>, 'at1'
)}, <r,1>: {(<q,4>, 'at2')}, <p,2>: {(<q,4>, 'at')}, <t,3>:
{(<q,4>, 'at3')}}
refct_copy {<r,1>: 0, <q,4>: 1, <p,2>: 1, <t,3>: 2}
<t,3> decref
parents {}
refct_copy {<t,2>: 2}


p: (q), q: (pqrt), r: (q), s: (p), t: (), u: (t)
>>>

>>> q.decref()
<q,4> decref
parents {<r,1>: {(<q,3>, 'at2')}, <p,2>: {(<q,3>, 'at')}, <q
,3>: {(<p,2>, 'at'), (<r,1>, 'at'), (<q,3>, 'at1')}, <t,2>:
{(<q,3>, 'at3')}}
refct_copy {<q,3>: 0, <r,1>: 0, <p,2>: 1, <t,2>: 1}


>>>
>>> assert_exist( p, q, r, s, t, u )

<p,2> exists
<q,3> exists
<r,1> exists
<s,1> exists
<t,2> exists
<u,1> exists
>>>
>>> run( 'qsu' )

<p,3> decref
parents {<q,4>: {(<r,2>, 'at'), (<p,2>, 'at'), (<q,4>, 'at1'
)}, <r,2>: {(<q,4>, 'at2')}, <p,2>: {(<q,4>, 'at')}, <t,3>:
{(<q,4>, 'at3')}}
refct_copy {<p,2>: 1, <q,4>: 1, <t,3>: 2, <r,2>: 1}
<r,2> decref
parents {<q,4>: {(<p,2>, 'at'), (<r,1>, 'at'), (<q,4>, 'at1'
)}, <r,1>: {(<q,4>, 'at2')}, <p,2>: {(<q,4>, 'at')}, <t,3>:
{(<q,4>, 'at3')}}
refct_copy {<r,1>: 0, <q,4>: 1, <p,2>: 1, <t,3>: 2}
<t,3> decref
parents {}
refct_copy {<t,2>: 2}


p: (q), q: (pqrt), r: (q), s: (p), t: (), u: (t)
>>>

>>> q.decref()
<q,4> decref
parents {<r,1>: {(<q,3>, 'at2')}, <p,2>: {(<q,3>, 'at')}, <t
,2>: {(<q,3>, 'at3')}, <q,3>: {(<p,2>, 'at'), (<r,1>, 'at'),
(<q,3>, 'at1')}}
refct_copy {<q,3>: 0, <r,1>: 0, <p,2>: 1, <t,2>: 1}
>>>
>>> s.decref()
<s,1> decref
<p,2> decref
finalizing <s,0>
parents {<q,3>: {(<r,1>, 'at'), (<p,1>, 'at'), (<q,3>, 'at1'
)}, <r,1>: {(<q,3>, 'at2')}, <p,1>: {(<q,3>, 'at')}, <t,2>:
{(<q,3>, 'at3')}}
refct_copy {<p,1>: 0, <q,3>: 0, <t,2>: 1, <r,1>: 0}
cycle of <p,1> found
<q,3> decref
parents {<r,1>: {(<q,2>, 'at2')}, <p,1>: {(<q,2>, 'at')}, <t
,2>: {(<q,2>, 'at3')}, <q,2>: {(<r,1>, 'at'), (<q,2>, 'at1')
}}
refct_copy {<q,2>: 0, <r,1>: 0, <p,1>: 0, <t,2>: 1}
cycle of <q,2> found
<r,1> decref
<p,1> decref
<q,2> decref
<t,2> decref
finalizing <p,0>
<q,1> decref
finalizing <r,0>
finalizing <q,0>
parents {}
refct_copy {<t,1>: 1}
>>>
>>> assert_exist( t, u )
<t,1> exists
<u,1> exists


>>> assert_destroyed( p, q, r, s )

<p,0> destroyed
<q,0> destroyed
<r,0> destroyed
<s,0> destroyed
>>>
>>> run( 'qsu' )

<p,3> decref
parents {<q,4>: {(<r,2>, 'at'), (<p,2>, 'at'), (<q,4>, 'at1'
)}, <r,2>: {(<q,4>, 'at2')}, <p,2>: {(<q,4>, 'at')}, <t,3>:
{(<q,4>, 'at3')}}
refct_copy {<p,2>: 1, <q,4>: 1, <t,3>: 2, <r,2>: 1}
<r,2> decref
parents {<q,4>: {(<p,2>, 'at'), (<r,1>, 'at'), (<q,4>, 'at1'
)}, <r,1>: {(<q,4>, 'at2')}, <p,2>: {(<q,4>, 'at')}, <t,3>:
{(<q,4>, 'at3')}}
refct_copy {<r,1>: 0, <q,4>: 1, <p,2>: 1, <t,3>: 2}
<t,3> decref
parents {}
refct_copy {<t,2>: 2}


p: (q), q: (pqrt), r: (q), s: (p), t: (), u: (t)
>>>

>>> s.decref()
<s,1> decref
<p,2> decref
finalizing <s,0>
parents {<q,4>: {(<r,1>, 'at'), (<p,1>, 'at'), (<q,4>, 'at1'
)}, <r,1>: {(<q,4>, 'at2')}, <p,1>: {(<q,4>, 'at')}, <t,2>:
{(<q,4>, 'at3')}}
refct_copy {<p,1>: 0, <q,4>: 1, <t,2>: 1, <r,1>: 0}
>>>
>>> q.decref()
<q,4> decref
parents {<r,1>: {(<q,3>, 'at2')}, <p,1>: {(<q,3>, 'at')}, <q
,3>: {(<p,1>, 'at'), (<r,1>, 'at'), (<q,3>, 'at1')}, <t,2>:
{(<q,3>, 'at3')}}
refct_copy {<q,3>: 0, <r,1>: 0, <p,1>: 0, <t,2>: 1}
cycle of <q,3> found
<r,1> decref
<p,1> decref
<q,3> decref
<t,2> decref
<q,2> decref
finalizing <r,0>
<q,1> decref
finalizing <p,0>
finalizing <q,0>
parents {}
refct_copy {<t,1>: 1}
>>>
>>> assert_exist( t, u )
<t,1> exists
<u,1> exists


>>> assert_destroyed( p, q, r, s )

<p,0> destroyed
<q,0> destroyed
<r,0> destroyed
<s,0> destroyed
>>>
>>> run( 'qsu' )

<p,3> decref
parents {<q,4>: {(<r,2>, 'at'), (<p,2>, 'at'), (<q,4>, 'at1'
)}, <r,2>: {(<q,4>, 'at2')}, <p,2>: {(<q,4>, 'at')}, <t,3>:
{(<q,4>, 'at3')}}
refct_copy {<p,2>: 1, <q,4>: 1, <r,2>: 1, <t,3>: 2}
<r,2> decref
parents {<q,4>: {(<p,2>, 'at'), (<r,1>, 'at'), (<q,4>, 'at1'
)}, <r,1>: {(<q,4>, 'at2')}, <p,2>: {(<q,4>, 'at')}, <t,3>:
{(<q,4>, 'at3')}}
refct_copy {<r,1>: 0, <q,4>: 1, <p,2>: 1, <t,3>: 2}
<t,3> decref
parents {}
refct_copy {<t,2>: 2}


p: (q), q: (pqrt), r: (q), s: (p), t: (), u: (t)
>>>

>>> s.decref()
<s,1> decref
<p,2> decref
finalizing <s,0>
parents {<q,4>: {(<r,1>, 'at'), (<p,1>, 'at'), (<q,4>, 'at1'
)}, <r,1>: {(<q,4>, 'at2')}, <p,1>: {(<q,4>, 'at')}, <t,2>:
{(<q,4>, 'at3')}}
refct_copy {<p,1>: 0, <q,4>: 1, <r,1>: 0, <t,2>: 1}


>>>
>>> assert_exist( p, q, r, t, u )

<p,1> exists
<q,4> exists
<r,1> exists
<t,2> exists
<u,1> exists
>>> assert_destroyed( s )
<s,0> destroyed

Reply all
Reply to author
Forward
0 new messages