Compare commits

...

10 Commits

Author SHA1 Message Date
Jason Rhinelander f46aaa7de6
1.0.5 release 2023-09-27 20:14:13 -03:00
Jason Rhinelander f11d0f32b3
Merge remote-tracking branch 'origin/stable' into ubuntu/lunar 2023-09-27 20:14:08 -03:00
Jason Rhinelander 9f521dfeb5
Merge remote-tracking branch 'origin/dev' into stable 2023-09-27 20:13:00 -03:00
Jason Rhinelander 48a6561002
Merge pull request #13 from jagerman/fix-reply-segfault
Fix python binding segfault
2023-09-27 19:57:23 -03:00
Jason Rhinelander 1198dc217d
sid build fix 2023-09-27 19:45:28 -03:00
Jason Rhinelander b0edf998f8
Fix python binding segfault
This fixes a segfault in the reply handling code, where the destructor
of a `py::function` wasn't necessarily happening with the GIL held,
which could make Python segfault.

The solution that was here to address this didn't work because it was
relying on std::optionals, but it is possible (and apparently sometimes
happens) that the std::function this lambda gets stuffed into gets moved
(or maybe copied?), which then results in *two* lambda destructions: we
were correctly dealing with the one that eventually gets called by
clearing things properly when it gets called, but the temporary
destructor also fires, and that is the one that broke.

This changes it to instead leak bare pointers into the lambda and then
recapture them inside when we get called; since we are guaranteed to be
called exactly once, this recaptures them without losing them but
doesn't incur destruction of a py::function deep in oxenmq (outside of
GIL scope).
2023-09-27 16:06:08 -03:00
Jason Rhinelander 38ba790d8b Merge branch 'dev' into stable 2023-03-28 09:33:33 +11:00
Jason Rhinelander 5359810a10
Merge pull request #12 from jagerman/connect-auth-level
Expose setting auth level on outgoing connection
2023-03-28 09:30:41 +11:00
Jason Rhinelander ace70998d7 fix debian sid builds
Python 3.11+ complains when trying to use pip globally, but for CI it
seems fine to override (and potentially break the temporary CI image).
2023-03-28 09:21:15 +11:00
Jason Rhinelander 6e50822bc9
Expose setting auth level on outgoing connection
This is necessary to allow a remote to issue authenticated commands back
to us.
2023-03-08 17:31:49 -04:00
3 changed files with 59 additions and 30 deletions

9
debian/changelog vendored
View File

@ -1,3 +1,12 @@
pyoxenmq (1.0.5-1~ubuntu2304) lunar; urgency=medium
* Expose setting auth level on outgoing connection
* fix debian sid builds
* Fix python binding segfault
* sid build fix
-- Jason Rhinelander <jason@imaginary.ca> Wed, 27 Sep 2023 20:14:10 -0300
pyoxenmq (1.0.3-1~ubuntu2304) lunar; urgency=medium
* rebuild for lunar

View File

@ -3,7 +3,7 @@ from setuptools import setup
# Available at setup time due to pyproject.toml
from pybind11.setup_helpers import Pybind11Extension, build_ext
__version__ = "1.0.3"
__version__ = "1.0.5"
# Note:
# Sort input source files if you glob sources to ensure bit-for-bit

View File

