1

PyCapsule_New accepts a destructor function, which is called when the capsule is destroyed:

PyObject* PyCapsule_New(void *pointer, const char *name, PyCapsule_Destructor destructor)

I am trying to use this mechanism to pass ownership of an object created by C++ code to Python. Specifically, the destructor simply calls "delete" for the object.

auto ptr = make_unique<ObjType>(arg);
PyObject * ret = PyCapsule_New(ptr.release(), nullptr, Destroyer);

void Destroyer(PyObject *capsule)
{
    auto rawPtr = static_cast<ObjType*>(PyCapsule_GetPointer(capsule, nullptr));
    delete rawPtr;
}

It seems to me there is a potential memory leak here: If the PyCapsule_New fails, the released raw pointer becomes dangling. I tried to get confirmation from Python C API document. However, it only mentions that upon failure, an exception is set, and a NULL is returned. It doesn't talk about the ownership.

It seems reasonable to assume the pointer will be dangling, because if the capsule is not generated in the first place, there is no handler to be passed to the destructor.

However, I am not sure if PyCapsule_New internally calls the destructor or not, specifically:

  • Inside PyCapsule_New, the capsule construction is almost complete.
  • A failure happens just before it returns.
  • PyCapsule_New sets an exception, returns NULL, after calling the destructor (???)

If the highlighted part is never going to happen, it seems to me the above code will have to be re-written as

auto ptr = make_unique<ObjType>(arg);
PyObject * ret = PyCapsule_New(ptr.get(), nullptr, Destroyer);
if (ret != nullptr)
    ptr.release();

Could someone please help confirm if that's the case?

L Song
  • 121
  • 4
  • Found the answer from source code here: https://github.com/python/cpython/blob/master/Objects/capsule.c#L44 . PyCapsule_New doesn't call the destroyer. Therefore, the pointer should only be released when the result is successful. – L Song Mar 15 '17 at 22:19
  • You should add an answer for this yourself. Not a comment. – tynn Mar 18 '17 at 08:38

1 Answers1

0

Changing comment to answer, as suggested.

Short answer: No, when PyCapsule_New fails, it does not call the destroyer.

See implementation in https://github.com/python/cpython/blob/master/Objects/capsule.c#L44

PyObject *
PyCapsule_New(void *pointer, const char *name, PyCapsule_Destructor destructor)
{
    PyCapsule *capsule;

    if (!pointer) {
        PyErr_SetString(PyExc_ValueError, "PyCapsule_New called with null pointer");
        return NULL;
    }

    capsule = PyObject_NEW(PyCapsule, &PyCapsule_Type);
    if (capsule == NULL) {
        return NULL;
    }

    capsule->pointer = pointer;
    capsule->name = name;
    capsule->context = NULL;
    capsule->destructor = destructor;

    return (PyObject *)capsule;
}

As a result, the first implementation does contain potential memory leak. "release()" should only be called when PyCapsule_New is successful.

L Song
  • 121
  • 4