Skip to content

WIP Add a consume call policy #971

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
wants to merge 2 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 11 additions & 0 deletions include/pybind11/attr.h
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,9 @@ template <typename T> struct base {
/// Keep patient alive while nurse lives
template <size_t Nurse, size_t Patient> struct keep_alive { };

/// Give ownership of parameter to the called function
template <size_t Consumed> struct consume { };

/// Annotation indicating that a class is involved in a multiple inheritance relationship
struct multiple_inheritance { };

Expand Down Expand Up @@ -117,6 +120,7 @@ enum op_type : int;
struct undefined_t;
template <op_id id, op_type ot, typename L = undefined_t, typename R = undefined_t> struct op_;
inline void keep_alive_impl(size_t Nurse, size_t Patient, function_call &call, handle ret);
inline void consume_impl(size_t Consumed, function_call &call);

/// Internal data structure which holds metadata about a keyword argument
struct argument_record {
Expand Down Expand Up @@ -450,6 +454,13 @@ template <size_t Nurse, size_t Patient> struct process_attribute<keep_alive<Nurs
static void postcall(function_call &call, handle ret) { keep_alive_impl(Nurse, Patient, call, ret); }
};

template <size_t Consumed> struct process_attribute<consume<Consumed>> : public process_attribute_default<consume<Consumed>> {
template <size_t C = Consumed, enable_if_t<C != 0, int> = 0>
static void precall(function_call &call) {
consume_impl(Consumed, call);
}
};

/// Recursively iterate over variadic template arguments
template <typename... Args> struct process_attributes {
static void init(const Args&... args, function_record *r) {
Expand Down
1 change: 1 addition & 0 deletions include/pybind11/cast.h
Original file line number Diff line number Diff line change
Expand Up @@ -1371,6 +1371,7 @@ template <typename... Ts> class type_caster<std::tuple<Ts...>>
template <typename T>
struct holder_helper {
static auto get(const T &p) -> decltype(p.get()) { return p.get(); }
static void release(T &p) { p.release(); }
};

/// Type caster for holder types like std::shared_ptr, etc.
Expand Down
28 changes: 28 additions & 0 deletions include/pybind11/pybind11.h
Original file line number Diff line number Diff line change
Expand Up @@ -1508,6 +1508,34 @@ PYBIND11_NOINLINE inline void keep_alive_impl(size_t Nurse, size_t Patient, func
keep_alive_impl(get_arg(Nurse), get_arg(Patient));
}

// template<typename holder_type> // ???
inline void consume_impl(handle consumed) {
if (!consumed)
pybind11_fail("Could not activate consume!");

if (consumed.is_none())
return; /* Nothing to consume */

auto inst = reinterpret_cast<detail::instance *>(consumed.ptr());
auto value_and_holder = values_and_holders(inst).begin();



auto &holder = value_and_holder->holder< std::unique_ptr<void*> >(); // holder_type ???


holder_helper<std::unique_ptr<void*>>::release(holder);

value_and_holder->set_holder_constructed(false);
inst->owned = false;
}

PYBIND11_NOINLINE inline void consume_impl(size_t Consumed, function_call &call) {
consume_impl(
Consumed == 0 ? handle() : Consumed <= call.args.size() ? call.args[Consumed - 1] : handle()
);
}

inline std::pair<decltype(internals::registered_types_py)::iterator, bool> all_type_info_get_cache(PyTypeObject *type) {
auto res = get_internals().registered_types_py
#ifdef __cpp_lib_unordered_map_try_emplace
Expand Down
1 change: 1 addition & 0 deletions tests/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ set(PYBIND11_TEST_FILES
test_buffers.cpp
test_builtin_casters.cpp
test_call_policies.cpp
test_consume.cpp
test_callbacks.cpp
test_chrono.cpp
test_class.cpp
Expand Down
50 changes: 50 additions & 0 deletions tests/test_consume.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
/*
tests/test_consume.cpp -- consume call policy

Copyright (c) 2016 Wenzel Jakob <[email protected]>
Copyright (c) 2017 Attila Török <[email protected]>

All rights reserved. Use of this source code is governed by a
BSD-style license that can be found in the LICENSE file.
*/

#include "pybind11_tests.h"

class Box {
int size;
static int num_boxes;

public:
Box(int size): size(size) { py::print("Box created."); ++num_boxes; }
~Box() { py::print("Box destroyed."); --num_boxes; }

int get_size() { return size; }
static int get_num_boxes() { return num_boxes; }
};

int Box::num_boxes = 0;

class Filter {
int threshold;

public:
Filter(int threshold): threshold(threshold) { py::print("Filter created."); }
~Filter() { py::print("Filter destroyed."); }

void process(Box *box) { // ownership of box is taken
py::print("Box is processed by Filter.");
if (box->get_size() > threshold)
delete box;
// otherwise the box is leaked
};
};

test_initializer consume([](py::module &m) {
py::class_<Box>(m, "Box")
.def(py::init<int>())
.def_static("get_num_boxes", &Box::get_num_boxes);

py::class_<Filter>(m, "Filter")
.def(py::init<int>())
.def("process", &Filter::process, py::consume<2>());
});
42 changes: 42 additions & 0 deletions tests/test_consume.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
import pytest


def test_consume_argument(capture):
from pybind11_tests import Box, Filter

with capture:
filt = Filter(4)
assert capture == "Filter created."
with capture:
box_1 = Box(1)
box_8 = Box(8)
assert capture == """
Box created.
Box created.
"""

assert Box.get_num_boxes() == 2

with capture:
filt.process(box_1) # box_1 is not big enough, but process() leaks it
assert capture == "Box is processed by Filter."

assert Box.get_num_boxes() == 2

with capture:
filt.process(box_8) # box_8 is destroyed by process() of filt
assert capture == """
Box is processed by Filter.
Box destroyed.
"""

assert Box.get_num_boxes() == 1 # box_1 still exists somehow, but we can't access it

with capture:
del filt
del box_1
del box_8
pytest.gc_collect()
assert capture == "Filter destroyed."

assert Box.get_num_boxes() == 1 # 1 box is leaked, and we can't do anything