Skip to content

Commit 341a343

Browse files
committed
Make instance_essentials hold references to patients
This is a first attempt to address pybind#856. There is still a bit of work to do (e.g., some tests, and double-checking that I'm not leaking references). At this point I'm looking for feedback on whether the idea is sound and would be accepted. Instead of the weakref trick, every pybind-managed object holds a (Python) list of references, which gets cleared in `clear_instance`. The list is only constructed the first time it is needed. The one downside I can see is that every pybind object will get bigger by `sizeof(PyObject *)`, even if no keep_alive is used. On the other hand, it should significantly reduce the overhead of using keep_alive, since there is no need to construct a weakref object and a callback object. One thing I'm not sure about is whether the call to `Py_CLEAR(instance->patients);` belongs inside or outside the `has_value` test. At the moment it's inside, but that's cargo culting rather than from an understanding of the conditions under which an instance might not have a value.
1 parent 6d2411f commit 341a343

File tree

3 files changed

+34
-6
lines changed

3 files changed

+34
-6
lines changed

include/pybind11/class_support.h

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -319,6 +319,8 @@ inline void clear_instance(PyObject *self) {
319319
PyObject **dict_ptr = _PyObject_GetDictPtr(self);
320320
if (dict_ptr)
321321
Py_CLEAR(*dict_ptr);
322+
323+
Py_CLEAR(instance->patients);
322324
}
323325
}
324326

@@ -412,6 +414,9 @@ extern "C" inline int pybind11_set_dict(PyObject *self, PyObject *new_dict, void
412414

413415
/// dynamic_attr: Allow the garbage collector to traverse the internal instance `__dict__`.
414416
extern "C" inline int pybind11_traverse(PyObject *self, visitproc visit, void *arg) {
417+
// Note: patients is explicitly *not* visited (and not cleared in
418+
// pybind11_clear), because it is a merely a reflection of C++-level
419+
// references and there is no way to safely clear those.
415420
PyObject *&dict = *_PyObject_GetDictPtr(self);
416421
Py_VISIT(dict);
417422
return 0;
@@ -424,6 +429,18 @@ extern "C" inline int pybind11_clear(PyObject *self) {
424429
return 0;
425430
}
426431

432+
inline void add_patient(PyObject *nurse, PyObject *patient) {
433+
auto instance = (instance_essentials<void> *) nurse;
434+
if (!instance->patients) {
435+
instance->patients = PyList_New(0);
436+
if (!instance->patients)
437+
throw error_already_set();
438+
}
439+
int result = PyList_Append(instance->patients, patient);
440+
if (result != 0)
441+
throw error_already_set();
442+
}
443+
427444
/// Give instances of this type a `__dict__` and opt into garbage collection.
428445
inline void enable_dynamic_attributes(PyHeapTypeObject *heap_type) {
429446
auto type = &heap_type->ht_type;

include/pybind11/common.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -302,6 +302,7 @@ template <typename type> struct instance_essentials {
302302
PyObject_HEAD
303303
type *value;
304304
PyObject *weakrefs;
305+
PyObject *patients;
305306
bool owned : 1;
306307
bool holder_constructed : 1;
307308
};

include/pybind11/pybind11.h

Lines changed: 16 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1322,20 +1322,30 @@ template <typename... Args> struct init_alias {
13221322

13231323

13241324
inline void keep_alive_impl(handle nurse, handle patient) {
1325-
/* Clever approach based on weak references taken from Boost.Python */
13261325
if (!nurse || !patient)
13271326
pybind11_fail("Could not activate keep_alive!");
13281327

13291328
if (patient.is_none() || nurse.is_none())
13301329
return; /* Nothing to keep alive or nothing to be kept alive by */
13311330

1332-
cpp_function disable_lifesupport(
1333-
[patient](handle weakref) { patient.dec_ref(); weakref.dec_ref(); });
1331+
auto tinfo = get_type_info(Py_TYPE(nurse.ptr()));
1332+
if (tinfo) {
1333+
/* It's a pybind-registered type, so we can store the patient in the
1334+
* list of patients in its instance object. */
1335+
add_patient(nurse.ptr(), patient.ptr());
1336+
}
1337+
else {
1338+
/* Fall back to clever approach based on weak references taken from
1339+
* Boost.Python. This is not used for pybind-registered types because
1340+
* the objects can be destroyed out-of-order in a GC pass. */
1341+
cpp_function disable_lifesupport(
1342+
[patient](handle weakref) { patient.dec_ref(); weakref.dec_ref(); });
13341343

1335-
weakref wr(nurse, disable_lifesupport);
1344+
weakref wr(nurse, disable_lifesupport);
13361345

1337-
patient.inc_ref(); /* reference patient and leak the weak reference */
1338-
(void) wr.release();
1346+
patient.inc_ref(); /* reference patient and leak the weak reference */
1347+
(void) wr.release();
1348+
}
13391349
}
13401350

13411351
PYBIND11_NOINLINE inline void keep_alive_impl(size_t Nurse, size_t Patient, function_call &call, handle ret) {

0 commit comments

Comments
 (0)