diff --git a/docs/advanced/functions.rst b/docs/advanced/functions.rst index 6a93ab9da..f6061ffbe 100644 --- a/docs/advanced/functions.rst +++ b/docs/advanced/functions.rst @@ -182,6 +182,9 @@ relies on the ability to create a *weak reference* to the nurse object. When the nurse object is not a pybind11-registered type and does not support weak references, an exception will be thrown. +If you use an incorrect argument index, you will get a ``RuntimeError`` saying +``Could not activate keep_alive!``. You should review the indices you're using. + Consider the following example: here, the binding code for a list append operation ties the lifetime of the newly added element to the underlying container: diff --git a/tests/test_call_policies.cpp b/tests/test_call_policies.cpp index 26c83f81b..7cb98d0d8 100644 --- a/tests/test_call_policies.cpp +++ b/tests/test_call_policies.cpp @@ -51,6 +51,7 @@ TEST_SUBMODULE(call_policies, m) { void addChild(Child *) { } Child *returnChild() { return new Child(); } Child *returnNullChild() { return nullptr; } + static Child *staticFunction(Parent*) { return new Child(); } }; py::class_(m, "Parent") .def(py::init<>()) @@ -60,7 +61,12 @@ TEST_SUBMODULE(call_policies, m) { .def("returnChild", &Parent::returnChild) .def("returnChildKeepAlive", &Parent::returnChild, py::keep_alive<1, 0>()) .def("returnNullChildKeepAliveChild", &Parent::returnNullChild, py::keep_alive<1, 0>()) - .def("returnNullChildKeepAliveParent", &Parent::returnNullChild, py::keep_alive<0, 1>()); + .def("returnNullChildKeepAliveParent", &Parent::returnNullChild, py::keep_alive<0, 1>()) + .def_static( + "staticFunction", &Parent::staticFunction, py::keep_alive<1, 0>()); + + m.def("free_function", [](Parent*, Child*) {}, py::keep_alive<1, 2>()); + m.def("invalid_arg_index", []{}, py::keep_alive<0, 1>()); #if !defined(PYPY_VERSION) // test_alive_gc diff --git a/tests/test_call_policies.py b/tests/test_call_policies.py index e0413d145..af017e9d0 100644 --- a/tests/test_call_policies.py +++ b/tests/test_call_policies.py @@ -46,6 +46,19 @@ def test_keep_alive_argument(capture): """ ) + p = m.Parent() + c = m.Child() + assert ConstructorStats.detail_reg_inst() == n_inst + 2 + m.free_function(p, c) + del c + assert ConstructorStats.detail_reg_inst() == n_inst + 2 + del p + assert ConstructorStats.detail_reg_inst() == n_inst + + with pytest.raises(RuntimeError) as excinfo: + m.invalid_arg_index() + assert str(excinfo.value) == "Could not activate keep_alive!" + def test_keep_alive_return_value(capture): n_inst = ConstructorStats.detail_reg_inst() @@ -85,6 +98,23 @@ def test_keep_alive_return_value(capture): """ ) + p = m.Parent() + assert ConstructorStats.detail_reg_inst() == n_inst + 1 + with capture: + m.Parent.staticFunction(p) + assert ConstructorStats.detail_reg_inst() == n_inst + 2 + assert capture == "Allocating child." + with capture: + del p + assert ConstructorStats.detail_reg_inst() == n_inst + assert ( + capture + == """ + Releasing parent. + Releasing child. + """ + ) + # https://foss.heptapod.net/pypy/pypy/-/issues/2447 @pytest.mark.xfail("env.PYPY", reason="_PyObject_GetDictPtr is unimplemented")