FazBrowse GitHub Viewer
|
Trending
|
URL:
|
Home
Tools:
[Download Repo ZIP]
[View Raw Code]
[Original HTTPS Page]
espp/lib/python_bindings/socket_reactor_bindings.cpp at main · esp-cpp/espp · GitHub
Uh oh!
There was an error while loading.
Please reload this page
.
esp-cpp
/
espp
Public
Notifications
You must be signed in to change notification settings
Fork
29
Star
113
Code
Issues
0
Pull requests
8
Discussions
Actions
Projects
Security and quality
0
Insights
Additional navigation options
Code
Issues
Pull requests
Discussions
Actions
Projects
Security and quality
Insights
Expand file tree
Breadcrumbs
espp
/
lib
/
python_bindings
/
socket_reactor_bindings.cpp
Copy path
More file actions
More file actions
Latest commit
History
History
History
126 lines (118 loc) · 5.97 KB
Breadcrumbs
espp
/
lib
/
python_bindings
/
socket_reactor_bindings.cpp
Copy path
File metadata and controls
126 lines (118 loc) · 5.97 KB
Raw
Copy raw file
Download raw file
Open symbols panel
Edit and raw actions
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
//
Hand-written pybind11 bindings for espp::SocketReactor.
//
//
Why hand-written (see the note in autogenerate_bindings.py): litgen/srcmlcpp cannot parse
//
SocketReactor (brace-init default members with parenthesized/cast expressions trip srcmlcpp's
//
brace-init fixer), and its TCP callbacks take std::unique_ptr<TcpSocket> / TcpSocket& which
//
litgen cannot bind. This shim exposes a clean, GIL-correct subset for Python: the reactor
//
lifecycle and the UDP-receiver path (the TCP listener/stream paths remain C++-only for now).
//
//
It is kept out of the generated pybind_espp.cpp so regeneration never clobbers it.
#
include
<
cstdint
>
#
include
<
memory
>
#
include
<
optional
>
#
include
<
string
>
#
include
<
vector
>
#
include
<
pybind11/chrono.h
>
#
include
<
pybind11/pybind11.h
>
#
include
<
pybind11/stl.h
>
#
include
"
dscp.hpp
"
#
include
"
qos_band.hpp
"
#
include
"
socket_reactor.hpp
"
#
include
"
udp_socket.hpp
"
namespace
py
=
pybind11;
using
espp::SocketReactor;
namespace
{
//
Adapt a Python callable `cb(data: bytes, sender: Socket.Info) -> Optional[bytes]` into the C++
//
receive callback. Like the rtps shim: capture the py::function in a shared_ptr so the reactor may
//
copy the std::function off the GIL (only the shared_ptr refcount moves, which is GIL-free); the
//
callable is invoked and finally destroyed under the GIL.
espp::Socket::receive_callback_fn
wrap_receive_callback
(
const
py::function &fn) {
if
(!fn) {
return
{};
}
//
Own the py::function via a shared_ptr with a GIL-acquiring deleter: the last
//
reference may be released on a non-Python thread (a reactor pool worker
//
erasing an entry after remove()), and ~py::function must run under the GIL.
auto
cb = std::shared_ptr<py::function>(
new
py::function
(fn), [](py::function *f) {
py::gil_scoped_acquire gil;
delete
f;
});
return
[cb](std::vector<
uint8_t
> &data,
const
espp::Socket::Info &sender) -> std::optional<std::vector<
uint8_t
>> {
py::gil_scoped_acquire gil;
//
The handler runs on a reactor pool worker; a Python exception (or a bad
//
return type) must not propagate out - it would crash/stall the worker.
//
Report it and degrade to "no response".
try
{
py::object result =
(*cb)(
py::bytes
(
reinterpret_cast
<
const
char
*>(data.
data
()), data.
size
()), sender);
if
(result.
is_none
()) {
return
std::
nullopt
;
}
//
Accept either bytes or str as the response payload.
std::string s = py::cast<std::string>(result);
return
std::vector<
uint8_t
>(s.
begin
(), s.
end
());
}
catch
(py::error_already_set &e) {
//
Reports the traceback via sys.unraisablehook and clears the error.
e.
discard_as_unraisable
(
"
espp.SocketReactor receive callback
"
);
return
std::
nullopt
;
}
catch
(
const
std::exception &e) {
py::print
(
"
espp.SocketReactor receive callback error:
"
, e.
what
());
return
std::
nullopt
;
}
};
}
}
//
namespace
void
py_init_socket_reactor
(py::
module
&m) {
py::class_<SocketReactor>(
m,
"
SocketReactor
"
,
py::dynamic_attr
(),
"
A select()-based event loop that multiplexes many receiver sockets onto a thread pool,
"
"
instead of one thread per socket.
"
)
//
Construct with an owned thread pool of `worker_count` workers. (Sharing an external
//
pool is available in C++ but not exposed here.)
.
def
(
py::init
(
[](std::
size_t
worker_count,
bool
auto_start, espp::Logger::Verbosity log_level) {
SocketReactor::Config config;
config.
pool_config
.
worker_count
= worker_count;
config.
auto_start
= auto_start;
config.
log_level
= log_level;
return
std::make_unique<SocketReactor>(config);
}),
py::arg
(
"
worker_count
"
) =
2
,
py::arg
(
"
auto_start
"
) =
true
,
py::arg
(
"
log_level
"
) = espp::Logger::Verbosity::
WARN
)
//
Release the GIL around start()/stop(): stop() blocks waiting for
//
in-flight handlers, and a pool worker running a Python receive callback
//
needs the GIL - holding it here would deadlock.
.
def
(
"
start
"
, &SocketReactor::start, py::call_guard<py::gil_scoped_release>(),
"
Start the select() loop (and owned pool).
"
)
.
def
(
"
stop
"
, &SocketReactor::stop, py::call_guard<py::gil_scoped_release>(),
"
Stop the loop and wait for in-flight handlers to finish.
"
)
.
def
(
"
is_running
"
, &SocketReactor::is_running)
.
def
(
"
num_registered
"
, &SocketReactor::num_registered)
.
def
(
"
remove
"
,
static_cast
<
bool
(SocketReactor::*)(SocketReactor::Id)>(&SocketReactor::remove),
py::arg
(
"
id
"
),
"
Unregister a socket by the id returned from add_udp_receiver().
"
)
.
def
(
"
add_udp_receiver
"
,
[](SocketReactor &self, espp::UdpSocket &socket, std::
size_t
port,
std::
size_t
buffer_size,
const
py::function &callback, espp::QosBand band,
std::optional<espp::Dscp> dscp) -> SocketReactor::Id {
espp::UdpSocket::ReceiveConfig rc;
rc.
port
= port;
rc.
buffer_size
= buffer_size;
rc.
on_receive_callback
=
wrap_receive_callback
(callback);
rc.
band
= band;
rc.
dscp
= dscp;
return
self.
add_udp_receiver
(socket, rc);
},
py::arg
(
"
socket
"
),
py::arg
(
"
port
"
),
py::arg
(
"
buffer_size
"
),
py::arg
(
"
callback
"
),
py::arg
(
"
band
"
) = espp::QosBand::Normal,
py::arg
(
"
dscp
"
) = std::optional<espp::Dscp>{},
"
Bind `socket` to `port` and receive on it via the reactor. `callback(data: bytes,
"
"
sender) -> Optional[bytes]`; a returned bytes is sent back to the sender. `band`
"
"
selects the espp.QosBand this socket's handlers are dispatched at; `dscp` (an
"
"
espp.Dscp, e.g. Dscp.Ef) optionally marks transmitted replies (IP_TOS, best-effort).
"
"
Returns a registration id (0 == INVALID_ID on failure).
"
)
.
def_property_readonly_static
(
"
INVALID_ID
"
, [](py::object) {
return
SocketReactor::
INVALID_ID
; },
"
The id value returned by add_* on failure.
"
);
}
Back
|
FazBrowse Home
|
New Git URL