[Python-Dev] Provoking Jim's MRO segfault before shutdown

Tim Peters tim at zope.com
Thu Nov 13 01:44:34 EST 2003

The following program provokes a segfault before shutdown in a release
build, or, in a debug build, triggers

    Assertion failed: mro != NULL, file C:\Code\python\Objects\object.c,
                                   line 1225

This is on current 2.4 trunk, so includes the fix checked in on Wednesday
for "Thomas Heller's bug".

In the "it figures" department:  I was never able to provoke Jim's problem
on purpose.  I was trying to provoke a different failure here, and never got
to the point of finishing the code for that purpose.  Heh.

import gc
import weakref

alist = []

class J(object):

class II(object):
    __slots__ = 'J', 'wr'

    def resurrect(self, ignore):

I = II()
J.I = I
I.J = J
I.wr = weakref.ref(J, I.resurrect)

del I, J, II

print alist

It's trying to resolve self.J in the callback at the time it dies.  Unlike
Jim's scenario, the failure here is due to that II is an insane state (the
class containing the callback code, not some other class) -- but close
enough for me.

I doubt the __slots__ declaration is necessary, but it *is* necessary for II
to be a new-style class.  If you make II an old-style class instead, you get
a different surprise in the callback:  because tp_clear has already been
called on I too the way things work today, and old-style classes look in the
instance dict first, the attempt to reference self.J raises AttributeError.
There's no way to guess that might happen from staring at the Python code,
though (and remember that this is before shutdown!  we're all too eager to
overlook shutdown failures, but even if we weren't this one is just a result
of regular garbage collection while the interpreter and all modules are in
perfect shape).

The suggested approach in the long earlier email should repair both the
segfault and the AttributeError-out-of-thin-air surprises.  It would instead
result in J's resurrection (with J wholly intact; and I and II would also
resurrect, since J has a strong reference to I, and I to II).  The specific
invocation of gc in which this occurred wouldn't be able to collect anything
(at all, even if there were a million other objects in vanilla trash cycles
at the time -- they wouldn't get collected until a later run of gc, one that
didn't resurrect dead cycles).

More information about the Python-Dev mailing list