forked from Kistler-Group/sdbus-cpp
feat: add Slot-returning overloads of async method calls (#433)
This commit is contained in:
@ -262,6 +262,7 @@ v2.0.0
|
|||||||
- Fix for external event loops in which the event loop thread ID was not correctly initialized (now fixed and simplified by not needing the thread ID anymore)
|
- Fix for external event loops in which the event loop thread ID was not correctly initialized (now fixed and simplified by not needing the thread ID anymore)
|
||||||
- Introduce native integration for sd-event
|
- Introduce native integration for sd-event
|
||||||
- Add method to get currently processed message also to `IConnection`
|
- Add method to get currently processed message also to `IConnection`
|
||||||
|
- Add Slot-returning overloads of `callMethodAsync()` functions
|
||||||
- `[[nodiscard]]` attribute has been added to relevant API methods.
|
- `[[nodiscard]]` attribute has been added to relevant API methods.
|
||||||
- Add new `SDBUSCPP_SDBUS_LIB` CMake configuration variable determining which sd-bus library shall be picked
|
- Add new `SDBUSCPP_SDBUS_LIB` CMake configuration variable determining which sd-bus library shall be picked
|
||||||
- Switch to C++20 standard (but C++20 is not required, and the used C++20 features are conditionally compiled)
|
- Switch to C++20 standard (but C++20 is not required, and the used C++20 features are conditionally compiled)
|
||||||
|
@ -1231,6 +1231,10 @@ int main(int argc, char *argv[])
|
|||||||
|
|
||||||
Empty `error` parameter means that no D-Bus error occurred while making the call, and subsequent arguments are valid D-Bus method return values. However, `error` parameter containing a value means that an error occurred during the call (and subsequent arguments are simply default-constructed), and the underlying `Error` instance provides us with the error name and message.
|
Empty `error` parameter means that no D-Bus error occurred while making the call, and subsequent arguments are valid D-Bus method return values. However, `error` parameter containing a value means that an error occurred during the call (and subsequent arguments are simply default-constructed), and the underlying `Error` instance provides us with the error name and message.
|
||||||
|
|
||||||
|
> **_Tip_:** The function returns the `sdbus::PendingAsyncCall` object, a non-owning, observing handle to the async call. It can be used to query whether the call is still in progress, and to cancel the call.
|
||||||
|
|
||||||
|
> **_Tip_:** There is also the `.uponReplyInvoke(callback, sdbus::return_slot);` variant with the `return_slot` tag, which returns `Slot` object, an owning RAII handle to the async call. This makes the client an owner of the pending async call. Letting go of the handle means cancelling the call.
|
||||||
|
|
||||||
Another option is to finish the async call statement with `getResultAsFuture()`, which is a template function which takes the list of types returned by the D-Bus method (empty list in case of `void`-returning method) which returns a `std::future` object, which will later, when the reply arrives, be set to contain the return value(s). Or if the call returns an error, `sdbus::Error` will be thrown by `std::future::get()`.
|
Another option is to finish the async call statement with `getResultAsFuture()`, which is a template function which takes the list of types returned by the D-Bus method (empty list in case of `void`-returning method) which returns a `std::future` object, which will later, when the reply arrives, be set to contain the return value(s). Or if the call returns an error, `sdbus::Error` will be thrown by `std::future::get()`.
|
||||||
|
|
||||||
The future object will contain void for a void-returning D-Bus method, a single type for a single value returning D-Bus method, and a `std::tuple` to hold multiple return values of a D-Bus method.
|
The future object will contain void for a void-returning D-Bus method, a single type for a single value returning D-Bus method, and a `std::tuple` to hold multiple return values of a D-Bus method.
|
||||||
|
@ -131,6 +131,7 @@ namespace sdbus {
|
|||||||
AsyncMethodInvoker& withTimeout(const std::chrono::duration<_Rep, _Period>& timeout);
|
AsyncMethodInvoker& withTimeout(const std::chrono::duration<_Rep, _Period>& timeout);
|
||||||
template <typename... _Args> AsyncMethodInvoker& withArguments(_Args&&... args);
|
template <typename... _Args> AsyncMethodInvoker& withArguments(_Args&&... args);
|
||||||
template <typename _Function> PendingAsyncCall uponReplyInvoke(_Function&& callback);
|
template <typename _Function> PendingAsyncCall uponReplyInvoke(_Function&& callback);
|
||||||
|
template <typename _Function> [[nodiscard]] Slot uponReplyInvoke(_Function&& callback, return_slot_t);
|
||||||
// Returned future will be std::future<void> for no (void) D-Bus method return value
|
// Returned future will be std::future<void> for no (void) D-Bus method return value
|
||||||
// or std::future<T> for single D-Bus method return value
|
// or std::future<T> for single D-Bus method return value
|
||||||
// or std::future<std::tuple<...>> for multiple method return values
|
// or std::future<std::tuple<...>> for multiple method return values
|
||||||
@ -140,6 +141,7 @@ namespace sdbus {
|
|||||||
friend IProxy;
|
friend IProxy;
|
||||||
AsyncMethodInvoker(IProxy& proxy, const MethodName& methodName);
|
AsyncMethodInvoker(IProxy& proxy, const MethodName& methodName);
|
||||||
AsyncMethodInvoker(IProxy& proxy, const char* methodName);
|
AsyncMethodInvoker(IProxy& proxy, const char* methodName);
|
||||||
|
template <typename _Function> async_reply_handler makeAsyncReplyHandler(_Function&& callback);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
IProxy& proxy_;
|
IProxy& proxy_;
|
||||||
@ -190,6 +192,7 @@ namespace sdbus {
|
|||||||
public:
|
public:
|
||||||
AsyncPropertyGetter& onInterface(std::string_view interfaceName);
|
AsyncPropertyGetter& onInterface(std::string_view interfaceName);
|
||||||
template <typename _Function> PendingAsyncCall uponReplyInvoke(_Function&& callback);
|
template <typename _Function> PendingAsyncCall uponReplyInvoke(_Function&& callback);
|
||||||
|
template <typename _Function> [[nodiscard]] Slot uponReplyInvoke(_Function&& callback, return_slot_t);
|
||||||
std::future<Variant> getResultAsFuture();
|
std::future<Variant> getResultAsFuture();
|
||||||
|
|
||||||
private:
|
private:
|
||||||
@ -232,6 +235,7 @@ namespace sdbus {
|
|||||||
template <typename _Value> AsyncPropertySetter& toValue(_Value&& value);
|
template <typename _Value> AsyncPropertySetter& toValue(_Value&& value);
|
||||||
AsyncPropertySetter& toValue(Variant value);
|
AsyncPropertySetter& toValue(Variant value);
|
||||||
template <typename _Function> PendingAsyncCall uponReplyInvoke(_Function&& callback);
|
template <typename _Function> PendingAsyncCall uponReplyInvoke(_Function&& callback);
|
||||||
|
template <typename _Function> [[nodiscard]] Slot uponReplyInvoke(_Function&& callback, return_slot_t);
|
||||||
std::future<void> getResultAsFuture();
|
std::future<void> getResultAsFuture();
|
||||||
|
|
||||||
private:
|
private:
|
||||||
@ -267,6 +271,7 @@ namespace sdbus {
|
|||||||
public:
|
public:
|
||||||
AsyncAllPropertiesGetter& onInterface(std::string_view interfaceName);
|
AsyncAllPropertiesGetter& onInterface(std::string_view interfaceName);
|
||||||
template <typename _Function> PendingAsyncCall uponReplyInvoke(_Function&& callback);
|
template <typename _Function> PendingAsyncCall uponReplyInvoke(_Function&& callback);
|
||||||
|
template <typename _Function> [[nodiscard]] Slot uponReplyInvoke(_Function&& callback, return_slot_t);
|
||||||
std::future<std::map<PropertyName, Variant>> getResultAsFuture();
|
std::future<std::map<PropertyName, Variant>> getResultAsFuture();
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
@ -287,7 +287,24 @@ namespace sdbus {
|
|||||||
{
|
{
|
||||||
assert(method_.isValid()); // onInterface() must be placed/called prior to this function
|
assert(method_.isValid()); // onInterface() must be placed/called prior to this function
|
||||||
|
|
||||||
auto asyncReplyHandler = [callback = std::forward<_Function>(callback)](MethodReply reply, std::optional<Error> error)
|
return proxy_.callMethodAsync(method_, makeAsyncReplyHandler(std::forward<_Function>(callback)), timeout_);
|
||||||
|
}
|
||||||
|
|
||||||
|
template <typename _Function>
|
||||||
|
[[nodiscard]] Slot AsyncMethodInvoker::uponReplyInvoke(_Function&& callback, return_slot_t)
|
||||||
|
{
|
||||||
|
assert(method_.isValid()); // onInterface() must be placed/called prior to this function
|
||||||
|
|
||||||
|
return proxy_.callMethodAsync( method_
|
||||||
|
, makeAsyncReplyHandler(std::forward<_Function>(callback))
|
||||||
|
, timeout_
|
||||||
|
, return_slot );
|
||||||
|
}
|
||||||
|
|
||||||
|
template <typename _Function>
|
||||||
|
inline async_reply_handler AsyncMethodInvoker::makeAsyncReplyHandler(_Function&& callback)
|
||||||
|
{
|
||||||
|
return [callback = std::forward<_Function>(callback)](MethodReply reply, std::optional<Error> error)
|
||||||
{
|
{
|
||||||
// Create a tuple of callback input arguments' types, which will be used
|
// Create a tuple of callback input arguments' types, which will be used
|
||||||
// as a storage for the argument values deserialized from the message.
|
// as a storage for the argument values deserialized from the message.
|
||||||
@ -312,8 +329,6 @@ namespace sdbus {
|
|||||||
// Invoke callback with input arguments from the tuple.
|
// Invoke callback with input arguments from the tuple.
|
||||||
sdbus::apply(callback, std::move(error), args);
|
sdbus::apply(callback, std::move(error), args);
|
||||||
};
|
};
|
||||||
|
|
||||||
return proxy_.callMethodAsync(method_, std::move(asyncReplyHandler), timeout_);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
template <typename... _Args>
|
template <typename... _Args>
|
||||||
@ -474,7 +489,8 @@ namespace sdbus {
|
|||||||
template <typename _Function>
|
template <typename _Function>
|
||||||
PendingAsyncCall AsyncPropertyGetter::uponReplyInvoke(_Function&& callback)
|
PendingAsyncCall AsyncPropertyGetter::uponReplyInvoke(_Function&& callback)
|
||||||
{
|
{
|
||||||
static_assert(std::is_invocable_r_v<void, _Function, std::optional<Error>, Variant>, "Property get callback function must accept std::optional<Error> and property value as Variant");
|
static_assert( std::is_invocable_r_v<void, _Function, std::optional<Error>, Variant>
|
||||||
|
, "Property get callback function must accept std::optional<Error> and property value as Variant" );
|
||||||
|
|
||||||
assert(!interfaceName_.empty()); // onInterface() must be placed/called prior to this function
|
assert(!interfaceName_.empty()); // onInterface() must be placed/called prior to this function
|
||||||
|
|
||||||
@ -484,6 +500,20 @@ namespace sdbus {
|
|||||||
.uponReplyInvoke(std::forward<_Function>(callback));
|
.uponReplyInvoke(std::forward<_Function>(callback));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
template <typename _Function>
|
||||||
|
[[nodiscard]] Slot AsyncPropertyGetter::uponReplyInvoke(_Function&& callback, return_slot_t)
|
||||||
|
{
|
||||||
|
static_assert( std::is_invocable_r_v<void, _Function, std::optional<Error>, Variant>
|
||||||
|
, "Property get callback function must accept std::optional<Error> and property value as Variant" );
|
||||||
|
|
||||||
|
assert(!interfaceName_.empty()); // onInterface() must be placed/called prior to this function
|
||||||
|
|
||||||
|
return proxy_.callMethodAsync("Get")
|
||||||
|
.onInterface(DBUS_PROPERTIES_INTERFACE_NAME)
|
||||||
|
.withArguments(interfaceName_, propertyName_)
|
||||||
|
.uponReplyInvoke(std::forward<_Function>(callback), return_slot);
|
||||||
|
}
|
||||||
|
|
||||||
inline std::future<Variant> AsyncPropertyGetter::getResultAsFuture()
|
inline std::future<Variant> AsyncPropertyGetter::getResultAsFuture()
|
||||||
{
|
{
|
||||||
assert(!interfaceName_.empty()); // onInterface() must be placed/called prior to this function
|
assert(!interfaceName_.empty()); // onInterface() must be placed/called prior to this function
|
||||||
@ -575,7 +605,8 @@ namespace sdbus {
|
|||||||
template <typename _Function>
|
template <typename _Function>
|
||||||
PendingAsyncCall AsyncPropertySetter::uponReplyInvoke(_Function&& callback)
|
PendingAsyncCall AsyncPropertySetter::uponReplyInvoke(_Function&& callback)
|
||||||
{
|
{
|
||||||
static_assert(std::is_invocable_r_v<void, _Function, std::optional<Error>>, "Property set callback function must accept std::optional<Error> only");
|
static_assert( std::is_invocable_r_v<void, _Function, std::optional<Error>>
|
||||||
|
, "Property set callback function must accept std::optional<Error> only" );
|
||||||
|
|
||||||
assert(!interfaceName_.empty()); // onInterface() must be placed/called prior to this function
|
assert(!interfaceName_.empty()); // onInterface() must be placed/called prior to this function
|
||||||
|
|
||||||
@ -585,6 +616,20 @@ namespace sdbus {
|
|||||||
.uponReplyInvoke(std::forward<_Function>(callback));
|
.uponReplyInvoke(std::forward<_Function>(callback));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
template <typename _Function>
|
||||||
|
[[nodiscard]] Slot AsyncPropertySetter::uponReplyInvoke(_Function&& callback, return_slot_t)
|
||||||
|
{
|
||||||
|
static_assert( std::is_invocable_r_v<void, _Function, std::optional<Error>>
|
||||||
|
, "Property set callback function must accept std::optional<Error> only" );
|
||||||
|
|
||||||
|
assert(!interfaceName_.empty()); // onInterface() must be placed/called prior to this function
|
||||||
|
|
||||||
|
return proxy_.callMethodAsync("Set")
|
||||||
|
.onInterface(DBUS_PROPERTIES_INTERFACE_NAME)
|
||||||
|
.withArguments(interfaceName_, propertyName_, std::move(value_))
|
||||||
|
.uponReplyInvoke(std::forward<_Function>(callback), return_slot);
|
||||||
|
}
|
||||||
|
|
||||||
inline std::future<void> AsyncPropertySetter::getResultAsFuture()
|
inline std::future<void> AsyncPropertySetter::getResultAsFuture()
|
||||||
{
|
{
|
||||||
assert(!interfaceName_.empty()); // onInterface() must be placed/called prior to this function
|
assert(!interfaceName_.empty()); // onInterface() must be placed/called prior to this function
|
||||||
@ -644,6 +689,20 @@ namespace sdbus {
|
|||||||
.uponReplyInvoke(std::forward<_Function>(callback));
|
.uponReplyInvoke(std::forward<_Function>(callback));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
template <typename _Function>
|
||||||
|
[[nodiscard]] Slot AsyncAllPropertiesGetter::uponReplyInvoke(_Function&& callback, return_slot_t)
|
||||||
|
{
|
||||||
|
static_assert( std::is_invocable_r_v<void, _Function, std::optional<Error>, std::map<PropertyName, Variant>>
|
||||||
|
, "All properties get callback function must accept std::optional<Error> and a map of property names to their values" );
|
||||||
|
|
||||||
|
assert(!interfaceName_.empty()); // onInterface() must be placed/called prior to this function
|
||||||
|
|
||||||
|
return proxy_.callMethodAsync("GetAll")
|
||||||
|
.onInterface(DBUS_PROPERTIES_INTERFACE_NAME)
|
||||||
|
.withArguments(interfaceName_)
|
||||||
|
.uponReplyInvoke(std::forward<_Function>(callback), return_slot);
|
||||||
|
}
|
||||||
|
|
||||||
inline std::future<std::map<PropertyName, Variant>> AsyncAllPropertiesGetter::getResultAsFuture()
|
inline std::future<std::map<PropertyName, Variant>> AsyncAllPropertiesGetter::getResultAsFuture()
|
||||||
{
|
{
|
||||||
assert(!interfaceName_.empty()); // onInterface() must be placed/called prior to this function
|
assert(!interfaceName_.empty()); // onInterface() must be placed/called prior to this function
|
||||||
|
@ -89,7 +89,6 @@ namespace sdbus {
|
|||||||
* @brief Calls method on the remote D-Bus object
|
* @brief Calls method on the remote D-Bus object
|
||||||
*
|
*
|
||||||
* @param[in] message Message representing a method call
|
* @param[in] message Message representing a method call
|
||||||
* @param[in] timeout Timeout for dbus call in microseconds
|
|
||||||
* @return A method reply message
|
* @return A method reply message
|
||||||
*
|
*
|
||||||
* The call does not block if the method call has dont-expect-reply flag set. In that case,
|
* The call does not block if the method call has dont-expect-reply flag set. In that case,
|
||||||
@ -108,11 +107,44 @@ namespace sdbus {
|
|||||||
* its own bus connection. So-called light-weight proxies (ones created with `dont_run_event_loop_thread`
|
* its own bus connection. So-called light-weight proxies (ones created with `dont_run_event_loop_thread`
|
||||||
* tag are designed for exactly that purpose.
|
* tag are designed for exactly that purpose.
|
||||||
*
|
*
|
||||||
|
* The default D-Bus method call timeout is used. See IConnection::getMethodCallTimeout().
|
||||||
|
*
|
||||||
* Note: To avoid messing with messages, use API on a higher level of abstraction defined below.
|
* Note: To avoid messing with messages, use API on a higher level of abstraction defined below.
|
||||||
*
|
*
|
||||||
* @throws sdbus::Error in case of failure (also in case the remote function returned an error)
|
* @throws sdbus::Error in case of failure (also in case the remote function returned an error)
|
||||||
*/
|
*/
|
||||||
virtual MethodReply callMethod(const MethodCall& message, uint64_t timeout = 0) = 0;
|
virtual MethodReply callMethod(const MethodCall& message) = 0;
|
||||||
|
|
||||||
|
/*!
|
||||||
|
* @brief Calls method on the remote D-Bus object
|
||||||
|
*
|
||||||
|
* @param[in] message Message representing a method call
|
||||||
|
* @param[in] timeout Method call timeout (in microseconds)
|
||||||
|
* @return A method reply message
|
||||||
|
*
|
||||||
|
* The call does not block if the method call has dont-expect-reply flag set. In that case,
|
||||||
|
* the call returns immediately and the return value is an empty, invalid method reply.
|
||||||
|
*
|
||||||
|
* The call blocks otherwise, waiting for the remote peer to send back a reply or an error,
|
||||||
|
* or until the call times out.
|
||||||
|
*
|
||||||
|
* While blocking, other concurrent operations (in other threads) on the underlying bus
|
||||||
|
* connection are stalled until the call returns. This is not an issue in vast majority of
|
||||||
|
* (simple, single-threaded) applications. In asynchronous, multi-threaded designs involving
|
||||||
|
* shared bus connections, this may be an issue. It is advised to instead use an asynchronous
|
||||||
|
* callMethod() function overload, which does not block the bus connection, or do the synchronous
|
||||||
|
* call from another Proxy instance created just before the call and then destroyed (which is
|
||||||
|
* anyway quite a typical approach in D-Bus implementations). Such proxy instance must have
|
||||||
|
* its own bus connection. So-called light-weight proxies (ones created with `dont_run_event_loop_thread`
|
||||||
|
* tag are designed for exactly that purpose.
|
||||||
|
*
|
||||||
|
* If timeout is zero, the default D-Bus method call timeout is used. See IConnection::getMethodCallTimeout().
|
||||||
|
*
|
||||||
|
* Note: To avoid messing with messages, use API on a higher level of abstraction defined below.
|
||||||
|
*
|
||||||
|
* @throws sdbus::Error in case of failure (also in case the remote function returned an error)
|
||||||
|
*/
|
||||||
|
virtual MethodReply callMethod(const MethodCall& message, uint64_t timeout) = 0;
|
||||||
|
|
||||||
/*!
|
/*!
|
||||||
* @copydoc IProxy::callMethod(const MethodCall&,uint64_t)
|
* @copydoc IProxy::callMethod(const MethodCall&,uint64_t)
|
||||||
@ -125,8 +157,7 @@ namespace sdbus {
|
|||||||
*
|
*
|
||||||
* @param[in] message Message representing an async method call
|
* @param[in] message Message representing an async method call
|
||||||
* @param[in] asyncReplyCallback Handler for the async reply
|
* @param[in] asyncReplyCallback Handler for the async reply
|
||||||
* @param[in] timeout Timeout for dbus call in microseconds
|
* @return Observing handle for the the pending asynchronous call
|
||||||
* @return Cookie for the the pending asynchronous call
|
|
||||||
*
|
*
|
||||||
* This is a callback-based way of asynchronously calling a remote D-Bus method.
|
* This is a callback-based way of asynchronously calling a remote D-Bus method.
|
||||||
*
|
*
|
||||||
@ -134,13 +165,95 @@ namespace sdbus {
|
|||||||
* the provided async reply handler will get invoked from the context of the bus
|
* the provided async reply handler will get invoked from the context of the bus
|
||||||
* connection I/O event loop thread.
|
* connection I/O event loop thread.
|
||||||
*
|
*
|
||||||
|
* An non-owning, observing async call handle is returned that can be used to query call status or cancel the call.
|
||||||
|
*
|
||||||
|
* The default D-Bus method call timeout is used. See IConnection::getMethodCallTimeout().
|
||||||
|
*
|
||||||
|
* Note: To avoid messing with messages, use API on a higher level of abstraction defined below.
|
||||||
|
*
|
||||||
|
* @throws sdbus::Error in case of failure
|
||||||
|
*/
|
||||||
|
virtual PendingAsyncCall callMethodAsync(const MethodCall& message, async_reply_handler asyncReplyCallback) = 0;
|
||||||
|
|
||||||
|
/*!
|
||||||
|
* @brief Calls method on the D-Bus object asynchronously
|
||||||
|
*
|
||||||
|
* @param[in] message Message representing an async method call
|
||||||
|
* @param[in] asyncReplyCallback Handler for the async reply
|
||||||
|
* @return RAII-style slot handle representing the ownership of the async call
|
||||||
|
*
|
||||||
|
* This is a callback-based way of asynchronously calling a remote D-Bus method.
|
||||||
|
*
|
||||||
|
* The call itself is non-blocking. It doesn't wait for the reply. Once the reply arrives,
|
||||||
|
* the provided async reply handler will get invoked from the context of the bus
|
||||||
|
* connection I/O event loop thread.
|
||||||
|
*
|
||||||
|
* A slot (an owning handle) is returned for the async call. Lifetime of the call is bound to the lifetime of the slot.
|
||||||
|
* The slot can be used to cancel the method call at a later time by simply destroying it.
|
||||||
|
*
|
||||||
|
* The default D-Bus method call timeout is used. See IConnection::getMethodCallTimeout().
|
||||||
|
*
|
||||||
|
* Note: To avoid messing with messages, use API on a higher level of abstraction defined below.
|
||||||
|
*
|
||||||
|
* @throws sdbus::Error in case of failure
|
||||||
|
*/
|
||||||
|
[[nodiscard]] virtual Slot callMethodAsync( const MethodCall& message
|
||||||
|
, async_reply_handler asyncReplyCallback
|
||||||
|
, return_slot_t ) = 0;
|
||||||
|
|
||||||
|
/*!
|
||||||
|
* @brief Calls method on the D-Bus object asynchronously, with custom timeout
|
||||||
|
*
|
||||||
|
* @param[in] message Message representing an async method call
|
||||||
|
* @param[in] asyncReplyCallback Handler for the async reply
|
||||||
|
* @param[in] timeout Method call timeout (in microseconds)
|
||||||
|
* @return Observing handle for the the pending asynchronous call
|
||||||
|
*
|
||||||
|
* This is a callback-based way of asynchronously calling a remote D-Bus method.
|
||||||
|
*
|
||||||
|
* The call itself is non-blocking. It doesn't wait for the reply. Once the reply arrives,
|
||||||
|
* the provided async reply handler will get invoked from the context of the bus
|
||||||
|
* connection I/O event loop thread.
|
||||||
|
*
|
||||||
|
* An non-owning, observing async call handle is returned that can be used to query call status or cancel the call.
|
||||||
|
*
|
||||||
|
* If timeout is zero, the default D-Bus method call timeout is used. See IConnection::getMethodCallTimeout().
|
||||||
|
*
|
||||||
* Note: To avoid messing with messages, use API on a higher level of abstraction defined below.
|
* Note: To avoid messing with messages, use API on a higher level of abstraction defined below.
|
||||||
*
|
*
|
||||||
* @throws sdbus::Error in case of failure
|
* @throws sdbus::Error in case of failure
|
||||||
*/
|
*/
|
||||||
virtual PendingAsyncCall callMethodAsync( const MethodCall& message
|
virtual PendingAsyncCall callMethodAsync( const MethodCall& message
|
||||||
, async_reply_handler asyncReplyCallback
|
, async_reply_handler asyncReplyCallback
|
||||||
, uint64_t timeout = 0 ) = 0;
|
, uint64_t timeout ) = 0;
|
||||||
|
|
||||||
|
/*!
|
||||||
|
* @brief Calls method on the D-Bus object asynchronously, with custom timeout
|
||||||
|
*
|
||||||
|
* @param[in] message Message representing an async method call
|
||||||
|
* @param[in] asyncReplyCallback Handler for the async reply
|
||||||
|
* @param[in] timeout Method call timeout (in microseconds)
|
||||||
|
* @return RAII-style slot handle representing the ownership of the async call
|
||||||
|
*
|
||||||
|
* This is a callback-based way of asynchronously calling a remote D-Bus method.
|
||||||
|
*
|
||||||
|
* The call itself is non-blocking. It doesn't wait for the reply. Once the reply arrives,
|
||||||
|
* the provided async reply handler will get invoked from the context of the bus
|
||||||
|
* connection I/O event loop thread.
|
||||||
|
*
|
||||||
|
* A slot (an owning handle) is returned for the async call. Lifetime of the call is bound to the lifetime of the slot.
|
||||||
|
* The slot can be used to cancel the method call at a later time by simply destroying it.
|
||||||
|
*
|
||||||
|
* If timeout is zero, the default D-Bus method call timeout is used. See IConnection::getMethodCallTimeout().
|
||||||
|
*
|
||||||
|
* Note: To avoid messing with messages, use API on a higher level of abstraction defined below.
|
||||||
|
*
|
||||||
|
* @throws sdbus::Error in case of failure
|
||||||
|
*/
|
||||||
|
[[nodiscard]] virtual Slot callMethodAsync( const MethodCall& message
|
||||||
|
, async_reply_handler asyncReplyCallback
|
||||||
|
, uint64_t timeout
|
||||||
|
, return_slot_t ) = 0;
|
||||||
|
|
||||||
/*!
|
/*!
|
||||||
* @copydoc IProxy::callMethod(const MethodCall&,async_reply_handler,uint64_t)
|
* @copydoc IProxy::callMethod(const MethodCall&,async_reply_handler,uint64_t)
|
||||||
@ -150,6 +263,15 @@ namespace sdbus {
|
|||||||
, async_reply_handler asyncReplyCallback
|
, async_reply_handler asyncReplyCallback
|
||||||
, const std::chrono::duration<_Rep, _Period>& timeout );
|
, const std::chrono::duration<_Rep, _Period>& timeout );
|
||||||
|
|
||||||
|
/*!
|
||||||
|
* @copydoc IProxy::callMethod(const MethodCall&,async_reply_handler,uint64_t,return_slot_t)
|
||||||
|
*/
|
||||||
|
template <typename _Rep, typename _Period>
|
||||||
|
[[nodiscard]] Slot callMethodAsync( const MethodCall& message
|
||||||
|
, async_reply_handler asyncReplyCallback
|
||||||
|
, const std::chrono::duration<_Rep, _Period>& timeout
|
||||||
|
, return_slot_t );
|
||||||
|
|
||||||
/*!
|
/*!
|
||||||
* @brief Calls method on the D-Bus object asynchronously
|
* @brief Calls method on the D-Bus object asynchronously
|
||||||
*
|
*
|
||||||
@ -163,6 +285,8 @@ namespace sdbus {
|
|||||||
* the provided future object will be set to contain the reply (or sdbus::Error
|
* the provided future object will be set to contain the reply (or sdbus::Error
|
||||||
* in case the remote method threw an exception).
|
* in case the remote method threw an exception).
|
||||||
*
|
*
|
||||||
|
* The default D-Bus method call timeout is used. See IConnection::getMethodCallTimeout().
|
||||||
|
*
|
||||||
* Note: To avoid messing with messages, use higher-level API defined below.
|
* Note: To avoid messing with messages, use higher-level API defined below.
|
||||||
*
|
*
|
||||||
* @throws sdbus::Error in case of failure
|
* @throws sdbus::Error in case of failure
|
||||||
@ -183,6 +307,8 @@ namespace sdbus {
|
|||||||
* the provided future object will be set to contain the reply (or sdbus::Error
|
* the provided future object will be set to contain the reply (or sdbus::Error
|
||||||
* in case the remote method threw an exception, or the call timed out).
|
* in case the remote method threw an exception, or the call timed out).
|
||||||
*
|
*
|
||||||
|
* If timeout is zero, the default D-Bus method call timeout is used. See IConnection::getMethodCallTimeout().
|
||||||
|
*
|
||||||
* Note: To avoid messing with messages, use higher-level API defined below.
|
* Note: To avoid messing with messages, use higher-level API defined below.
|
||||||
*
|
*
|
||||||
* @throws sdbus::Error in case of failure
|
* @throws sdbus::Error in case of failure
|
||||||
@ -273,8 +399,12 @@ namespace sdbus {
|
|||||||
* @param[in] signalName Name of the signal
|
* @param[in] signalName Name of the signal
|
||||||
* @param[in] signalHandler Callback that implements the body of the signal handler
|
* @param[in] signalHandler Callback that implements the body of the signal handler
|
||||||
*
|
*
|
||||||
* A signal can be subscribed to and unsubscribed from at any time during proxy
|
* A signal can be subscribed to at any time during proxy lifetime. The subscription
|
||||||
* lifetime. The subscription is active immediately after the call.
|
* is active immediately after the call, and stays active for the entire lifetime
|
||||||
|
* of the Proxy object.
|
||||||
|
*
|
||||||
|
* To be able to unsubscribe from the signal at a later time, use the registerSignalHandler()
|
||||||
|
* overload with request_slot tag.
|
||||||
*
|
*
|
||||||
* @throws sdbus::Error in case of failure
|
* @throws sdbus::Error in case of failure
|
||||||
*/
|
*/
|
||||||
@ -292,8 +422,9 @@ namespace sdbus {
|
|||||||
* @return RAII-style slot handle representing the ownership of the subscription
|
* @return RAII-style slot handle representing the ownership of the subscription
|
||||||
*
|
*
|
||||||
* A signal can be subscribed to and unsubscribed from at any time during proxy
|
* A signal can be subscribed to and unsubscribed from at any time during proxy
|
||||||
* lifetime. The subscription is active immediately after the call. The subscription
|
* lifetime. The subscription is active immediately after the call. The lifetime
|
||||||
* is unregistered when the client destroys the returned slot object.
|
* of the subscription is bound to the lifetime of the slot object. The subscription
|
||||||
|
* is unregistered by letting go of the slot object.
|
||||||
*
|
*
|
||||||
* @throws sdbus::Error in case of failure
|
* @throws sdbus::Error in case of failure
|
||||||
*/
|
*/
|
||||||
@ -566,10 +697,10 @@ namespace sdbus {
|
|||||||
|
|
||||||
private:
|
private:
|
||||||
friend internal::Proxy;
|
friend internal::Proxy;
|
||||||
PendingAsyncCall(std::weak_ptr<void> callData);
|
PendingAsyncCall(std::weak_ptr<void> callInfo);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
std::weak_ptr<void> callData_;
|
std::weak_ptr<void> callInfo_;
|
||||||
};
|
};
|
||||||
|
|
||||||
// Out-of-line member definitions
|
// Out-of-line member definitions
|
||||||
@ -590,6 +721,16 @@ namespace sdbus {
|
|||||||
return callMethodAsync(message, std::move(asyncReplyCallback), microsecs.count());
|
return callMethodAsync(message, std::move(asyncReplyCallback), microsecs.count());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
template <typename _Rep, typename _Period>
|
||||||
|
inline Slot IProxy::callMethodAsync( const MethodCall& message
|
||||||
|
, async_reply_handler asyncReplyCallback
|
||||||
|
, const std::chrono::duration<_Rep, _Period>& timeout
|
||||||
|
, return_slot_t )
|
||||||
|
{
|
||||||
|
auto microsecs = std::chrono::duration_cast<std::chrono::microseconds>(timeout);
|
||||||
|
return callMethodAsync(message, std::move(asyncReplyCallback), microsecs.count(), return_slot);
|
||||||
|
}
|
||||||
|
|
||||||
template <typename _Rep, typename _Period>
|
template <typename _Rep, typename _Period>
|
||||||
inline std::future<MethodReply> IProxy::callMethodAsync( const MethodCall& message
|
inline std::future<MethodReply> IProxy::callMethodAsync( const MethodCall& message
|
||||||
, const std::chrono::duration<_Rep, _Period>& timeout
|
, const std::chrono::duration<_Rep, _Period>& timeout
|
||||||
|
@ -162,12 +162,24 @@ namespace sdbus {
|
|||||||
return proxy_->getPropertyAsync(propertyName).onInterface(interfaceName).uponReplyInvoke(std::forward<_Function>(callback));
|
return proxy_->getPropertyAsync(propertyName).onInterface(interfaceName).uponReplyInvoke(std::forward<_Function>(callback));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
template <typename _Function>
|
||||||
|
[[nodiscard]] Slot GetAsync(const InterfaceName& interfaceName, const PropertyName& propertyName, _Function&& callback, return_slot_t)
|
||||||
|
{
|
||||||
|
return proxy_->getPropertyAsync(propertyName).onInterface(interfaceName).uponReplyInvoke(std::forward<_Function>(callback), return_slot);
|
||||||
|
}
|
||||||
|
|
||||||
template <typename _Function>
|
template <typename _Function>
|
||||||
PendingAsyncCall GetAsync(std::string_view interfaceName, std::string_view propertyName, _Function&& callback)
|
PendingAsyncCall GetAsync(std::string_view interfaceName, std::string_view propertyName, _Function&& callback)
|
||||||
{
|
{
|
||||||
return proxy_->getPropertyAsync(propertyName).onInterface(interfaceName).uponReplyInvoke(std::forward<_Function>(callback));
|
return proxy_->getPropertyAsync(propertyName).onInterface(interfaceName).uponReplyInvoke(std::forward<_Function>(callback));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
template <typename _Function>
|
||||||
|
[[nodiscard]] Slot GetAsync(std::string_view interfaceName, std::string_view propertyName, _Function&& callback, return_slot_t)
|
||||||
|
{
|
||||||
|
return proxy_->getPropertyAsync(propertyName).onInterface(interfaceName).uponReplyInvoke(std::forward<_Function>(callback), return_slot);
|
||||||
|
}
|
||||||
|
|
||||||
std::future<sdbus::Variant> GetAsync(const InterfaceName& interfaceName, const PropertyName& propertyName, with_future_t)
|
std::future<sdbus::Variant> GetAsync(const InterfaceName& interfaceName, const PropertyName& propertyName, with_future_t)
|
||||||
{
|
{
|
||||||
return proxy_->getPropertyAsync(propertyName).onInterface(interfaceName).getResultAsFuture();
|
return proxy_->getPropertyAsync(propertyName).onInterface(interfaceName).getResultAsFuture();
|
||||||
@ -204,12 +216,24 @@ namespace sdbus {
|
|||||||
return proxy_->setPropertyAsync(propertyName).onInterface(interfaceName).toValue(value).uponReplyInvoke(std::forward<_Function>(callback));
|
return proxy_->setPropertyAsync(propertyName).onInterface(interfaceName).toValue(value).uponReplyInvoke(std::forward<_Function>(callback));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
template <typename _Function>
|
||||||
|
PendingAsyncCall SetAsync(const InterfaceName& interfaceName, const PropertyName& propertyName, const sdbus::Variant& value, _Function&& callback, return_slot_t)
|
||||||
|
{
|
||||||
|
return proxy_->setPropertyAsync(propertyName).onInterface(interfaceName).toValue(value).uponReplyInvoke(std::forward<_Function>(callback), return_slot);
|
||||||
|
}
|
||||||
|
|
||||||
template <typename _Function>
|
template <typename _Function>
|
||||||
PendingAsyncCall SetAsync(std::string_view interfaceName, std::string_view propertyName, const sdbus::Variant& value, _Function&& callback)
|
PendingAsyncCall SetAsync(std::string_view interfaceName, std::string_view propertyName, const sdbus::Variant& value, _Function&& callback)
|
||||||
{
|
{
|
||||||
return proxy_->setPropertyAsync(propertyName).onInterface(interfaceName).toValue(value).uponReplyInvoke(std::forward<_Function>(callback));
|
return proxy_->setPropertyAsync(propertyName).onInterface(interfaceName).toValue(value).uponReplyInvoke(std::forward<_Function>(callback));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
template <typename _Function>
|
||||||
|
PendingAsyncCall SetAsync(std::string_view interfaceName, std::string_view propertyName, const sdbus::Variant& value, _Function&& callback, return_slot_t)
|
||||||
|
{
|
||||||
|
return proxy_->setPropertyAsync(propertyName).onInterface(interfaceName).toValue(value).uponReplyInvoke(std::forward<_Function>(callback), return_slot);
|
||||||
|
}
|
||||||
|
|
||||||
std::future<void> SetAsync(const InterfaceName& interfaceName, const PropertyName& propertyName, const sdbus::Variant& value, with_future_t)
|
std::future<void> SetAsync(const InterfaceName& interfaceName, const PropertyName& propertyName, const sdbus::Variant& value, with_future_t)
|
||||||
{
|
{
|
||||||
return proxy_->setPropertyAsync(propertyName).onInterface(interfaceName).toValue(value).getResultAsFuture();
|
return proxy_->setPropertyAsync(propertyName).onInterface(interfaceName).toValue(value).getResultAsFuture();
|
||||||
@ -236,12 +260,24 @@ namespace sdbus {
|
|||||||
return proxy_->getAllPropertiesAsync().onInterface(interfaceName).uponReplyInvoke(std::forward<_Function>(callback));
|
return proxy_->getAllPropertiesAsync().onInterface(interfaceName).uponReplyInvoke(std::forward<_Function>(callback));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
template <typename _Function>
|
||||||
|
PendingAsyncCall GetAllAsync(const InterfaceName& interfaceName, _Function&& callback, return_slot_t)
|
||||||
|
{
|
||||||
|
return proxy_->getAllPropertiesAsync().onInterface(interfaceName).uponReplyInvoke(std::forward<_Function>(callback), return_slot);
|
||||||
|
}
|
||||||
|
|
||||||
template <typename _Function>
|
template <typename _Function>
|
||||||
PendingAsyncCall GetAllAsync(std::string_view interfaceName, _Function&& callback)
|
PendingAsyncCall GetAllAsync(std::string_view interfaceName, _Function&& callback)
|
||||||
{
|
{
|
||||||
return proxy_->getAllPropertiesAsync().onInterface(interfaceName).uponReplyInvoke(std::forward<_Function>(callback));
|
return proxy_->getAllPropertiesAsync().onInterface(interfaceName).uponReplyInvoke(std::forward<_Function>(callback));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
template <typename _Function>
|
||||||
|
PendingAsyncCall GetAllAsync(std::string_view interfaceName, _Function&& callback, return_slot_t)
|
||||||
|
{
|
||||||
|
return proxy_->getAllPropertiesAsync().onInterface(interfaceName).uponReplyInvoke(std::forward<_Function>(callback), return_slot);
|
||||||
|
}
|
||||||
|
|
||||||
std::future<std::map<PropertyName, sdbus::Variant>> GetAllAsync(const InterfaceName& interfaceName, with_future_t)
|
std::future<std::map<PropertyName, sdbus::Variant>> GetAllAsync(const InterfaceName& interfaceName, with_future_t)
|
||||||
{
|
{
|
||||||
return proxy_->getAllPropertiesAsync().onInterface(interfaceName).getResultAsFuture();
|
return proxy_->getAllPropertiesAsync().onInterface(interfaceName).getResultAsFuture();
|
||||||
|
@ -84,7 +84,7 @@ namespace sdbus {
|
|||||||
// Type-erased RAII-style handle to callbacks/subscriptions registered to sdbus-c++
|
// Type-erased RAII-style handle to callbacks/subscriptions registered to sdbus-c++
|
||||||
using Slot = std::unique_ptr<void, std::function<void(void*)>>;
|
using Slot = std::unique_ptr<void, std::function<void(void*)>>;
|
||||||
|
|
||||||
// Tag specifying that an owning slot handle shall be returned from a registration/subscription function to the caller
|
// Tag specifying that an owning handle (so-called slot) of the logical resource shall be provided to the client
|
||||||
struct return_slot_t { explicit return_slot_t() = default; };
|
struct return_slot_t { explicit return_slot_t() = default; };
|
||||||
inline constexpr return_slot_t return_slot{};
|
inline constexpr return_slot_t return_slot{};
|
||||||
// Tag specifying that the library shall own the slot resulting from the call of the function (so-called floating slot)
|
// Tag specifying that the library shall own the slot resulting from the call of the function (so-called floating slot)
|
||||||
|
113
src/Proxy.cpp
113
src/Proxy.cpp
@ -94,6 +94,11 @@ MethodCall Proxy::createMethodCall(const char* interfaceName, const char* method
|
|||||||
return connection_->createMethodCall(destination_.c_str(), objectPath_.c_str(), interfaceName, methodName);
|
return connection_->createMethodCall(destination_.c_str(), objectPath_.c_str(), interfaceName, methodName);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
MethodReply Proxy::callMethod(const MethodCall& message)
|
||||||
|
{
|
||||||
|
return Proxy::callMethod(message, /*timeout*/ 0);
|
||||||
|
}
|
||||||
|
|
||||||
MethodReply Proxy::callMethod(const MethodCall& message, uint64_t timeout)
|
MethodReply Proxy::callMethod(const MethodCall& message, uint64_t timeout)
|
||||||
{
|
{
|
||||||
SDBUS_THROW_ERROR_IF(!message.isValid(), "Invalid method call message provided", EINVAL);
|
SDBUS_THROW_ERROR_IF(!message.isValid(), "Invalid method call message provided", EINVAL);
|
||||||
@ -101,19 +106,44 @@ MethodReply Proxy::callMethod(const MethodCall& message, uint64_t timeout)
|
|||||||
return connection_->callMethod(message, timeout);
|
return connection_->callMethod(message, timeout);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
PendingAsyncCall Proxy::callMethodAsync(const MethodCall& message, async_reply_handler asyncReplyCallback)
|
||||||
|
{
|
||||||
|
return Proxy::callMethodAsync(message, std::move(asyncReplyCallback), /*timeout*/ 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
Slot Proxy::callMethodAsync(const MethodCall& message, async_reply_handler asyncReplyCallback, return_slot_t)
|
||||||
|
{
|
||||||
|
return Proxy::callMethodAsync(message, std::move(asyncReplyCallback), /*timeout*/ 0, return_slot);
|
||||||
|
}
|
||||||
|
|
||||||
PendingAsyncCall Proxy::callMethodAsync(const MethodCall& message, async_reply_handler asyncReplyCallback, uint64_t timeout)
|
PendingAsyncCall Proxy::callMethodAsync(const MethodCall& message, async_reply_handler asyncReplyCallback, uint64_t timeout)
|
||||||
{
|
{
|
||||||
SDBUS_THROW_ERROR_IF(!message.isValid(), "Invalid async method call message provided", EINVAL);
|
SDBUS_THROW_ERROR_IF(!message.isValid(), "Invalid async method call message provided", EINVAL);
|
||||||
|
|
||||||
auto callback = (void*)&Proxy::sdbus_async_reply_handler;
|
auto asyncCallInfo = std::make_shared<AsyncCallInfo>(AsyncCallInfo{ .callback = std::move(asyncReplyCallback)
|
||||||
auto callData = std::make_shared<AsyncCalls::CallData>(AsyncCalls::CallData{*this, std::move(asyncReplyCallback)});
|
, .proxy = *this
|
||||||
auto weakData = std::weak_ptr<AsyncCalls::CallData>{callData};
|
, .floating = false });
|
||||||
|
|
||||||
callData->slot = connection_->callMethod(message, callback, callData.get(), timeout);
|
asyncCallInfo->slot = connection_->callMethod(message, (void*)&Proxy::sdbus_async_reply_handler, asyncCallInfo.get(), timeout);
|
||||||
|
|
||||||
pendingAsyncCalls_.addCall(std::move(callData));
|
auto asyncCallInfoWeakPtr = std::weak_ptr{asyncCallInfo};
|
||||||
|
|
||||||
return {weakData};
|
floatingAsyncCallSlots_.push_back(std::move(asyncCallInfo));
|
||||||
|
|
||||||
|
return {asyncCallInfoWeakPtr};
|
||||||
|
}
|
||||||
|
|
||||||
|
Slot Proxy::callMethodAsync(const MethodCall& message, async_reply_handler asyncReplyCallback, uint64_t timeout, return_slot_t)
|
||||||
|
{
|
||||||
|
SDBUS_THROW_ERROR_IF(!message.isValid(), "Invalid async method call message provided", EINVAL);
|
||||||
|
|
||||||
|
auto asyncCallInfo = std::make_unique<AsyncCallInfo>(AsyncCallInfo{ .callback = std::move(asyncReplyCallback)
|
||||||
|
, .proxy = *this
|
||||||
|
, .floating = true });
|
||||||
|
|
||||||
|
asyncCallInfo->slot = connection_->callMethod(message, (void*)&Proxy::sdbus_async_reply_handler, asyncCallInfo.get(), timeout);
|
||||||
|
|
||||||
|
return {asyncCallInfo.release(), [](void *ptr){ delete static_cast<AsyncCallInfo*>(ptr); }};
|
||||||
}
|
}
|
||||||
|
|
||||||
std::future<MethodReply> Proxy::callMethodAsync(const MethodCall& message, with_future_t)
|
std::future<MethodReply> Proxy::callMethodAsync(const MethodCall& message, with_future_t)
|
||||||
@ -186,7 +216,7 @@ Slot Proxy::registerSignalHandler( const char* interfaceName
|
|||||||
|
|
||||||
void Proxy::unregister()
|
void Proxy::unregister()
|
||||||
{
|
{
|
||||||
pendingAsyncCalls_.clear();
|
floatingAsyncCallSlots_.clear();
|
||||||
floatingSignalSlots_.clear();
|
floatingSignalSlots_.clear();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -207,17 +237,17 @@ Message Proxy::getCurrentlyProcessedMessage() const
|
|||||||
|
|
||||||
int Proxy::sdbus_async_reply_handler(sd_bus_message *sdbusMessage, void *userData, sd_bus_error *retError)
|
int Proxy::sdbus_async_reply_handler(sd_bus_message *sdbusMessage, void *userData, sd_bus_error *retError)
|
||||||
{
|
{
|
||||||
auto* asyncCallData = static_cast<AsyncCalls::CallData*>(userData);
|
auto* asyncCallInfo = static_cast<AsyncCallInfo*>(userData);
|
||||||
assert(asyncCallData != nullptr);
|
assert(asyncCallInfo != nullptr);
|
||||||
assert(asyncCallData->callback);
|
assert(asyncCallInfo->callback);
|
||||||
auto& proxy = asyncCallData->proxy;
|
auto& proxy = asyncCallInfo->proxy;
|
||||||
|
|
||||||
// We are removing the CallData item at the complete scope exit, after the callback has been invoked.
|
// We are removing the CallData item at the complete scope exit, after the callback has been invoked.
|
||||||
// We can't do it earlier (before callback invocation for example), because CallBack data (slot release)
|
// We can't do it earlier (before callback invocation for example), because CallBack data (slot release)
|
||||||
// is the synchronization point between callback invocation and Proxy::unregister.
|
// is the synchronization point between callback invocation and Proxy::unregister.
|
||||||
SCOPE_EXIT
|
SCOPE_EXIT
|
||||||
{
|
{
|
||||||
proxy.pendingAsyncCalls_.removeCall(asyncCallData);
|
proxy.floatingAsyncCallSlots_.erase(asyncCallInfo);
|
||||||
};
|
};
|
||||||
|
|
||||||
auto message = Message::Factory::create<MethodReply>(sdbusMessage, &proxy.connection_->getSdBusInterface());
|
auto message = Message::Factory::create<MethodReply>(sdbusMessage, &proxy.connection_->getSdBusInterface());
|
||||||
@ -227,12 +257,12 @@ int Proxy::sdbus_async_reply_handler(sd_bus_message *sdbusMessage, void *userDat
|
|||||||
const auto* error = sd_bus_message_get_error(sdbusMessage);
|
const auto* error = sd_bus_message_get_error(sdbusMessage);
|
||||||
if (error == nullptr)
|
if (error == nullptr)
|
||||||
{
|
{
|
||||||
asyncCallData->callback(std::move(message), {});
|
asyncCallInfo->callback(std::move(message), {});
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
Error exception(Error::Name{error->name}, error->message);
|
Error exception(Error::Name{error->name}, error->message);
|
||||||
asyncCallData->callback(std::move(message), std::move(exception));
|
asyncCallInfo->callback(std::move(message), std::move(exception));
|
||||||
}
|
}
|
||||||
}, retError);
|
}, retError);
|
||||||
|
|
||||||
@ -253,21 +283,64 @@ int Proxy::sdbus_signal_handler(sd_bus_message *sdbusMessage, void *userData, sd
|
|||||||
return ok ? 0 : -1;
|
return ok ? 0 : -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Proxy::FloatingAsyncCallSlots::~FloatingAsyncCallSlots()
|
||||||
|
{
|
||||||
|
clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
void Proxy::FloatingAsyncCallSlots::push_back(std::shared_ptr<AsyncCallInfo> asyncCallInfo)
|
||||||
|
{
|
||||||
|
std::lock_guard lock(mutex_);
|
||||||
|
if (!asyncCallInfo->finished) // The call may have finished in the meantime
|
||||||
|
slots_.emplace_back(std::move(asyncCallInfo));
|
||||||
|
}
|
||||||
|
|
||||||
|
void Proxy::FloatingAsyncCallSlots::erase(AsyncCallInfo* info)
|
||||||
|
{
|
||||||
|
std::unique_lock lock(mutex_);
|
||||||
|
info->finished = true;
|
||||||
|
auto it = std::find_if(slots_.begin(), slots_.end(), [info](auto const& entry){ return entry.get() == info; });
|
||||||
|
if (it != slots_.end())
|
||||||
|
{
|
||||||
|
auto callInfo = std::move(*it);
|
||||||
|
slots_.erase(it);
|
||||||
|
lock.unlock();
|
||||||
|
|
||||||
|
// Releasing call slot pointer acquires global sd-bus mutex. We have to perform the release
|
||||||
|
// out of the `mutex_' critical section here, because if the `removeCall` is called by some
|
||||||
|
// thread and at the same time Proxy's async reply handler (which already holds global sd-bus
|
||||||
|
// mutex) is in progress in a different thread, we get double-mutex deadlock.
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void Proxy::FloatingAsyncCallSlots::clear()
|
||||||
|
{
|
||||||
|
std::unique_lock lock(mutex_);
|
||||||
|
auto asyncCallSlots = std::move(slots_);
|
||||||
|
slots_ = {};
|
||||||
|
lock.unlock();
|
||||||
|
|
||||||
|
// Releasing call slot pointer acquires global sd-bus mutex. We have to perform the release
|
||||||
|
// out of the `mutex_' critical section here, because if the `clear` is called by some thread
|
||||||
|
// and at the same time Proxy's async reply handler (which already holds global sd-bus
|
||||||
|
// mutex) is in progress in a different thread, we get double-mutex deadlock.
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
namespace sdbus {
|
namespace sdbus {
|
||||||
|
|
||||||
PendingAsyncCall::PendingAsyncCall(std::weak_ptr<void> callData)
|
PendingAsyncCall::PendingAsyncCall(std::weak_ptr<void> callInfo)
|
||||||
: callData_(std::move(callData))
|
: callInfo_(std::move(callInfo))
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
void PendingAsyncCall::cancel()
|
void PendingAsyncCall::cancel()
|
||||||
{
|
{
|
||||||
if (auto ptr = callData_.lock(); ptr != nullptr)
|
if (auto ptr = callInfo_.lock(); ptr != nullptr)
|
||||||
{
|
{
|
||||||
auto* callData = static_cast<internal::Proxy::AsyncCalls::CallData*>(ptr.get());
|
auto* asyncCallInfo = static_cast<internal::Proxy::AsyncCallInfo*>(ptr.get());
|
||||||
callData->proxy.pendingAsyncCalls_.removeCall(callData);
|
asyncCallInfo->proxy.floatingAsyncCallSlots_.erase(asyncCallInfo);
|
||||||
|
|
||||||
// At this point, the callData item is being deleted, leading to the release of the
|
// At this point, the callData item is being deleted, leading to the release of the
|
||||||
// sd-bus slot pointer. This release locks the global sd-bus mutex. If the async
|
// sd-bus slot pointer. This release locks the global sd-bus mutex. If the async
|
||||||
@ -278,7 +351,7 @@ void PendingAsyncCall::cancel()
|
|||||||
|
|
||||||
bool PendingAsyncCall::isPending() const
|
bool PendingAsyncCall::isPending() const
|
||||||
{
|
{
|
||||||
return !callData_.expired();
|
return !callInfo_.expired();
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
78
src/Proxy.h
78
src/Proxy.h
@ -58,8 +58,19 @@ namespace sdbus::internal {
|
|||||||
|
|
||||||
MethodCall createMethodCall(const InterfaceName& interfaceName, const MethodName& methodName) override;
|
MethodCall createMethodCall(const InterfaceName& interfaceName, const MethodName& methodName) override;
|
||||||
MethodCall createMethodCall(const char* interfaceName, const char* methodName) override;
|
MethodCall createMethodCall(const char* interfaceName, const char* methodName) override;
|
||||||
|
MethodReply callMethod(const MethodCall& message) override;
|
||||||
MethodReply callMethod(const MethodCall& message, uint64_t timeout) override;
|
MethodReply callMethod(const MethodCall& message, uint64_t timeout) override;
|
||||||
PendingAsyncCall callMethodAsync(const MethodCall& message, async_reply_handler asyncReplyCallback, uint64_t timeout) override;
|
PendingAsyncCall callMethodAsync(const MethodCall& message, async_reply_handler asyncReplyCallback) override;
|
||||||
|
Slot callMethodAsync( const MethodCall& message
|
||||||
|
, async_reply_handler asyncReplyCallback
|
||||||
|
, return_slot_t ) override;
|
||||||
|
PendingAsyncCall callMethodAsync( const MethodCall& message
|
||||||
|
, async_reply_handler asyncReplyCallback
|
||||||
|
, uint64_t timeout ) override;
|
||||||
|
Slot callMethodAsync( const MethodCall& message
|
||||||
|
, async_reply_handler asyncReplyCallback
|
||||||
|
, uint64_t timeout
|
||||||
|
, return_slot_t ) override;
|
||||||
std::future<MethodReply> callMethodAsync(const MethodCall& message, with_future_t) override;
|
std::future<MethodReply> callMethodAsync(const MethodCall& message, with_future_t) override;
|
||||||
std::future<MethodReply> callMethodAsync(const MethodCall& message, uint64_t timeout, with_future_t) override;
|
std::future<MethodReply> callMethodAsync(const MethodCall& message, uint64_t timeout, with_future_t) override;
|
||||||
|
|
||||||
@ -105,67 +116,30 @@ namespace sdbus::internal {
|
|||||||
Slot slot;
|
Slot slot;
|
||||||
};
|
};
|
||||||
|
|
||||||
// We need to keep track of pending async calls. When the proxy is being destructed, we must
|
struct AsyncCallInfo
|
||||||
// remove all slots of these pending calls, otherwise in case when the connection outlives
|
|
||||||
// the proxy, we might get async reply handlers invoked for pending async calls after the proxy
|
|
||||||
// has been destroyed, which is a free ticket into the realm of undefined behavior.
|
|
||||||
class AsyncCalls
|
|
||||||
{
|
{
|
||||||
public:
|
|
||||||
struct CallData
|
|
||||||
{
|
|
||||||
Proxy& proxy;
|
|
||||||
async_reply_handler callback;
|
async_reply_handler callback;
|
||||||
|
Proxy& proxy;
|
||||||
Slot slot{};
|
Slot slot{};
|
||||||
bool finished{false};
|
bool finished{false};
|
||||||
|
bool floating;
|
||||||
};
|
};
|
||||||
|
|
||||||
~AsyncCalls()
|
// Container keeping track of pending async calls
|
||||||
|
class FloatingAsyncCallSlots
|
||||||
{
|
{
|
||||||
clear();
|
public:
|
||||||
}
|
~FloatingAsyncCallSlots();
|
||||||
|
void push_back(std::shared_ptr<AsyncCallInfo> asyncCallInfo);
|
||||||
void addCall(std::shared_ptr<CallData> asyncCallData)
|
void erase(AsyncCallInfo* info);
|
||||||
{
|
void clear();
|
||||||
std::lock_guard lock(mutex_);
|
|
||||||
if (!asyncCallData->finished) // The call may have finished in the meantime
|
|
||||||
calls_.emplace_back(std::move(asyncCallData));
|
|
||||||
}
|
|
||||||
|
|
||||||
void removeCall(CallData* data)
|
|
||||||
{
|
|
||||||
std::unique_lock lock(mutex_);
|
|
||||||
data->finished = true;
|
|
||||||
if (auto it = std::find_if(calls_.begin(), calls_.end(), [data](auto const& entry){ return entry.get() == data; }); it != calls_.end())
|
|
||||||
{
|
|
||||||
auto callData = std::move(*it);
|
|
||||||
calls_.erase(it);
|
|
||||||
lock.unlock();
|
|
||||||
|
|
||||||
// Releasing call slot pointer acquires global sd-bus mutex. We have to perform the release
|
|
||||||
// out of the `mutex_' critical section here, because if the `removeCall` is called by some
|
|
||||||
// thread and at the same time Proxy's async reply handler (which already holds global sd-bus
|
|
||||||
// mutex) is in progress in a different thread, we get double-mutex deadlock.
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void clear()
|
|
||||||
{
|
|
||||||
std::unique_lock lock(mutex_);
|
|
||||||
auto asyncCallSlots = std::move(calls_);
|
|
||||||
calls_ = {};
|
|
||||||
lock.unlock();
|
|
||||||
|
|
||||||
// Releasing call slot pointer acquires global sd-bus mutex. We have to perform the release
|
|
||||||
// out of the `mutex_' critical section here, because if the `clear` is called by some thread
|
|
||||||
// and at the same time Proxy's async reply handler (which already holds global sd-bus
|
|
||||||
// mutex) is in progress in a different thread, we get double-mutex deadlock.
|
|
||||||
}
|
|
||||||
|
|
||||||
private:
|
private:
|
||||||
std::mutex mutex_;
|
std::mutex mutex_;
|
||||||
std::deque<std::shared_ptr<CallData>> calls_;
|
std::deque<std::shared_ptr<AsyncCallInfo>> slots_;
|
||||||
} pendingAsyncCalls_;
|
};
|
||||||
|
|
||||||
|
FloatingAsyncCallSlots floatingAsyncCallSlots_;
|
||||||
};
|
};
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -198,6 +198,20 @@ TYPED_TEST(AsyncSdbusTestObject, CancelsPendingAsyncCallOnClientSide)
|
|||||||
ASSERT_THAT(future.wait_for(300ms), Eq(std::future_status::timeout));
|
ASSERT_THAT(future.wait_for(300ms), Eq(std::future_status::timeout));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
TYPED_TEST(AsyncSdbusTestObject, CancelsPendingAsyncCallOnClientSideByDestroyingOwningSlot)
|
||||||
|
{
|
||||||
|
std::promise<uint32_t> promise;
|
||||||
|
auto future = promise.get_future();
|
||||||
|
this->m_proxy->installDoOperationClientSideAsyncReplyHandler([&](uint32_t /*res*/, std::optional<sdbus::Error> /*err*/){ promise.set_value(1); });
|
||||||
|
|
||||||
|
{
|
||||||
|
auto slot = this->m_proxy->doOperationClientSideAsync(100, sdbus::return_slot);
|
||||||
|
// Now the slot is destroyed, cancelling the async call
|
||||||
|
}
|
||||||
|
|
||||||
|
ASSERT_THAT(future.wait_for(300ms), Eq(std::future_status::timeout));
|
||||||
|
}
|
||||||
|
|
||||||
TYPED_TEST(AsyncSdbusTestObject, AnswersThatAsyncCallIsNotPendingAfterItHasBeenCancelled)
|
TYPED_TEST(AsyncSdbusTestObject, AnswersThatAsyncCallIsNotPendingAfterItHasBeenCancelled)
|
||||||
{
|
{
|
||||||
std::promise<uint32_t> promise;
|
std::promise<uint32_t> promise;
|
||||||
|
@ -124,6 +124,17 @@ sdbus::PendingAsyncCall TestProxy::doOperationClientSideAsync(uint32_t param)
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Slot TestProxy::doOperationClientSideAsync(uint32_t param, sdbus::return_slot_t)
|
||||||
|
{
|
||||||
|
return getProxy().callMethodAsync("doOperation")
|
||||||
|
.onInterface(sdbus::test::INTERFACE_NAME)
|
||||||
|
.withArguments(param)
|
||||||
|
.uponReplyInvoke([this](std::optional<sdbus::Error> error, uint32_t returnValue)
|
||||||
|
{
|
||||||
|
this->onDoOperationReply(returnValue, std::move(error));
|
||||||
|
}, sdbus::return_slot);
|
||||||
|
}
|
||||||
|
|
||||||
std::future<uint32_t> TestProxy::doOperationClientSideAsync(uint32_t param, with_future_t)
|
std::future<uint32_t> TestProxy::doOperationClientSideAsync(uint32_t param, with_future_t)
|
||||||
{
|
{
|
||||||
return getProxy().callMethodAsync("doOperation")
|
return getProxy().callMethodAsync("doOperation")
|
||||||
|
@ -96,6 +96,7 @@ public:
|
|||||||
void installDoOperationClientSideAsyncReplyHandler(std::function<void(uint32_t res, std::optional<sdbus::Error> err)> handler);
|
void installDoOperationClientSideAsyncReplyHandler(std::function<void(uint32_t res, std::optional<sdbus::Error> err)> handler);
|
||||||
uint32_t doOperationWithTimeout(const std::chrono::microseconds &timeout, uint32_t param);
|
uint32_t doOperationWithTimeout(const std::chrono::microseconds &timeout, uint32_t param);
|
||||||
sdbus::PendingAsyncCall doOperationClientSideAsync(uint32_t param);
|
sdbus::PendingAsyncCall doOperationClientSideAsync(uint32_t param);
|
||||||
|
[[nodiscard]] sdbus::Slot doOperationClientSideAsync(uint32_t param, sdbus::return_slot_t);
|
||||||
std::future<uint32_t> doOperationClientSideAsync(uint32_t param, with_future_t);
|
std::future<uint32_t> doOperationClientSideAsync(uint32_t param, with_future_t);
|
||||||
std::future<MethodReply> doOperationClientSideAsyncOnBasicAPILevel(uint32_t param);
|
std::future<MethodReply> doOperationClientSideAsyncOnBasicAPILevel(uint32_t param);
|
||||||
std::future<void> doErroneousOperationClientSideAsync(with_future_t);
|
std::future<void> doErroneousOperationClientSideAsync(with_future_t);
|
||||||
|
Reference in New Issue
Block a user