Skip to content

Commit e7b199c

Browse files
committed
This fixes it... I think?
1 parent 23afde8 commit e7b199c

File tree

1 file changed

+48
-26
lines changed

1 file changed

+48
-26
lines changed

python/pybind11/custom_tests/test_gil_issue1723.cc

Lines changed: 48 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,11 @@
22
#include <iostream>
33
#include <thread>
44
#include <future>
5+
#include <functional>
56

67
#include <pybind11/embed.h>
78
#include <pybind11/eval.h>
9+
#include <pybind11/functional.h>
810
#include <pybind11/pybind11.h>
911

1012
namespace py = pybind11;
@@ -13,38 +15,66 @@ void hello() {
1315
std::cout << "hello\n";
1416
}
1517

18+
// Keep this as C++ code!
1619
struct Executor {
20+
using Callback = std::function<void ()>;
1721

1822
void execute() {
1923
auto func = channel.get_future().get();
2024
func();
2125
}
2226

23-
explicit Executor(py::object callback)
24-
: callback(std::move(callback)),
25-
channel(),
27+
explicit Executor()
28+
: channel(),
2629
worker([this]{execute();}) {
2730
}
2831

29-
void run() {
30-
pybind11::gil_scoped_release release{};
32+
void run(Callback callback) {
3133
channel.set_value(callback);
3234
}
3335

34-
py::object callback;
35-
std::promise<py::object> channel;
36+
void stop() {
37+
if (!stopped) {
38+
worker.join();
39+
stopped = true;
40+
} else {
41+
throw std::runtime_error("Cannot stop twice");
42+
}
43+
}
44+
45+
std::promise<Callback> channel;
3646
std::thread worker;
47+
bool stopped{false};
3748

3849
~Executor() {
39-
worker.join();
50+
if (!stopped) {
51+
std::cerr
52+
<< "WARNING: Joining thread in destructor for Python stuff might "
53+
"be bad, b/c the `std::function<>` dtor could cause a decref "
54+
"without GIL being held. Call `stop()` explicitly instead!\n";
55+
stop();
56+
}
4057
}
4158
};
4259

4360
void init_module(py::module m) {
4461
m.def("hello", &hello, "Say hello");
4562
py::class_<Executor>(m, "Executor")
46-
.def(py::init<py::object>())
47-
.def("run", &Executor::run);
63+
.def(py::init<>())
64+
.def("run",
65+
[](Executor& self, Executor::Callback callback) {
66+
// Acquire when executing potential Python code!
67+
// In general, you will want wrapped stuff to do GIL acquisition.
68+
// NOTE: I (eric) dunno if `cpp_function` automatically does this or
69+
// not...
70+
auto gil_callback = [callback]() {
71+
pybind11::gil_scoped_acquire lock{};
72+
callback();
73+
};
74+
self.run(gil_callback);
75+
},
76+
py::arg("callback"))
77+
.def("stop", &Executor::stop);
4878
}
4979

5080
int main(int, char**) {
@@ -56,25 +86,17 @@ int main(int, char**) {
5686

5787
py::print("[ Eval ]");
5888
py::exec(R"""(
89+
import time
90+
5991
def bye():
6092
print('bye')
6193
62-
def main():
63-
pass
64-
65-
if True:
66-
m.hello()
67-
ex = m.Executor(bye)
68-
ex.run()
69-
70-
use_trace = False
71-
if use_trace:
72-
import sys, trace
73-
sys.stdout = sys.stderr
74-
tracer = trace.Trace(trace=1, count=0, ignoredirs=["/usr", sys.prefix])
75-
tracer.runfunc(main)
76-
else:
77-
main()
94+
m.hello()
95+
ex = m.Executor()
96+
ex.run(callback=bye)
97+
# Give thread time to execute.
98+
time.sleep(0.1)
99+
ex.stop()
78100
)""");
79101

80102
py::print("[ Done ]");

0 commit comments

Comments
 (0)