@ -622,28 +622,34 @@ permissions: in this example, the required permissions the access the endpoint w
OxenMQ::ConnectSuccess on_success,
OxenMQ::ConnectFailure on_failure,
std::chrono::milliseconds timeout,
std::optional<bool> ephemeral_routing_id) {
std::optional<bool> ephemeral_routing_id,
AuthLevel auth_level) {
return self.connect_remote(remote, std::move(on_success), std::move(on_failure),
connect_option::timeout{timeout},
connect_option::ephemeral_routing_id{ephemeral_routing_id.value_or(self.EPHEMERAL_ROUTING_ID)}
connect_option::ephemeral_routing_id{ephemeral_routing_id.value_or(self.EPHEMERAL_ROUTING_ID)},
auth_level
);
},
"remote"_a, "on_success"_a, "on_failure"_a,
kwonly,
"timeout"_a = oxenmq::REMOTE_CONNECT_TIMEOUT, "ephemeral_routing_id"_a = std::nullopt,
"timeout"_a = oxenmq::REMOTE_CONNECT_TIMEOUT,
"ephemeral_routing_id"_a = std::nullopt,
"auth_level"_a = AuthLevel::none,
R"(
Starts connecting to a remote address and return immediately. The connection can be used
immediately, however messages will only be queued until the connection is established (or dropped if
the connection fails). The given callbacks are invoked for success or failure.
`ephemeral_routing_id` and `timeout` allowing overriding the defaults (oxenmq.EPHEMERAL_ROUTING_ID
and 10s, respectively).
and 10s, respectively). `auth_level` can be specified to set the auth level of *incoming* requests
that arrive through this connection.
)")
.def("connect_remote", [](OxenMQ& self,
const address& remote,
std::chrono::milliseconds timeout,
std::optional<bool> ephemeral_routing_id) {
std::optional<bool> ephemeral_routing_id,
AuthLevel auth_level) {
std::promise<ConnectionID> promise;
self.connect_remote(
remote,
@ -653,10 +659,16 @@ and 10s, respectively).
std::runtime_error{"Connection failed: " + std::string{reason}}));
},
oxenmq::connect_option::timeout{timeout},
connect_option::ephemeral_routing_id{ephemeral_routing_id.value_or(self.EPHEMERAL_ROUTING_ID)}
connect_option::ephemeral_routing_id{ephemeral_routing_id.value_or(self.EPHEMERAL_ROUTING_ID)},
auth_level
);
return promise.get_future().get();
}, "remote"_a, "timeout"_a = oxenmq::REMOTE_CONNECT_TIMEOUT, "ephemeral_routing_id"_a = std::nullopt,
},
"remote"_a,
"timeout"_a = oxenmq::REMOTE_CONNECT_TIMEOUT,
kwonly,
"ephemeral_routing_id"_a = std::nullopt,
"auth_level"_a = AuthLevel::none,
R"(Simpler version of connect_remote that connects to a remote address synchronously.
This will block until the connection is established or times out; throws on connection failure,
@ -667,12 +679,14 @@ Takes the address and an optional `timeout` to override the timeout (default 10s
py::bytes pubkey,
std::optional<std::chrono::milliseconds> keep_alive,
std::optional<std::string> remote_hint,
std::optional<bool> ephemeral_routing_id) {
std::optional<bool> ephemeral_routing_id,
AuthLevel auth_level) {
return self.connect_sn(std::string{pubkey},
connect_option::keep_alive{keep_alive.value_or(-1ms)},
connect_option::hint{remote_hint.value_or("")},
connect_option::ephemeral_routing_id{ephemeral_routing_id.value_or(self.EPHEMERAL_ROUTING_ID)});
}, "pubkey"_a, kwonly, "keep_alive"_a, "remote_hint"_a, "ephemeral_routing_id"_a,
connect_option::ephemeral_routing_id{ephemeral_routing_id.value_or(self.EPHEMERAL_ROUTING_ID)},
auth_level);
}, "pubkey"_a, kwonly, "keep_alive"_a, "remote_hint"_a, "ephemeral_routing_id"_a, "auth_level"_a = AuthLevel::none,
R"(Connect to a remote service node by pubkey.
Try to initiate a connection to the given SN in anticipation of needing a connection in the future.
@ -699,6 +713,9 @@ Parameters:
- ephemeral_routing_id - if set, override the default OxenMQ.EPHEMERAL_ROUTING_ID for this
connection.
- auth_level - specified the authentication level for incoming commands (i.e. issued *to us*) over
this connection.
Returns a ConnectionID that identifies an connection with the given SN. Typically you *don't* need
to worry about saving this (and can just discard it): you can always simply pass the pubkey into
send/request methods to send to the SN by pubkey.
@ -740,12 +757,12 @@ the background).)")
}
bool request = kwargs.contains("request") && kwargs["request"].cast<bool>();
std::optional<py::function> on_reply, on_reply_failure;
std::unique_ptr<py::function> on_reply, on_reply_failure;
if (request) {
if (kwargs.contains("on_reply"))
on_reply = kwargs["on_reply"].cast<py::function>();
on_reply = std::make_unique<py::function>(kwargs["on_reply"].cast<py::function>());
if (kwargs.contains("on_reply_failure"))
on_reply_failure = kwargs["on_reply_failure"].cast<py::function>();
on_reply_failure = std::make_unique<py::function>(kwargs["on_reply_failure"].cast<py::function>());
} else if (kwargs.contains("on_reply") || kwargs.contains("on_reply_failure")) {
throw std::logic_error{"Error: send(...) on_reply=/on_reply_failure= option "
"requires request=True (perhaps you meant to use `.request(...)` instead?)"};
@ -778,29 +795,32 @@ the background).)")
hint, optional, incoming, outgoing, keep_alive, request_timeout,
std::move(qfail), std::move(qfull));
} else {
auto reply_cb = [on_reply = std::move(on_reply), on_fail = std::move(on_reply_failure)]
(bool success, std::vector<std::string> data) mutable {
auto reply_cb = [reply_rawptr = on_reply.release(), fail_rawptr = on_reply_failure.release()]
(bool success, std::vector<std::string> data) {
// The gil here makes things tricky: the function invocation itself is
// already gil protected, but the *destruction* of the lambda isn't, and
// that breaks things because the destruction frees a python reference to
// the callback. However oxenmq invokes this callback exactly once so we
// can deal with it by stealing the captures out of the lambda to force
// destruction here, with the gil held.
// can deal with it by leaking raw pointers into the lambda captures then
// reclaiming them the one and only time we are called.
py::gil_scoped_acquire gil;
auto reply = std::move(on_reply);
auto fail = std::move(on_fail);
std::unique_ptr<py::function> reply{reply_rawptr};
std::unique_ptr<py::function> fail{fail_rawptr};
if (success ? !reply : !fail)
return;
py::list l;
if (success) {
for (const auto& part : data)
l.append(py::memoryview::from_memory(part.data(), part.size()));
(*reply)(l);
} else if (on_fail) {
for (const auto& part : data)
l.append(py::bytes(part.data(), part.size()));
(*fail)(l);
if (reply) {
py::list l;
for (const auto& part : data)
l.append(py::memoryview::from_memory(part.data(), part.size()));
(*reply)(l);
}
} else {
if (fail) {
py::list l;
for (const auto& part : data)
l.append(py::bytes(part.data(), part.size()));
(*fail)(l);
}
}
};