Follow

Keep Up to Date with the Most Important News

By pressing the Subscribe button, you confirm that you have read and are agreeing to our Privacy Policy and Terms of Use
Contact

When does exception handling unexpectedly influence object lifetimes?

The Python reference on the data model notes that

catching an exception with a ‘try…except’ statement may keep objects alive.

It seems rather obvious that exceptions change control flow, potentially leading to different objects remaining referenced. Why is it explicitly mentioned? Is there a potential for memory leaks here?

MEDevel.com: Open-source for Healthcare and Education

Collecting and validating open-source software for healthcare, education, enterprise, development, medical imaging, medical records, and digital pathology.

Visit Medevel

>Solution :

An exception stores a traceback, which stores all child frames ("function calls") between raising and excepting. Frames reference all local names and their values, preventing the garbage collection of local names and values.

This means that an exception handler should promptly finish handling exceptions to allow child locals to be cleaned up. Still, a function cannot rely on its locals being collectable immediately after the function ends.

As a result, patterns such as RAII are not reliable to be prompt even on reference counted implementations. When prompt cleanup is required, objects should provide a means for explicit cleanup (for use in finally blocks) or preferably automatic cleanup (for use in with blocks).

Objects, values and types

[…]
Programs are strongly recommended to explicitly close such objects. The ‘tryfinally’ statement and the ‘with’ statement provide convenient ways to do this.


One can observe this with a class that marks when it is garbage collected.

class Collectible:
    def __init__(self, name):
        self.name = name
    def __del__(self, print=print):
        print("Collecting", self.name)

def inner():
    local_name = Collectible("inner local value")
    raise RuntimeError("This is a drill")

def outer():
    local_name = Collectible("outer local value")
    inner()

try:
    outer()
except RuntimeError as e:
    print(f"handling a {type(e).__name__}: {e}")

On CPython, the output shows that the handler runs before the locals are collected:

handling a RuntimeError: This is a drill
Collecting inner local value
Collecting outer local value

Note that CPython uses reference counting, which already leads to quick cleanup as soon as possible. Other implementations may further and arbitrarily delay cleanup.

Add a comment

Leave a Reply

Keep Up to Date with the Most Important News

By pressing the Subscribe button, you confirm that you have read and are agreeing to our Privacy Policy and Terms of Use

Discover more from Dev solutions

Subscribe now to keep reading and get access to the full archive.

Continue reading