Why doesn't module finalization delete names as expected?

Philippe Proulx eeppeliteloop at gmail.com
Wed Feb 1 14:28:27 EST 2017


I'm developing a C module with the help of SWIG. My library manages
objects with reference counting, much like Python, except that it's
deterministic: there's no GC. I'm using Python 3.5.2.

I create two Python objects like this:

    bakery = Bakery()
    bread = bakery.create_bread()

Behind the scenes, the situation looks like this:

                                       +--------------------+
                                       | UserBread obj (Py) |
                                       +----------^---+-----+
                                                  |   :
                                                  |   :
                                                  |   :
    +------------------+                +---------+---V---+
    | Bakery obj (lib) <----------------+ Bread obj (lib) |
    +--------^---+-----+                +--------^--------+
             |   :                               |
             |   :                               |
    +--------+---V----+                 +--------+-------+
    | Bakery obj (Py) |                 | Bread obj (Py) |
    +---------^-------+                 +------^---------+
              |                                |
              |                                |
              +                                +
           bakery                            bread

A pipe link means one "strong" reference and a colon link means one
borrowed/weak reference.

I have some ownership inversion magic for the Bakery lib. and Python
objects to always coexist.

So here it's pretty clear what can happen. I don't know which reference
gets deleted first, but let's assume it's `bakery`. Then the situation
looks like this:

                                       +--------------------+
                                       | UserBread obj (Py) |
                                       +----------^---+-----+
                                                  |   :
                                                  |   :
                                                  |   :
    +------------------+                +---------+---V---+
    | Bakery obj (lib) <----------------+ Bread obj (lib) |
    +--------^---+-----+                +--------^--------+
             :   |                               |
             :   |                               |
    +--------+---V----+                 +--------+-------+
    | Bakery obj (Py) |                 | Bread obj (Py) |
    +-----------------+                 +------^---------+
                                               |
                                               |
                                               +
                                             bread

The Bakery Python object's __del__() drops the reference to its library
object, but first its reference count is incremented during this call
(so it's not really destroyed) and it's marked as only owned by the
library object from now on.

When `bread` gets deleted:

1. The Bread Python object's __del__() method gets called: the reference
   to its library object is dropped.
2. The Bread library object's destroy function drops its reference to
   the Bakery library object.
3. The Bakery library object's destroy function drops its reference to
   the Bakery Python object.
4. The Bakery Python object's __del__() method does nothing this time,
   since the object is marked as only owned by its library object
   (inverted ownership).
5. The Bread library object's destroy function then drops its reference
   to the UserBread Python object.

In the end, everything is correctly destroyed and released. This also
works if `bread` is deleted before `bakery`.

My problem is that this works as expected when used like this:

    def func():
        bakery = Bakery()
        bread = bakery.create_bread()

    if __name__ == '__main__':
        func()

but NOTHING is destroyed when used like this:

    bakery = Bakery()
    bread = bakery.create_bread()

That is, directly during the module's initialization. It works, however,
if I delete `bread` manually:

    bakery = Bakery()
    bread = bakery.create_bread()
    del bread

It also works with `bakery` only:

    bakery = Bakery()

My question is: what could explain this?

My guess is that my logic is correct since it works fine in the function
call situation.

It feels like `bread` is never deleted in the module initialization
situation, but I don't know why: the only reference to the Bread Python
object is this `bread` name in the module... what could prevent this
object's __del__() method to be called? It works when I call `del bread`
manually: I would expect that the module finalization does the exact
same thing?

Am I missing anything?

Thanks,
Phil



More information about the Python-list mailing list