Skip to content

Commit b4e4ada

Browse files
committed
First iteration of consume call policy. Noisy, ugly, and incomplete.
some cleanup
1 parent b48d4a0 commit b4e4ada

File tree

5 files changed

+129
-0
lines changed

5 files changed

+129
-0
lines changed

include/pybind11/attr.h

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,9 @@ template <typename T> struct base {
4444
/// Keep patient alive while nurse lives
4545
template <size_t Nurse, size_t Patient> struct keep_alive { };
4646

47+
/// Give ownership of parameter to the called function
48+
template <size_t Consumed> struct consume { };
49+
4750
/// Annotation indicating that a class is involved in a multiple inheritance relationship
4851
struct multiple_inheritance { };
4952

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

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

457+
template <size_t Consumed> struct process_attribute<consume<Consumed>> : public process_attribute_default<consume<Consumed>> {
458+
template <size_t C = Consumed, enable_if_t<C != 0, int> = 0>
459+
static void precall(function_call &call) {
460+
consume_impl(Consumed, call);
461+
}
462+
};
463+
453464
/// Recursively iterate over variadic template arguments
454465
template <typename... Args> struct process_attributes {
455466
static void init(const Args&... args, function_record *r) {

include/pybind11/pybind11.h

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1508,6 +1508,31 @@ PYBIND11_NOINLINE inline void keep_alive_impl(size_t Nurse, size_t Patient, func
15081508
keep_alive_impl(get_arg(Nurse), get_arg(Patient));
15091509
}
15101510

1511+
// template<typename holder_type> // ???
1512+
inline void consume_impl(handle consumed) {
1513+
if (!consumed)
1514+
pybind11_fail("Could not activate consume!");
1515+
1516+
if (consumed.is_none())
1517+
return; /* Nothing to consume */
1518+
1519+
// auto tinfo = all_type_info(Py_TYPE(consumed.ptr())); // ???
1520+
1521+
//consumed.dec_ref(); ???
1522+
1523+
// del the name of consumed from the Python scope somehow ...
1524+
1525+
auto inst = reinterpret_cast<detail::instance *>(consumed.ptr());
1526+
auto &holder = values_and_holders(inst).begin()->holder< std::unique_ptr <void*> >(); // holder_type ???
1527+
holder.release();
1528+
}
1529+
1530+
PYBIND11_NOINLINE inline void consume_impl(size_t Consumed, function_call &call) {
1531+
consume_impl(
1532+
Consumed == 0 ? handle() : Consumed <= call.args.size() ? call.args[Consumed - 1] : handle()
1533+
);
1534+
}
1535+
15111536
inline std::pair<decltype(internals::registered_types_py)::iterator, bool> all_type_info_get_cache(PyTypeObject *type) {
15121537
auto res = get_internals().registered_types_py
15131538
#ifdef __cpp_lib_unordered_map_try_emplace

tests/CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ set(PYBIND11_TEST_FILES
2929
test_buffers.cpp
3030
test_builtin_casters.cpp
3131
test_call_policies.cpp
32+
test_consume.cpp
3233
test_callbacks.cpp
3334
test_chrono.cpp
3435
test_class.cpp

tests/test_consume.cpp

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
/*
2+
tests/test_consume.cpp -- consume call policy
3+
4+
Copyright (c) 2016 Wenzel Jakob <[email protected]>
5+
Copyright (c) 2017 Attila Török <[email protected]>
6+
7+
All rights reserved. Use of this source code is governed by a
8+
BSD-style license that can be found in the LICENSE file.
9+
*/
10+
11+
#include "pybind11_tests.h"
12+
13+
class Box {
14+
int size;
15+
static int num_boxes;
16+
17+
public:
18+
Box(int size): size(size) { py::print("Box created."); ++num_boxes; }
19+
~Box() { py::print("Box destroyed."); --num_boxes; }
20+
21+
int get_size() { return size; }
22+
static int get_num_boxes() { return num_boxes; }
23+
};
24+
25+
int Box::num_boxes = 0;
26+
27+
class Filter {
28+
int threshold;
29+
30+
public:
31+
Filter(int threshold): threshold(threshold) { py::print("Filter created."); }
32+
~Filter() { py::print("Filter destroyed."); }
33+
34+
void process(Box *box) { // ownership of box is taken
35+
py::print("Box is processed by Filter.");
36+
if (box->get_size() > threshold)
37+
delete box;
38+
// otherwise the box is leaked
39+
};
40+
};
41+
42+
test_initializer consume([](py::module &m) {
43+
py::class_<Box>(m, "Box")
44+
.def(py::init<int>())
45+
.def_static("get_num_boxes", &Box::get_num_boxes);
46+
47+
py::class_<Filter>(m, "Filter")
48+
.def(py::init<int>())
49+
.def("process", &Filter::process, py::consume<2>());
50+
});

tests/test_consume.py

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
import pytest
2+
3+
4+
def test_consume_argument(capture):
5+
from pybind11_tests import Box, Filter
6+
7+
with capture:
8+
filt = Filter(4)
9+
assert capture == "Filter created."
10+
with capture:
11+
box_1 = Box(1)
12+
box_8 = Box(8)
13+
assert capture == """
14+
Box created.
15+
Box created.
16+
"""
17+
18+
assert Box.get_num_boxes() == 2
19+
20+
with capture:
21+
filt.process(box_1) # box_1 is not big enough, but process() leaks it
22+
assert capture == "Box is processed by Filter."
23+
24+
assert Box.get_num_boxes() == 2
25+
26+
with capture:
27+
filt.process(box_8) # box_8 is destroyed by process() of filt
28+
assert capture == """
29+
Box is processed by Filter.
30+
Box destroyed.
31+
"""
32+
33+
assert Box.get_num_boxes() == 1 # box_1 still exists somehow, but we can't access it
34+
35+
with capture:
36+
del filt
37+
del box_1
38+
del box_8
39+
pytest.gc_collect()
40+
assert capture == "Filter destroyed."
41+
42+
assert Box.get_num_boxes() == 1 # 1 box is leaked, and we can't do anything

0 commit comments

Comments
 (0)