2
2
#include < iostream>
3
3
#include < thread>
4
4
#include < future>
5
+ #include < functional>
5
6
6
7
#include < pybind11/embed.h>
7
8
#include < pybind11/eval.h>
9
+ #include < pybind11/functional.h>
8
10
#include < pybind11/pybind11.h>
9
11
10
12
namespace py = pybind11;
@@ -13,38 +15,66 @@ void hello() {
13
15
std::cout << " hello\n " ;
14
16
}
15
17
18
+ // Keep this as C++ code!
16
19
struct Executor {
20
+ using Callback = std::function<void ()>;
17
21
18
22
void execute () {
19
23
auto func = channel.get_future ().get ();
20
24
func ();
21
25
}
22
26
23
- explicit Executor (py::object callback)
24
- : callback(std::move(callback)),
25
- channel(),
27
+ explicit Executor ()
28
+ : channel(),
26
29
worker([this ]{execute ();}) {
27
30
}
28
31
29
- void run () {
30
- pybind11::gil_scoped_release release{};
32
+ void run (Callback callback) {
31
33
channel.set_value (callback);
32
34
}
33
35
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;
36
46
std::thread worker;
47
+ bool stopped{false };
37
48
38
49
~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
+ }
40
57
}
41
58
};
42
59
43
60
void init_module (py::module m) {
44
61
m.def (" hello" , &hello, " Say hello" );
45
62
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);
48
78
}
49
79
50
80
int main (int , char **) {
@@ -56,25 +86,17 @@ int main(int, char**) {
56
86
57
87
py::print (" [ Eval ]" );
58
88
py::exec (R"""(
89
+ import time
90
+
59
91
def bye():
60
92
print('bye')
61
93
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()
78
100
)""" );
79
101
80
102
py::print (" [ Done ]" );
0 commit comments