When, why and how to use SafePyObjects? How do they differ from THPObjectPtr?

When, why and how to use SafePyObjects? How do they differ from THPObjectPtr?

I was recently reminded that SafePyObjects exists by @albanD while working on a Python refcounting bug during BE week. Posting here since it seems like a useful utility in case others find it useful.

The bug: Previously we stored stack of saved tensor hooks on the TLS as plain PyObject* and manually managing the refcount ourselves, and due to some missed refcounting logic, this lead to potential leaks and a reported segfault (issue).

What is SafePyObject: Wrapping objects in SafePyObjects is quite similar to THPObjectPtr in that it is a RAII-style owning pointer to PyObject* (NB: copy constructor support is landing soon!).

Relative to THPObjectPtr , it is more “heavy-weight” but is more broadly applicable:

  • Incref/decref can be properly triggered while in code where we shouldn’t have a direct dependency on the Python intepreter like aten
  • We automatically take out the GIL, so it can be used a field on objects.

For SafePyObject, incref/decref can be properly triggered in places like aten due to the PyIntepreter infra (see c10/core/impl/PyInterpreter.h for more info), a class that virtualizes these python-specific methods, adding some indirection to avoid the direct dependency.

Historically the PyIntepreter infra was built for multipy support, where multiple copies of Python can be loaded. In this world, a plain PyObject* pointer is not sufficient to determine which copy of Python this object belongs to.

With the PyInterpreter infra, one PyInterpreter is allocated for each copy of python, and to invoke an operation like decref, the user must explicitly provide the PyInterpreter.

SafePyObject is able to incref/decref because it also stores PyInterpreter* as a field, which upon construction the user must pass in addition to the PyObject. This means that you can only create SafePyObjects in code where python is guaranteed to exist and the current interpreter is known via getPyInterpreter().

1 Like