Introduce support for asynchronous server-side methods (#12)

* Add preliminary changes for async server methods

* Refactor the Message concept and break it into distinctive types

* Continue working on async server methods (high-level API mainly)

* Continue developing support for async server

* Finishing async server methods

* Finishing async server methods (fixing tests & cleaning up)

* A little code cleaning

* Add unit tests for type traits of free functions

* Support for generating async server methods in stub headers

* Update ChangeLog for v0.3.0

* Update the tutorial with how to use async server-side methods

* Update the TOC in sdbus-c++ tutorial

* Update numbering in TOC

* Remove unnecessary code

* Final cleanups
This commit is contained in:
Stanislav Angelovič
2018-07-02 11:22:00 +02:00
committed by Lukáš Ďurfina
parent b041f76bfc
commit d8fd053714
33 changed files with 866 additions and 223 deletions

View File

@ -15,3 +15,11 @@ v0.2.5
v0.2.6
- Fixed memory leak in Message copy operations
v0.3.0
- Introduced support for asynchronous server-side methods
- Refactored the concept of Message into distinctive concrete message types
- As this release comes with breaking API changes:
* if you're using lower-level API, please rename 'Message' to whatever concrete message type (MethodCall, MethodReply, Signal) is needed there,
* if you're using higher-level API, please re-compile and re-link your application against sdbus-c++,
* if you're using generated stub headers, please re-compile and re-link your application against sdbus-c++.

View File

@ -4,8 +4,8 @@ AC_PREREQ(2.61)
# odd micro numbers indicate in-progress development
# even micro numbers indicate released versions
m4_define(sdbus_cpp_version_major, 0)
m4_define(sdbus_cpp_version_minor, 2)
m4_define(sdbus_cpp_version_micro, 6)
m4_define(sdbus_cpp_version_minor, 3)
m4_define(sdbus_cpp_version_micro, 0)
m4_define([sdbus_cpp_version],
[sdbus_cpp_version_major.sdbus_cpp_version_minor.sdbus_cpp_version_micro])

View File

@ -12,8 +12,9 @@ Using sdbus-c++ library
7. [Implementing the Concatenator example using basic sdbus-c++ API layer](#implementing-the-concatenator-example-using-basic-sdbus-c-api-layer)
8. [Implementing the Concatenator example using convenience sdbus-c++ API layer](#implementing-the-concatenator-example-using-convenience-sdbus-c-api-layer)
9. [Implementing the Concatenator example using sdbus-c++-generated stubs](#implementing-the-concatenator-example-using-sdbus-c-generated-stubs)
10. [Using D-Bus properties](#using-d-bus-properties)
11. [Conclusion](#conclusion)
10. [Asynchronous server-side methods](#asynchronous-server-side-methods)
11. [Using D-Bus properties](#using-d-bus-properties)
12. [Conclusion](#conclusion)
Introduction
------------
@ -92,10 +93,10 @@ The following diagram illustrates the major entities in sdbus-c++.
* invoking remote methods of the corresponding object,
* registering handlers for signals.
`Message` class represents a message, which is the fundamental DBus concept. The message can be
* a method call (with serialized parameters),
* a method reply (with serialized return values),
* or a signal (with serialized parameters).
`Message` class represents a message, which is the fundamental DBus concept. There are three distinctive types of message that derive from the `Message` class:
* `MethodCall` (with serialized parameters),
* `MethodReply` (with serialized return values),
* or a `Signal` (with serialized parameters).
Multiple layers of sdbus-c++ API
-------------------------------
@ -138,15 +139,15 @@ Overloaded versions of C++ insertion/extraction operators are used for serializa
// to emit signals.
sdbus::IObject* g_concatenator{};
void concatenate(sdbus::Message& msg, sdbus::Message& reply)
void concatenate(sdbus::MethodCall& call, sdbus::MethodReply& reply)
{
// Deserialize the collection of numbers from the message
std::vector<int> numbers;
msg >> numbers;
call >> numbers;
// Deserialize separator from the message
std::string separator;
msg >> separator;
call >> separator;
// Return error if there are no numbers in the collection
if (numbers.empty())
@ -163,9 +164,9 @@ void concatenate(sdbus::Message& msg, sdbus::Message& reply)
// Emit 'concatenated' signal
const char* interfaceName = "org.sdbuscpp.Concatenator";
auto signalMsg = g_concatenator->createSignal(interfaceName, "concatenated");
signalMsg << result;
g_concatenator->emitSignal(signalMsg);
auto signal = g_concatenator->createSignal(interfaceName, "concatenated");
signal << result;
g_concatenator->emitSignal(signal);
}
int main(int argc, char *argv[])
@ -200,10 +201,10 @@ int main(int argc, char *argv[])
#include <iostream>
#include <unistd.h>
void onConcatenated(sdbus::Message& signalMsg)
void onConcatenated(sdbus::Signal& signal)
{
std::string concatenatedString;
msg >> concatenatedString;
signal >> concatenatedString;
std::cout << "Received signal with concatenated string " << concatenatedString << std::endl;
}
@ -666,6 +667,104 @@ int main(int argc, char *argv[])
}
```
Asynchronous server-side methods
--------------------------------
So far in our tutorial, we have only considered simple server methods that are executed in a synchronous way. Sometimes the method call may take longer, however, and we don't want to block (potentially starve) other clients (whose requests may take relative short time). The solution is to execute the D-Bus methods asynchronously. How physically is that done is up to the server design (e.g. thread pool), but sdbus-c++ provides API supporting async methods.
### Lower-level API
Considering the Concatenator example based on lower-level API, if we wanted to write `concatenate` method in an asynchronous way, you only have to adapt method signature and its body (registering the method and all the other stuff stays the same):
```c++
void concatenate(sdbus::MethodCall& call, sdbus::MethodResult result)
{
// Deserialize the collection of numbers from the message
std::vector<int> numbers;
call >> numbers;
// Deserialize separator from the message
std::string separator;
call >> separator;
// Launch a thread for async execution...
std::thread([numbers, separator, result = std::move(result)]()
{
// Return error if there are no numbers in the collection
if (numbers.empty())
{
// This will send the error reply message back to the client
result.returnError("org.sdbuscpp.Concatenator.Error", "No numbers provided");
return;
}
std::string concatenatedStr;
for (auto number : numbers)
{
concatenatedStr += (result.empty() ? std::string() : separator) + std::to_string(number);
}
// This will send the reply message back to the client
result.returnResults(concatenatedStr);
// Note: emitting signals from other than D-Bus dispatcher thread is not supported yet...
/*
// Emit 'concatenated' signal
const char* interfaceName = "org.sdbuscpp.Concatenator";
auto signal = g_concatenator->createSignal(interfaceName, "concatenated");
signal << result;
g_concatenator->emitSignal(signal);
*/
}).detach();
}
```
Notice these differences as compared to the synchronous version:
* Instead of `MethodReply` message given by reference, there is `MethodResult` as a second parameter of the callback, which will hold method results and can be written to from any thread.
* You shall pass the result holder (`MethodResult` instance) by moving it to the thread of execution, and eventually write method results (or method error) to it via its `returnResults()` or `returnError()` method, respectively.
* Unlike in sync methods, reporting errors cannot be done by throwing sdbus::Error, since the execution takes place out of context of the D-Bus dispatcher thread. Instead, just pass the error name and message to the `returnError` method of the result holder.
That's all.
Note: We can use the concept of asynchronous D-Bus methods in both the synchronous and asynchronous way. Whether we return the results directly in the callback in the synchronous way, or we pass the arguments and the result holder to a different thread, and compute and set the results in there, is irrelevant to sdbus-c++. This has the benefit that we can decide at run-time, per each method call, whether we execute it synchronously or (perhaps in case of complex operation) execute it asynchronously to e.g. a thread pool.
### Convenience API
Method callbacks in convenience sdbus-c++ API also need to take the result object as a parameter. The requirements are:
* The result holder is of type `sdbus::Result<Types...>`, where `Types...` is a list of method output argument types.
* The result object must be a first physical parameter of the callback taken by value.
* The callback itself is physically a void-returning function.
For example, we would have to change the concatenate callback signature from `std::string concatenate(const std::vector<int32_t>& numbers, const std::string& separator)` to `void concatenate(sdbus::Result<std::string> result, const std::vector<int32_t>& numbers, const std::string& separator)`.
`sdbus::Result` class template has effectively the same API as `sdbus::MethodResult` class from above example (it inherits from MethodResult), so you use it in the very same way to send the results or an error back to the client.
Nothing else has to be changed. The registration of the method callback (`implementedAs`) and all the mechanics around remains completely the same.
### Marking async methods in the IDL
sdbus-c++ stub generator can generate stub code for server-side async methods. We just need to annotate the method with the `annotate` element having the "org.freedesktop.DBus.Method.Async" name, like so:
```xml
<?xml version="1.0" encoding="UTF-8"?>
<node name="/org/sdbuscpp/concatenator">
<interface name="org.sdbuscpp.Concatenator">
<method name="concatenate">
<annotation name="org.freedesktop.DBus.Method.Async" value="server" />
<arg type="ai" name="numbers" direction="in" />
<arg type="s" name="separator" direction="in" />
<arg type="s" name="concatenatedString" direction="out" />
</method>
<signal name="concatenated">
<arg type="s" name="concatenatedString" />
</signal>
</interface>
</node>
```
Using D-Bus properties
----------------------

View File

@ -14,6 +14,7 @@ libsdbuscpp_HEADERS = \
$(HEADER_DIR)/IObject.h \
$(HEADER_DIR)/IObjectProxy.h \
$(HEADER_DIR)/Message.h \
$(HEADER_DIR)/MethodResult.h \
$(HEADER_DIR)/Types.h \
$(HEADER_DIR)/TypeTraits.h \
$(HEADER_DIR)/sdbus-c++.h

View File

@ -27,7 +27,9 @@
#define SDBUS_CXX_CONVENIENCECLASSES_H_
#include <sdbus-c++/Message.h>
#include <sdbus-c++/TypeTraits.h>
#include <string>
#include <type_traits>
// Forward declarations
namespace sdbus {
@ -43,7 +45,10 @@ namespace sdbus {
public:
MethodRegistrator(IObject& object, const std::string& methodName);
MethodRegistrator& onInterface(const std::string& interfaceName);
template <typename _Function> void implementedAs(_Function&& callback);
template <typename _Function>
std::enable_if_t<!is_async_method_v<_Function>> implementedAs(_Function&& callback);
template <typename _Function>
std::enable_if_t<is_async_method_v<_Function>> implementedAs(_Function&& callback);
private:
IObject& object_;
@ -103,7 +108,7 @@ namespace sdbus {
private:
IObject& object_;
const std::string& signalName_;
Message signal_;
Signal signal_;
int exceptions_{}; // Number of active exceptions when SignalEmitter is constructed
};
@ -122,7 +127,7 @@ namespace sdbus {
private:
IObjectProxy& objectProxy_;
const std::string& methodName_;
Message method_;
MethodCall method_;
int exceptions_{}; // Number of active exceptions when MethodInvoker is constructed
bool methodCalled_{};
};

View File

@ -29,6 +29,7 @@
#include <sdbus-c++/IObject.h>
#include <sdbus-c++/IObjectProxy.h>
#include <sdbus-c++/Message.h>
#include <sdbus-c++/MethodResult.h>
#include <sdbus-c++/Types.h>
#include <sdbus-c++/TypeTraits.h>
#include <sdbus-c++/Error.h>
@ -52,7 +53,7 @@ namespace sdbus {
}
template <typename _Function>
inline void MethodRegistrator::implementedAs(_Function&& callback)
inline std::enable_if_t<!is_async_method_v<_Function>> MethodRegistrator::implementedAs(_Function&& callback)
{
SDBUS_THROW_ERROR_IF(interfaceName_.empty(), "DBus interface not specified when registering a DBus method", EINVAL);
@ -60,7 +61,7 @@ namespace sdbus {
, methodName_
, signature_of_function_input_arguments<_Function>::str()
, signature_of_function_output_arguments<_Function>::str()
, [callback = std::forward<_Function>(callback)](Message& msg, Message& reply)
, [callback = std::forward<_Function>(callback)](MethodCall& msg, MethodReply& reply)
{
// Create a tuple of callback input arguments' types, which will be used
// as a storage for the argument values deserialized from the message.
@ -80,6 +81,29 @@ namespace sdbus {
});
}
template <typename _Function>
inline std::enable_if_t<is_async_method_v<_Function>> MethodRegistrator::implementedAs(_Function&& callback)
{
SDBUS_THROW_ERROR_IF(interfaceName_.empty(), "DBus interface not specified when registering a DBus method", EINVAL);
object_.registerMethod( interfaceName_
, methodName_
, signature_of_function_input_arguments<_Function>::str()
, signature_of_function_output_arguments<_Function>::str() //signature_of<last_function_argument_t<_Function>>::str() // because last argument contains output types
, [callback = std::forward<_Function>(callback)](MethodCall& msg, MethodResult result)
{
// Create a tuple of callback input arguments' types, which will be used
// as a storage for the argument values deserialized from the message.
tuple_of_function_input_arg_types_t<_Function> inputArgs;
// Deserialize input arguments from the message into the tuple,
// plus store the result object as a last item of the tuple.
msg >> inputArgs;
// Invoke callback with input arguments from the tuple.
apply(callback, std::move(result), inputArgs); // TODO: Use std::apply when switching to full C++17 support
});
}
// Moved into the library to isolate from C++17 dependency
/*
@ -339,7 +363,7 @@ namespace sdbus {
objectProxy_.registerSignalHandler( interfaceName_
, signalName_
, [callback = std::forward<_Function>(callback)](Message& signal)
, [callback = std::forward<_Function>(callback)](Signal& signal)
{
// Create a tuple of callback input arguments' types, which will be used
// as a storage for the argument values deserialized from the signal message.

View File

@ -34,7 +34,7 @@
// Forward declarations
namespace sdbus {
class Message;
class Signal;
class IConnection;
}
@ -70,6 +70,28 @@ namespace sdbus {
, const std::string& outputSignature
, method_callback methodCallback ) = 0;
/*!
* @brief Registers method that the object will provide on D-Bus
*
* @param[in] interfaceName Name of an interface that the method will belong to
* @param[in] methodName Name of the method
* @param[in] inputSignature D-Bus signature of method input parameters
* @param[in] outputSignature D-Bus signature of method output parameters
* @param[in] asyncMethodCallback Callback that implements the body of the method
*
* This overload register a method callback that will have a freedom to execute
* its body in asynchronous contexts, and send the results from those contexts.
* This can help in e.g. long operations, which then don't block the D-Bus processing
* loop thread.
*
* @throws sdbus::Error in case of failure
*/
virtual void registerMethod( const std::string& interfaceName
, const std::string& methodName
, const std::string& inputSignature
, const std::string& outputSignature
, async_method_callback asyncMethodCallback ) = 0;
/*!
* @brief Registers signal that the object will emit on D-Bus
*
@ -138,7 +160,7 @@ namespace sdbus {
*
* @throws sdbus::Error in case of failure
*/
virtual Message createSignal(const std::string& interfaceName, const std::string& signalName) = 0;
virtual Signal createSignal(const std::string& interfaceName, const std::string& signalName) = 0;
/*!
* @brief Emits signal on D-Bus
@ -149,7 +171,7 @@ namespace sdbus {
*
* @throws sdbus::Error in case of failure
*/
virtual void emitSignal(const sdbus::Message& message) = 0;
virtual void emitSignal(const sdbus::Signal& message) = 0;
/*!
* @brief Registers method that the object will provide on D-Bus

View File

@ -33,7 +33,8 @@
// Forward declarations
namespace sdbus {
class Message;
class MethodCall;
class MethodReply;
class IConnection;
}
@ -65,7 +66,7 @@ namespace sdbus {
*
* @throws sdbus::Error in case of failure
*/
virtual Message createMethodCall(const std::string& interfaceName, const std::string& methodName) = 0;
virtual MethodCall createMethodCall(const std::string& interfaceName, const std::string& methodName) = 0;
/*!
* @brief Calls method on the proxied D-Bus object
@ -76,7 +77,7 @@ namespace sdbus {
*
* @throws sdbus::Error in case of failure
*/
virtual Message callMethod(const sdbus::Message& message) = 0;
virtual MethodReply callMethod(const sdbus::MethodCall& message) = 0;
/*!
* @brief Registers a handler for the desired signal emitted by the proxied D-Bus object

View File

@ -44,6 +44,12 @@ namespace sdbus {
class ObjectPath;
class Signature;
template <typename... _ValueTypes> class Struct;
class Message;
class MethodCall;
class MethodReply;
class Signal;
template <typename... _Results> class Result;
}
namespace sdbus {
@ -65,16 +71,8 @@ namespace sdbus {
class Message
{
public:
enum class Type
{
ePlainMessage
, eMethodCall
, eMethodReply
, eSignal
};
Message() = default;
Message(void *msg, Type type = Type::ePlainMessage) noexcept;
Message(void *msg) noexcept;
Message(const Message&) noexcept;
Message& operator=(const Message&) noexcept;
Message(Message&& other) noexcept;
@ -137,21 +135,42 @@ namespace sdbus {
void peekType(std::string& type, std::string& contents) const;
bool isValid() const;
bool isEmpty() const;
Type getType() const;
void copyTo(Message& destination, bool complete) const;
void seal();
void rewind(bool complete);
Message createReply() const;
Message send() const;
protected:
void* getMsg() const;
private:
void* msg_{};
Type type_{Type::ePlainMessage};
mutable bool ok_{true};
};
class MethodCall : public Message
{
public:
using Message::Message;
MethodReply send() const;
MethodReply createReply() const;
MethodReply createErrorReply(const sdbus::Error& error) const;
};
class MethodReply : public Message
{
public:
using Message::Message;
void send() const;
};
class Signal : public Message
{
public:
using Message::Message;
void send() const;
};
template <typename _Element>
inline Message& operator<<(Message& msg, const std::vector<_Element>& items)
{

126
include/sdbus-c++/MethodResult.h Executable file
View File

@ -0,0 +1,126 @@
/**
* (C) 2017 KISTLER INSTRUMENTE AG, Winterthur, Switzerland
*
* @file ConvenienceClasses.h
*
* Created on: Nov 8, 2016
* Project: sdbus-c++
* Description: High-level D-Bus IPC C++ library based on sd-bus
*
* This file is part of sdbus-c++.
*
* sdbus-c++ is free software; you can redistribute it and/or modify it
* under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 2.1 of the License, or
* (at your option) any later version.
*
* sdbus-c++ is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with sdbus-c++. If not, see <http://www.gnu.org/licenses/>.
*/
#ifndef SDBUS_CXX_METHODRESULT_H_
#define SDBUS_CXX_METHODRESULT_H_
#include <sdbus-c++/Message.h>
#include <cassert>
// Forward declaration
namespace sdbus {
namespace internal {
class Object;
}
class Error;
}
namespace sdbus {
/********************************************//**
* @class MethodResult
*
* Represents result of an asynchronous server-side method.
* An instance is provided to the method and shall be set
* by the method to either method return value or an error.
*
***********************************************/
class MethodResult
{
protected:
friend sdbus::internal::Object;
MethodResult() = default;
MethodResult(const MethodCall& msg, sdbus::internal::Object& object);
template <typename... _Results> void returnResults(const _Results&... results) const;
void returnError(const Error& error) const;
private:
void send(const MethodReply& reply) const;
private:
MethodCall call_;
sdbus::internal::Object* object_{};
};
template <typename... _Results>
inline void MethodResult::returnResults(const _Results&... results) const
{
assert(call_.isValid());
auto reply = call_.createReply();
#ifdef __cpp_fold_expressions
(reply << ... << results);
#else
using _ = std::initializer_list<int>;
(void)_{(void(reply << results), 0)...};
#endif
send(reply);
}
inline void MethodResult::returnError(const Error& error) const
{
auto reply = call_.createErrorReply(error);
send(reply);
}
/********************************************//**
* @class Result
*
* Represents result of an asynchronous server-side method.
* An instance is provided to the method and shall be set
* by the method to either method return value or an error.
*
***********************************************/
template <typename... _Results>
class Result : protected MethodResult
{
public:
Result() = default;
Result(MethodResult result);
void returnResults(const _Results&... results) const;
void returnError(const Error& error) const;
};
template <typename... _Results>
inline Result<_Results...>::Result(MethodResult result)
: MethodResult(std::move(result))
{
}
template <typename... _Results>
inline void Result<_Results...>::returnResults(const _Results&... results) const
{
MethodResult::returnResults(results...);
}
template <typename... _Results>
inline void Result<_Results...>::returnError(const Error& error) const
{
MethodResult::returnError(error);
}
}
#endif /* SDBUS_CXX_METHODRESULT_H_ */

View File

@ -26,6 +26,7 @@
#ifndef SDBUS_CXX_TYPETRAITS_H_
#define SDBUS_CXX_TYPETRAITS_H_
#include <type_traits>
#include <string>
#include <vector>
#include <map>
@ -40,16 +41,21 @@ namespace sdbus {
class ObjectPath;
class Signature;
class Message;
class MethodCall;
class MethodReply;
class Signal;
class MethodResult;
template <typename... _Results> class Result;
}
namespace sdbus {
using method_callback = std::function<void(Message& msg, Message& reply)>;
using signal_handler = std::function<void(Message& signal)>;
using method_callback = std::function<void(MethodCall& msg, MethodReply& reply)>;
using async_method_callback = std::function<void(MethodCall& msg, MethodResult result)>;
using signal_handler = std::function<void(Signal& signal)>;
using property_set_callback = std::function<void(Message& msg)>;
using property_get_callback = std::function<void(Message& reply)>;
// Primary template
template <typename _T>
struct signature_of
{
@ -301,6 +307,9 @@ namespace sdbus {
}
};
// Function traits implementation inspired by (c) kennytm,
// https://github.com/kennytm/utils/blob/master/traits.hpp
template <typename _Type>
struct function_traits
: public function_traits<decltype(&_Type::operator())>
@ -316,18 +325,32 @@ namespace sdbus {
: public function_traits<_Type>
{};
// Function traits implementation inspired by (c) kennytm,
// https://github.com/kennytm/utils/blob/master/traits.hpp
template <typename _ReturnType, typename... _Args>
struct function_traits<_ReturnType(_Args...)>
struct function_traits_base
{
typedef _ReturnType result_type;
typedef std::tuple<_Args...> arguments_type;
typedef std::tuple<std::decay_t<_Args>...> decayed_arguments_type;
typedef _ReturnType function_type(_Args...);
static constexpr std::size_t arity = sizeof...(_Args);
// template <size_t _Idx, typename _Enabled = void>
// struct arg;
//
// template <size_t _Idx>
// struct arg<_Idx, std::enable_if_t<(_Idx < arity)>>
// {
// typedef std::tuple_element_t<_Idx, arguments_type> type;
// };
//
// template <size_t _Idx>
// struct arg<_Idx, std::enable_if_t<!(_Idx < arity)>>
// {
// typedef void type;
// };
template <size_t _Idx>
struct arg
{
@ -338,6 +361,20 @@ namespace sdbus {
using arg_t = typename arg<_Idx>::type;
};
template <typename _ReturnType, typename... _Args>
struct function_traits<_ReturnType(_Args...)>
: public function_traits_base<_ReturnType, _Args...>
{
static constexpr bool is_async = false;
};
template <typename... _Args, typename... _Results>
struct function_traits<void(Result<_Results...>, _Args...)>
: public function_traits_base<std::tuple<_Results...>, _Args...>
{
static constexpr bool is_async = true;
};
template <typename _ReturnType, typename... _Args>
struct function_traits<_ReturnType(*)(_Args...)>
: public function_traits<_ReturnType(_Args...)>
@ -376,12 +413,39 @@ namespace sdbus {
: public function_traits<FunctionType>
{};
template <class _Function>
constexpr auto is_async_method_v = function_traits<_Function>::is_async;
template <typename _FunctionType>
using function_arguments_t = typename function_traits<_FunctionType>::arguments_type;
template <typename _FunctionType, size_t _Idx>
using function_argument_t = typename function_traits<_FunctionType>::template arg_t<_Idx>;
template <typename _FunctionType>
constexpr auto function_argument_count_v = function_traits<_FunctionType>::arity;
template <typename _FunctionType>
using function_result_t = typename function_traits<_FunctionType>::result_type;
template <typename _Function>
struct tuple_of_function_input_arg_types
{
typedef typename function_traits<_Function>::decayed_arguments_type type;
};
template <typename _Function>
using tuple_of_function_input_arg_types_t = typename tuple_of_function_input_arg_types<_Function>::type;
template <typename _Function>
struct tuple_of_function_output_arg_types
{
typedef typename function_traits<_Function>::result_type type;
};
template <typename _Function>
using tuple_of_function_output_arg_types_t = typename tuple_of_function_output_arg_types<_Function>::type;
template <typename _Type>
struct aggregate_signature
{
@ -396,6 +460,7 @@ namespace sdbus {
{
static const std::string str()
{
// TODO: This could be a fold expression in C++17...
std::initializer_list<std::string> signatures{signature_of<std::decay_t<_Types>>::str()...};
std::string signature;
for (const auto& item : signatures)
@ -404,28 +469,6 @@ namespace sdbus {
}
};
// Get a tuple of function input argument types from function signature.
// But first, convert provided function signature to the standardized form `out(in...)'.
template <typename _Function>
struct tuple_of_function_input_arg_types
: public tuple_of_function_input_arg_types<typename function_traits<_Function>::function_type>
{};
// Get a tuple of function input argument types from function signature.
// Function signature is expected in the standardized form `out(in...)'.
template <typename _ReturnType, typename... _Args>
struct tuple_of_function_input_arg_types<_ReturnType(_Args...)>
{
// Arguments may be cv-qualified and may be references, so we have to strip cv and references
// with decay_t in order to get real 'naked' types.
// Example: for a function with signature void(const int i, const std::vector<float> v, double d)
// the `type' will be `std::tuple<int, std::vector<float>, double>'.
typedef std::tuple<std::decay_t<_Args>...> type;
};
template <typename _Function>
using tuple_of_function_input_arg_types_t = typename tuple_of_function_input_arg_types<_Function>::type;
template <typename _Function>
struct signature_of_function_input_arguments
{
@ -440,12 +483,21 @@ namespace sdbus {
{
static const std::string str()
{
return aggregate_signature<function_result_t<_Function>>::str();
return aggregate_signature<tuple_of_function_output_arg_types_t<_Function>>::str();
}
};
namespace detail
{
template <class _Function, class _Tuple, std::size_t... _I>
constexpr decltype(auto) apply_impl( _Function&& f
, MethodResult&& r
, _Tuple&& t
, std::index_sequence<_I...> )
{
return std::forward<_Function>(f)(std::move(r), std::get<_I>(std::forward<_Tuple>(t))...);
}
// Version of apply_impl for functions returning non-void values.
// In this case just forward function return value.
template <class _Function, class _Tuple, std::size_t... _I>
@ -480,6 +532,16 @@ namespace sdbus {
, std::make_index_sequence<std::tuple_size<std::decay_t<_Tuple>>::value>{} );
}
// Convert tuple `t' of values into a list of arguments
// and invoke function `f' with those arguments.
template <class _Function, class _Tuple>
constexpr decltype(auto) apply(_Function&& f, MethodResult&& r, _Tuple&& t)
{
return detail::apply_impl( std::forward<_Function>(f)
, std::move(r)
, std::forward<_Tuple>(t)
, std::make_index_sequence<std::tuple_size<std::decay_t<_Tuple>>::value>{} );
}
}
#endif /* SDBUS_CXX_TYPETRAITS_H_ */

View File

@ -28,6 +28,7 @@
#include <sdbus-c++/IObjectProxy.h>
#include <sdbus-c++/Interfaces.h>
#include <sdbus-c++/Message.h>
#include <sdbus-c++/MethodResult.h>
#include <sdbus-c++/Types.h>
#include <sdbus-c++/TypeTraits.h>
#include <sdbus-c++/Introspection.h>

View File

@ -42,13 +42,13 @@ Connection::Connection(Connection::BusType type)
finishHandshake(bus);
exitLoopFd_ = createProcessingExitDescriptor();
notificationFd_ = createLoopNotificationDescriptor();
}
Connection::~Connection()
{
leaveProcessingLoop();
closeProcessingExitDescriptor(exitLoopFd_);
closeLoopNotificationDescriptor(notificationFd_);
}
void Connection::requestName(const std::string& name)
@ -67,13 +67,15 @@ void Connection::enterProcessingLoop()
{
while (true)
{
auto processed = processPendingRequest(bus_.get());
auto processed = processPendingRequest();
if (processed)
continue; // Process next one
auto success = waitForNextRequest(bus_.get(), exitLoopFd_);
auto success = waitForNextRequest();
if (!success)
break; // Exit processing loop
if (success.asyncMsgsToProcess)
processAsynchronousMessages();
}
}
@ -112,10 +114,10 @@ void Connection::removeObjectVTable(void* vtableHandle)
sd_bus_slot_unref((sd_bus_slot *)vtableHandle);
}
sdbus::Message Connection::createMethodCall( const std::string& destination
, const std::string& objectPath
, const std::string& interfaceName
, const std::string& methodName ) const
sdbus::MethodCall Connection::createMethodCall( const std::string& destination
, const std::string& objectPath
, const std::string& interfaceName
, const std::string& methodName ) const
{
sd_bus_message *sdbusMsg{};
@ -131,12 +133,12 @@ sdbus::Message Connection::createMethodCall( const std::string& destination
SDBUS_THROW_ERROR_IF(r < 0, "Failed to create method call", -r);
return Message(sdbusMsg, Message::Type::eMethodCall);
return MethodCall(sdbusMsg);
}
sdbus::Message Connection::createSignal( const std::string& objectPath
, const std::string& interfaceName
, const std::string& signalName ) const
sdbus::Signal Connection::createSignal( const std::string& objectPath
, const std::string& interfaceName
, const std::string& signalName ) const
{
sd_bus_message *sdbusSignal{};
@ -151,7 +153,7 @@ sdbus::Message Connection::createSignal( const std::string& objectPath
SDBUS_THROW_ERROR_IF(r < 0, "Failed to create signal", -r);
return Message(sdbusSignal, Message::Type::eSignal);
return Signal(sdbusSignal);
}
void* Connection::registerSignalHandler( const std::string& objectPath
@ -175,6 +177,13 @@ void Connection::unregisterSignalHandler(void* handlerCookie)
sd_bus_slot_unref((sd_bus_slot *)handlerCookie);
}
void Connection::sendReplyAsynchronously(const sdbus::MethodReply& reply)
{
std::lock_guard<std::mutex> guard(mutex_);
asyncReplies_.push(reply);
notifyProcessingLoop();
}
std::unique_ptr<sdbus::internal::IConnection> Connection::clone() const
{
return std::make_unique<sdbus::internal::Connection>(busType_);
@ -211,10 +220,8 @@ void Connection::finishHandshake(sd_bus* bus)
SDBUS_THROW_ERROR_IF(r < 0, "Failed to flush bus on opening", -r);
}
int Connection::createProcessingExitDescriptor()
int Connection::createLoopNotificationDescriptor()
{
// Mechanism for graceful termination of processing loop
auto r = eventfd(0, EFD_SEMAPHORE | EFD_CLOEXEC);
SDBUS_THROW_ERROR_IF(r < 0, "Failed to create event object", -errno);
@ -222,16 +229,23 @@ int Connection::createProcessingExitDescriptor()
return r;
}
void Connection::closeProcessingExitDescriptor(int fd)
void Connection::closeLoopNotificationDescriptor(int fd)
{
close(fd);
}
void Connection::notifyProcessingLoop()
{
assert(notificationFd_ >= 0);
uint64_t value = 1;
write(notificationFd_, &value, sizeof(value));
}
void Connection::notifyProcessingLoopToExit()
{
assert(exitLoopFd_ >= 0);
uint64_t value = 1;
write(exitLoopFd_, &value, sizeof(value));
exitLoopThread_ = true;
notifyProcessingLoop();
}
void Connection::joinWithProcessingLoop()
@ -240,8 +254,10 @@ void Connection::joinWithProcessingLoop()
asyncLoopThread_.join();
}
bool Connection::processPendingRequest(sd_bus* bus)
bool Connection::processPendingRequest()
{
auto bus = bus_.get();
assert(bus != nullptr);
int r = sd_bus_process(bus, nullptr);
@ -251,10 +267,23 @@ bool Connection::processPendingRequest(sd_bus* bus)
return r > 0;
}
bool Connection::waitForNextRequest(sd_bus* bus, int exitFd)
void Connection::processAsynchronousMessages()
{
std::lock_guard<std::mutex> guard(mutex_);
while (!asyncReplies_.empty())
{
auto reply = asyncReplies_.front();
asyncReplies_.pop();
reply.send();
}
}
Connection::WaitResult Connection::waitForNextRequest()
{
auto bus = bus_.get();
assert(bus != nullptr);
assert(exitFd != 0);
assert(notificationFd_ != 0);
auto r = sd_bus_get_fd(bus);
SDBUS_THROW_ERROR_IF(r < 0, "Failed to get bus descriptor", -r);
@ -267,20 +296,26 @@ bool Connection::waitForNextRequest(sd_bus* bus, int exitFd)
uint64_t usec;
sd_bus_get_timeout(bus, &usec);
struct pollfd fds[] = {{sdbusFd, sdbusEvents, 0}, {exitFd, POLLIN, 0}};
struct pollfd fds[] = {{sdbusFd, sdbusEvents, 0}, {notificationFd_, POLLIN, 0}};
auto fdsCount = sizeof(fds)/sizeof(fds[0]);
r = poll(fds, fdsCount, usec == (uint64_t) -1 ? -1 : (usec+999)/1000);
if (r < 0 && errno == EINTR)
return true; // Try again
return {true, false}; // Try again
SDBUS_THROW_ERROR_IF(r < 0, "Failed to wait on the bus", -errno);
if (fds[1].revents & POLLIN)
return false; // Got exit notification
{
if (exitLoopThread_)
return {false, false}; // Got exit notification
return true;
// Otherwise we have some async messages to process
return {false, true};
}
return {true, false};
}
std::string Connection::composeSignalMatchFilter( const std::string& objectPath

View File

@ -27,12 +27,14 @@
#define SDBUS_CXX_INTERNAL_CONNECTION_H_
#include <sdbus-c++/IConnection.h>
#include <sdbus-c++/Message.h>
#include "IConnection.h"
#include <systemd/sd-bus.h>
#include <memory>
#include <atomic>
#include <thread>
#include "IConnection.h"
#include <atomic>
#include <mutex>
#include <queue>
namespace sdbus { namespace internal {
@ -62,13 +64,13 @@ namespace sdbus { namespace internal {
, void* userData ) override;
void removeObjectVTable(void* vtableHandle) override;
sdbus::Message createMethodCall( const std::string& destination
, const std::string& objectPath
, const std::string& interfaceName
, const std::string& methodName ) const override;
sdbus::Message createSignal( const std::string& objectPath
, const std::string& interfaceName
, const std::string& signalName ) const override;
sdbus::MethodCall createMethodCall( const std::string& destination
, const std::string& objectPath
, const std::string& interfaceName
, const std::string& methodName ) const override;
sdbus::Signal createSignal( const std::string& objectPath
, const std::string& interfaceName
, const std::string& signalName ) const override;
void* registerSignalHandler( const std::string& objectPath
, const std::string& interfaceName
@ -77,25 +79,41 @@ namespace sdbus { namespace internal {
, void* userData ) override;
void unregisterSignalHandler(void* handlerCookie) override;
void sendReplyAsynchronously(const sdbus::MethodReply& reply) override;
std::unique_ptr<sdbus::internal::IConnection> clone() const override;
private:
struct WaitResult
{
bool msgsToProcess;
bool asyncMsgsToProcess;
operator bool()
{
return msgsToProcess || asyncMsgsToProcess;
}
};
static sd_bus* openBus(Connection::BusType type);
static void finishHandshake(sd_bus* bus);
static int createProcessingExitDescriptor();
static void closeProcessingExitDescriptor(int fd);
static bool processPendingRequest(sd_bus* bus);
static bool waitForNextRequest(sd_bus* bus, int exitFd);
static int createLoopNotificationDescriptor();
static void closeLoopNotificationDescriptor(int fd);
bool processPendingRequest();
void processAsynchronousMessages();
WaitResult waitForNextRequest();
static std::string composeSignalMatchFilter( const std::string& objectPath
, const std::string& interfaceName
, const std::string& signalName );
void notifyProcessingLoop();
void notifyProcessingLoopToExit();
void joinWithProcessingLoop();
private:
std::unique_ptr<sd_bus, decltype(&sd_bus_flush_close_unref)> bus_{nullptr, &sd_bus_flush_close_unref};
std::thread asyncLoopThread_;
std::atomic<int> exitLoopFd_{-1};
std::mutex mutex_;
std::queue<MethodReply> asyncReplies_;
std::atomic<bool> exitLoopThread_;
int notificationFd_{-1};
BusType busType_;
static constexpr const uint64_t POLL_TIMEOUT_USEC = 500000;

View File

@ -32,7 +32,9 @@
// Forward declaration
namespace sdbus {
class Message;
class MethodCall;
class MethodReply;
class Signal;
}
namespace sdbus {
@ -47,14 +49,14 @@ namespace internal {
, void* userData ) = 0;
virtual void removeObjectVTable(void* vtableHandle) = 0;
virtual sdbus::Message createMethodCall( const std::string& destination
, const std::string& objectPath
, const std::string& interfaceName
, const std::string& methodName ) const = 0;
virtual sdbus::MethodCall createMethodCall( const std::string& destination
, const std::string& objectPath
, const std::string& interfaceName
, const std::string& methodName ) const = 0;
virtual sdbus::Message createSignal( const std::string& objectPath
, const std::string& interfaceName
, const std::string& signalName ) const = 0;
virtual sdbus::Signal createSignal( const std::string& objectPath
, const std::string& interfaceName
, const std::string& signalName ) const = 0;
virtual void* registerSignalHandler( const std::string& objectPath
, const std::string& interfaceName
@ -66,6 +68,8 @@ namespace internal {
virtual void enterProcessingLoopAsync() = 0;
virtual void leaveProcessingLoop() = 0;
virtual void sendReplyAsynchronously(const sdbus::MethodReply& reply) = 0;
virtual std::unique_ptr<sdbus::internal::IConnection> clone() const = 0;
virtual ~IConnection() = default;

View File

@ -5,6 +5,7 @@ libsdbus_c___la_SOURCES = \
Connection.cpp \
ConvenienceClasses.cpp \
Message.cpp \
MethodResult.cpp \
Object.cpp \
ObjectProxy.cpp \
Types.cpp \

View File

@ -31,11 +31,10 @@
#include <systemd/sd-bus.h>
#include <cassert>
namespace sdbus { /*namespace internal {*/
namespace sdbus {
Message::Message(void *msg, Type type) noexcept
Message::Message(void *msg) noexcept
: msg_(msg)
, type_(type)
{
assert(msg_ != nullptr);
sd_bus_message_ref((sd_bus_message*)msg_);
@ -52,7 +51,6 @@ Message& Message::operator=(const Message& other) noexcept
sd_bus_message_unref((sd_bus_message*)msg_);
msg_ = other.msg_;
type_ = other.type_;
ok_ = other.ok_;
sd_bus_message_ref((sd_bus_message*)msg_);
@ -72,8 +70,6 @@ Message& Message::operator=(Message&& other) noexcept
msg_ = other.msg_;
other.msg_ = nullptr;
type_ = other.type_;
other.type_ = {};
ok_ = other.ok_;
other.ok_ = true;
@ -540,59 +536,6 @@ void Message::rewind(bool complete)
SDBUS_THROW_ERROR_IF(r < 0, "Failed to rewind the message", -r);
}
Message Message::send() const
{
if (type_ == Type::eMethodCall)
{
sd_bus_message* sdbusReply{};
SCOPE_EXIT{ sd_bus_message_unref(sdbusReply); }; // Returned message will become an owner of sdbusReply
sd_bus_error sdbusError = SD_BUS_ERROR_NULL;
SCOPE_EXIT{ sd_bus_error_free(&sdbusError); };
auto r = sd_bus_call(nullptr, (sd_bus_message*)msg_, 0, &sdbusError, &sdbusReply);
if (sd_bus_error_is_set(&sdbusError))
{
throw sdbus::Error(sdbusError.name, sdbusError.message);
}
SDBUS_THROW_ERROR_IF(r < 0, "Failed to call method", -r);
return Message(sdbusReply);
}
else if (type_ == Type::eMethodReply)
{
auto r = sd_bus_send(nullptr, (sd_bus_message*)msg_, nullptr);
SDBUS_THROW_ERROR_IF(r < 0, "Failed to send reply", -r);
return Message();
}
else if (type_ == Type::eSignal)
{
auto r = sd_bus_send(nullptr, (sd_bus_message*)msg_, nullptr);
SDBUS_THROW_ERROR_IF(r < 0, "Failed to emit signal", -r);
return Message();
}
else
{
assert(false);
return Message();
}
}
Message Message::createReply() const
{
sd_bus_message *sdbusReply{};
SCOPE_EXIT{ sd_bus_message_unref(sdbusReply); }; // Returned message will become an owner of sdbusReply
auto r = sd_bus_message_new_method_return((sd_bus_message*)msg_, &sdbusReply);
SDBUS_THROW_ERROR_IF(r < 0, "Failed to create method reply", -r);
assert(sdbusReply != nullptr);
return Message(sdbusReply, Type::eMethodReply);
}
std::string Message::getInterfaceName() const
{
return sd_bus_message_get_interface((sd_bus_message*)msg_);
@ -623,9 +566,70 @@ bool Message::isEmpty() const
return sd_bus_message_is_empty((sd_bus_message*)msg_);
}
Message::Type Message::getType() const
void* Message::getMsg() const
{
return type_;
return msg_;
}
MethodReply MethodCall::send() const
{
sd_bus_message* sdbusReply{};
SCOPE_EXIT{ sd_bus_message_unref(sdbusReply); }; // Returned message will become an owner of sdbusReply
sd_bus_error sdbusError = SD_BUS_ERROR_NULL;
SCOPE_EXIT{ sd_bus_error_free(&sdbusError); };
auto r = sd_bus_call(nullptr, (sd_bus_message*)getMsg(), 0, &sdbusError, &sdbusReply);
if (sd_bus_error_is_set(&sdbusError))
{
throw sdbus::Error(sdbusError.name, sdbusError.message);
}
SDBUS_THROW_ERROR_IF(r < 0, "Failed to call method", -r);
return MethodReply(sdbusReply);
}
MethodReply MethodCall::createReply() const
{
sd_bus_message *sdbusReply{};
SCOPE_EXIT{ sd_bus_message_unref(sdbusReply); }; // Returned message will become an owner of sdbusReply
auto r = sd_bus_message_new_method_return((sd_bus_message*)getMsg(), &sdbusReply);
SDBUS_THROW_ERROR_IF(r < 0, "Failed to create method reply", -r);
assert(sdbusReply != nullptr);
return MethodReply(sdbusReply);
}
MethodReply MethodCall::createErrorReply(const Error& error) const
{
sd_bus_error sdbusError = SD_BUS_ERROR_NULL;
SCOPE_EXIT{ sd_bus_error_free(&sdbusError); };
sd_bus_error_set(&sdbusError, error.getName().c_str(), error.getMessage().c_str());
sd_bus_message *sdbusErrorReply{};
SCOPE_EXIT{ sd_bus_message_unref(sdbusErrorReply); }; // Returned message will become an owner of sdbusErrorReply
auto r = sd_bus_message_new_method_error((sd_bus_message*)getMsg(), &sdbusErrorReply, &sdbusError);
SDBUS_THROW_ERROR_IF(r < 0, "Failed to create method error reply", -r);
assert(sdbusErrorReply != nullptr);
return MethodReply(sdbusErrorReply);
}
void MethodReply::send() const
{
auto r = sd_bus_send(nullptr, (sd_bus_message*)getMsg(), nullptr);
SDBUS_THROW_ERROR_IF(r < 0, "Failed to send reply", -r);
}
void Signal::send() const
{
auto r = sd_bus_send(nullptr, (sd_bus_message*)getMsg(), nullptr);
SDBUS_THROW_ERROR_IF(r < 0, "Failed to emit signal", -r);
}
Message createPlainMessage()
@ -642,7 +646,7 @@ Message createPlainMessage()
r = sd_bus_message_new(bus, &sdbusMsg, _SD_BUS_MESSAGE_TYPE_INVALID);
SDBUS_THROW_ERROR_IF(r < 0, "Failed to create a new message", -r);
return Message(sdbusMsg, Message::Type::ePlainMessage);
return Message(sdbusMsg);
}
/*}*/}
}

43
src/MethodResult.cpp Normal file
View File

@ -0,0 +1,43 @@
/**
* (C) 2017 KISTLER INSTRUMENTE AG, Winterthur, Switzerland
*
* @file Object.cpp
*
* Created on: Nov 8, 2016
* Project: sdbus-c++
* Description: High-level D-Bus IPC C++ library based on sd-bus
*
* This file is part of sdbus-c++.
*
* sdbus-c++ is free software; you can redistribute it and/or modify it
* under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 2.1 of the License, or
* (at your option) any later version.
*
* sdbus-c++ is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with sdbus-c++. If not, see <http://www.gnu.org/licenses/>.
*/
#include <sdbus-c++/MethodResult.h>
#include "Object.h"
namespace sdbus {
MethodResult::MethodResult(const MethodCall& msg, sdbus::internal::Object& object)
: call_(msg)
, object_(&object)
{
}
void MethodResult::send(const MethodReply& reply) const
{
assert(object_ != nullptr);
object_->sendReplyAsynchronously(reply);
}
}

View File

@ -27,6 +27,7 @@
#include <sdbus-c++/IConnection.h>
#include <sdbus-c++/Message.h>
#include <sdbus-c++/Error.h>
#include <sdbus-c++/MethodResult.h>
#include "IConnection.h"
#include "VTableUtils.h"
#include <systemd/sd-bus.h>
@ -48,9 +49,36 @@ void Object::registerMethod( const std::string& interfaceName
{
SDBUS_THROW_ERROR_IF(!methodCallback, "Invalid method callback provided", EINVAL);
auto& interface = interfaces_[interfaceName];
auto syncCallback = [callback = std::move(methodCallback)](MethodCall& msg)
{
auto reply = msg.createReply();
callback(msg, reply);
reply.send();
};
InterfaceData::MethodData methodData{inputSignature, outputSignature, std::move(methodCallback)};
auto& interface = interfaces_[interfaceName];
InterfaceData::MethodData methodData{inputSignature, outputSignature, std::move(syncCallback)};
auto inserted = interface.methods_.emplace(methodName, std::move(methodData)).second;
SDBUS_THROW_ERROR_IF(!inserted, "Failed to register method: method already exists", EINVAL);
}
void Object::registerMethod( const std::string& interfaceName
, const std::string& methodName
, const std::string& inputSignature
, const std::string& outputSignature
, async_method_callback asyncMethodCallback )
{
SDBUS_THROW_ERROR_IF(!asyncMethodCallback, "Invalid method callback provided", EINVAL);
auto asyncCallback = [this, callback = std::move(asyncMethodCallback)](MethodCall& msg)
{
MethodResult result{msg, *this};
callback(msg, result);
};
auto& interface = interfaces_[interfaceName];
InterfaceData::MethodData methodData{inputSignature, outputSignature, std::move(asyncCallback)};
auto inserted = interface.methods_.emplace(methodName, std::move(methodData)).second;
SDBUS_THROW_ERROR_IF(!inserted, "Failed to register method: method already exists", EINVAL);
@ -108,17 +136,24 @@ void Object::finishRegistration()
}
}
sdbus::Message Object::createSignal(const std::string& interfaceName, const std::string& signalName)
sdbus::Signal Object::createSignal(const std::string& interfaceName, const std::string& signalName)
{
// Tell, don't ask
return connection_.createSignal(objectPath_, interfaceName, signalName);
}
void Object::emitSignal(const sdbus::Message& message)
void Object::emitSignal(const sdbus::Signal& message)
{
// TODO: Make signal emitting asynchronous. Now signal can probably be emitted only from user code
// handled within the D-Bus processing loop thread, but not from any thread. In principle it will
// be the same as async replies.
message.send();
}
void Object::sendReplyAsynchronously(const MethodReply& reply)
{
connection_.sendReplyAsynchronously(reply);
}
const std::vector<sd_bus_vtable>& Object::createInterfaceVTable(InterfaceData& interfaceData)
{
auto& vtable = interfaceData.vtable_;
@ -190,27 +225,22 @@ void Object::activateInterfaceVTable( const std::string& interfaceName
int Object::sdbus_method_callback(sd_bus_message *sdbusMessage, void *userData, sd_bus_error *retError)
{
Message message(sdbusMessage, Message::Type::eMethodCall);
MethodCall message(sdbusMessage);
auto* object = static_cast<Object*>(userData);
// Note: The lookup can be optimized by using sorted vectors instead of associative containers
auto& callback = object->interfaces_[message.getInterfaceName()].methods_[message.getMemberName()].callback_;
assert(callback);
auto reply = message.createReply();
try
{
callback(message, reply);
callback(message);
}
catch (const sdbus::Error& e)
{
sd_bus_error_set(retError, e.getName().c_str(), e.getMessage().c_str());
return 1;
}
reply.send();
return 1;
}
@ -222,7 +252,7 @@ int Object::sdbus_property_get_callback( sd_bus */*bus*/
, void *userData
, sd_bus_error *retError )
{
Message reply(sdbusReply, Message::Type::ePlainMessage);
Message reply(sdbusReply);
auto* object = static_cast<Object*>(userData);
// Note: The lookup can be optimized by using sorted vectors instead of associative containers
@ -254,7 +284,7 @@ int Object::sdbus_property_set_callback( sd_bus */*bus*/
, void *userData
, sd_bus_error *retError )
{
Message value(sdbusValue, Message::Type::ePlainMessage);
Message value(sdbusValue);
auto* object = static_cast<Object*>(userData);
// Note: The lookup can be optimized by using sorted vectors instead of associative containers
@ -268,7 +298,6 @@ int Object::sdbus_property_set_callback( sd_bus */*bus*/
catch (const sdbus::Error& e)
{
sd_bus_error_set(retError, e.getName().c_str(), e.getMessage().c_str());
return 1;
}
return 1;

View File

@ -32,6 +32,7 @@
#include <string>
#include <map>
#include <vector>
#include <functional>
#include <memory>
#include <cassert>
@ -50,6 +51,12 @@ namespace internal {
, const std::string& outputSignature
, method_callback methodCallback ) override;
void registerMethod( const std::string& interfaceName
, const std::string& methodName
, const std::string& inputSignature
, const std::string& outputSignature
, async_method_callback asyncMethodCallback ) override;
void registerSignal( const std::string& interfaceName
, const std::string& signalName
, const std::string& signature ) override;
@ -67,8 +74,10 @@ namespace internal {
void finishRegistration() override;
sdbus::Message createSignal(const std::string& interfaceName, const std::string& signalName) override;
void emitSignal(const sdbus::Message& message) override;
sdbus::Signal createSignal(const std::string& interfaceName, const std::string& signalName) override;
void emitSignal(const sdbus::Signal& message) override;
void sendReplyAsynchronously(const MethodReply& reply);
private:
using InterfaceName = std::string;
@ -79,7 +88,7 @@ namespace internal {
{
std::string inputArgs_;
std::string outputArgs_;
method_callback callback_;
std::function<void(MethodCall&)> callback_;
};
std::map<MethodName, MethodData> methods_;
using SignalName = std::string;

View File

@ -60,13 +60,13 @@ ObjectProxy::~ObjectProxy()
signalConnection_->leaveProcessingLoop();
}
Message ObjectProxy::createMethodCall(const std::string& interfaceName, const std::string& methodName)
MethodCall ObjectProxy::createMethodCall(const std::string& interfaceName, const std::string& methodName)
{
// Tell, don't ask
return connection_->createMethodCall(destination_, objectPath_, interfaceName, methodName);
}
Message ObjectProxy::callMethod(const Message& message)
MethodReply ObjectProxy::callMethod(const MethodCall& message)
{
return message.send();
}
@ -138,7 +138,7 @@ void ObjectProxy::registerSignalHandlers(sdbus::internal::IConnection& connectio
int ObjectProxy::sdbus_signal_callback(sd_bus_message *sdbusMessage, void *userData, sd_bus_error */*retError*/)
{
Message message(sdbusMessage, Message::Type::eSignal);
Signal message(sdbusMessage);
auto* object = static_cast<ObjectProxy*>(userData);
// Note: The lookup can be optimized by using sorted vectors instead of associative containers

View File

@ -52,8 +52,8 @@ namespace internal {
, std::string objectPath );
~ObjectProxy();
Message createMethodCall(const std::string& interfaceName, const std::string& methodName) override;
Message callMethod(const Message& message) override;
MethodCall createMethodCall(const std::string& interfaceName, const std::string& methodName) override;
MethodReply callMethod(const MethodCall& message) override;
void registerSignalHandler( const std::string& interfaceName
, const std::string& signalName

View File

@ -136,25 +136,47 @@ std::tuple<std::string, std::string> AdaptorGenerator::processMethods(const Node
{
auto methodName = method->get("name");
bool async{false};
Nodes annotations = (*method)["annotation"];
for (const auto& annotation : annotations)
{
if (annotation->get("name") == "org.freedesktop.DBus.Method.Async"
&& (annotation->get("value") == "server" || annotation->get("value") == "clientserver"))
{
async = true;
break;
}
}
Nodes args = (*method)["arg"];
Nodes inArgs = args.select("direction" , "in");
Nodes outArgs = args.select("direction" , "out");
std::string argStr, argTypeStr;
std::tie(argStr, argTypeStr, std::ignore) = argsToNamesAndTypes(inArgs);
using namespace std::string_literals;
registrationSS << tab << tab << "object_.registerMethod(\""
<< methodName << "\")"
<< ".onInterface(interfaceName)"
<< ".implementedAs("
<< "[this]("
<< (async ? "sdbus::Result<" + outArgsToType(outArgs, true) + "> result" + (argTypeStr.empty() ? "" : ", ") : "")
<< argTypeStr
<< "){ return this->" << methodName << "("
<< "){ " << (async ? "" : "return ") << "this->" << methodName << "("
<< (async ? "std::move(result)"s + (argTypeStr.empty() ? "" : ", ") : "")
<< argStr << "); });" << endl;
declarationSS << tab
<< "virtual " << outArgsToType(outArgs) << " " << methodName
<< "(" << argTypeStr << ") = 0;" << endl;
<< "virtual "
<< (async ? "void" : outArgsToType(outArgs))
<< " " << methodName
<< "("
<< (async ? "sdbus::Result<" + outArgsToType(outArgs, true) + "> result" + (argTypeStr.empty() ? "" : ", ") : "")
<< argTypeStr
<< ") = 0;" << endl;
}
return std::make_tuple(registrationSS.str(), declarationSS.str());

View File

@ -126,12 +126,15 @@ std::tuple<std::string, std::string, std::string> BaseGenerator::argsToNamesAndT
/**
*
*/
std::string BaseGenerator::outArgsToType(const Nodes& args) const
std::string BaseGenerator::outArgsToType(const Nodes& args, bool bareList) const
{
std::ostringstream retTypeSS;
if (args.size() == 0)
{
if (bareList)
return "";
retTypeSS << "void";
}
else if (args.size() == 1)
@ -141,7 +144,8 @@ std::string BaseGenerator::outArgsToType(const Nodes& args) const
}
else if (args.size() >= 2)
{
retTypeSS << "std::tuple<";
if (!bareList)
retTypeSS << "std::tuple<";
bool firstArg = true;
for (const auto& arg : args)
@ -150,7 +154,8 @@ std::string BaseGenerator::outArgsToType(const Nodes& args) const
retTypeSS << signature_to_type(arg->get("type"));
}
retTypeSS << ">";
if (!bareList)
retTypeSS << ">";
}
return retTypeSS.str();

View File

@ -94,7 +94,7 @@ protected:
* @param args
* @return return type
*/
std::string outArgsToType(const sdbuscpp::xml::Nodes& args) const;
std::string outArgsToType(const sdbuscpp::xml::Nodes& args, bool bareList = false) const;
};

View File

@ -1,4 +1,4 @@
cmake_minimum_required (VERSION 3.3)
cmake_minimum_required (VERSION 3.4)
add_definitions(-std=c++14)

View File

@ -14,7 +14,7 @@ endif
# Setting per-file flags
AM_CPPFLAGS = -I$(top_srcdir)/include -I$(top_srcdir)/src
AM_CXXFLAGS = @libsdbus_cpp_CFLAGS@ @SYSTEMD_CFLAGS@ -W -Wall -Werror -pedantic -pipe -std=c++14
AM_CXXFLAGS = @libsdbus_cpp_CFLAGS@ @SYSTEMD_CFLAGS@ -W -Wall -Werror -pedantic -pipe -std=c++17
AM_LDFLAGS = @libsdbus_cpp_LIBS@ @SYSTEMD_LIBS@
CLEANFILES = *~ *.lo *.la

View File

@ -40,9 +40,12 @@
#include <string>
#include <thread>
#include <tuple>
#include <chrono>
using ::testing::Eq;
using ::testing::Gt;
using ::testing::ElementsAre;
using namespace std::chrono_literals;
namespace
{
@ -67,7 +70,7 @@ private:
{
m_adaptor = std::make_unique<TestingAdaptor>(m_connection);
m_proxy = std::make_unique<TestingProxy>(INTERFACE_NAME, OBJECT_PATH);
usleep(50000); // Give time for the proxy to start listening to signals
std::this_thread::sleep_for(50ms); // Give time for the proxy to start listening to signals
}
void TearDown() override
@ -197,6 +200,31 @@ TEST_F(SdbusTestObject, CallsMethodWithComplexTypeSuccesfully)
ASSERT_THAT(resComplex.count(0), Eq(1));
}
TEST_F(SdbusTestObject, DoesServerSideAsynchoronousMethodInParallel)
{
// Yeah, this is kinda timing-dependent test, but times should be safe...
std::mutex mtx;
std::vector<uint32_t> results;
std::atomic<bool> invoke{};
std::atomic<int> startedCount{};
auto call = [&](uint32_t param)
{
TestingProxy proxy{INTERFACE_NAME, OBJECT_PATH};
++startedCount;
while (!invoke) ;
auto result = proxy.doOperationAsync(param);
std::lock_guard<std::mutex> guard(mtx);
results.push_back(result);
};
std::thread invocations[]{std::thread{call, 1500}, std::thread{call, 1000}, std::thread{call, 500}};
while (startedCount != 3) ;
invoke = true;
std::for_each(std::begin(invocations), std::end(invocations), [](auto& t){ t.join(); });
ASSERT_THAT(results, ElementsAre(500, 1000, 1500));
}
TEST_F(SdbusTestObject, FailsCallingNonexistentMethod)
{
ASSERT_THROW(m_proxy->callNonexistentMethod(), sdbus::Error);

View File

@ -27,6 +27,8 @@
#define SDBUS_CPP_INTEGRATIONTESTS_TESTINGADAPTOR_H_
#include "adaptor-glue.h"
#include <thread>
#include <chrono>
class TestingAdaptor : public sdbus::Interfaces<testing_adaptor>
{
@ -97,6 +99,22 @@ protected:
return res;
}
uint32_t doOperationSync(uint32_t param)
{
std::this_thread::sleep_for(std::chrono::milliseconds(param));
return param;
}
void doOperationAsync(uint32_t param, sdbus::Result<uint32_t> result)
{
// The same as doOperationSync, just written as an asynchronous method callback
std::thread([param, result = std::move(result)]()
{
std::this_thread::sleep_for(std::chrono::milliseconds(param));
result.returnResults(param);
}).detach();
}
sdbus::Signature getSignature() const { return SIGNATURE_VALUE; }
sdbus::ObjectPath getObjectPath() const { return OBJECT_PATH_VALUE; }

View File

@ -82,6 +82,16 @@ protected:
return this->sumVectorItems(a, b);
});
object_.registerMethod("doOperationSync").onInterface(INTERFACE_NAME).implementedAs([this](uint32_t param)
{
return this->doOperationSync(param);
});
object_.registerMethod("doOperationAsync").onInterface(INTERFACE_NAME).implementedAs([this](sdbus::Result<uint32_t> result, uint32_t param)
{
this->doOperationAsync(param, std::move(result));
});
object_.registerMethod("getSignature").onInterface(INTERFACE_NAME).implementedAs([this](){ return this->getSignature(); });
object_.registerMethod("getObjectPath").onInterface(INTERFACE_NAME).implementedAs([this](){ return this->getObjectPath(); });
@ -139,6 +149,8 @@ protected:
virtual sdbus::Struct<std::string, sdbus::Struct<std::map<int32_t, int32_t>>> getStructInStruct() const = 0;
virtual int32_t sumStructItems(const sdbus::Struct<uint8_t, uint16_t>& a, const sdbus::Struct<int32_t, int64_t>& b) = 0;
virtual uint32_t sumVectorItems(const std::vector<uint16_t>& a, const std::vector<uint64_t>& b) = 0;
virtual uint32_t doOperationSync(uint32_t param) = 0;
virtual void doOperationAsync(uint32_t param, sdbus::Result<uint32_t> result) = 0;
virtual sdbus::Signature getSignature() const = 0;
virtual sdbus::ObjectPath getObjectPath() const = 0;
virtual ComplexType getComplex() const = 0;

View File

@ -120,6 +120,20 @@ public:
return result;
}
uint32_t doOperationSync(uint32_t param)
{
uint32_t result;
object_.callMethod("doOperationSync").onInterface(INTERFACE_NAME).withArguments(param).storeResultsTo(result);
return result;
}
uint32_t doOperationAsync(uint32_t param)
{
uint32_t result;
object_.callMethod("doOperationAsync").onInterface(INTERFACE_NAME).withArguments(param).storeResultsTo(result);
return result;
}
sdbus::Signature getSignature()
{
sdbus::Signature result;

View File

@ -110,13 +110,6 @@ TEST(AMessage, IsNotEmptyWhenContainsAValue)
ASSERT_FALSE(msg.isEmpty());
}
TEST(AMessage, ReturnsItsTypeWhenAsked)
{
sdbus::Message msg{sdbus::createPlainMessage()};
ASSERT_THAT(msg.getType(), Eq(sdbus::Message::Type::ePlainMessage));
}
TEST(AMessage, CanCarryASimpleInteger)
{
sdbus::Message msg{sdbus::createPlainMessage()};

View File

@ -28,6 +28,7 @@
#include <gtest/gtest.h>
#include <gmock/gmock.h>
#include <cstdint>
#include <type_traits>
using ::testing::Eq;
@ -127,3 +128,42 @@ TYPED_TEST(Type2DBusTypeSignatureConversion, ConvertsTypeToProperDBusSignature)
{
ASSERT_THAT(sdbus::signature_of<TypeParam>::str(), Eq(this->dbusTypeSignature_));
}
TEST(FreeFunctionTypeTraits, DetectsTraitsOfTrivialSignatureFunction)
{
void f();
using Fnc = decltype(f);
static_assert(!sdbus::is_async_method_v<Fnc>, "Free function incorrectly detected as async method");
static_assert(std::is_same<sdbus::function_arguments_t<Fnc>, std::tuple<>>::value, "Incorrectly detected free function parameters");
static_assert(std::is_same<sdbus::tuple_of_function_input_arg_types_t<Fnc>, std::tuple<>>::value, "Incorrectly detected tuple of free function parameters");
static_assert(std::is_same<sdbus::tuple_of_function_output_arg_types_t<Fnc>, void>::value, "Incorrectly detected tuple of free function return types");
static_assert(sdbus::function_argument_count_v<Fnc> == 0, "Incorrectly detected free function parameter count");
static_assert(std::is_void<sdbus::function_result_t<Fnc>>::value, "Incorrectly detected free function return type");
}
TEST(FreeFunctionTypeTraits, DetectsTraitsOfNontrivialSignatureFunction)
{
std::tuple<char, int> f(double&, const char*, int);
using Fnc = decltype(f);
static_assert(!sdbus::is_async_method_v<Fnc>, "Free function incorrectly detected as async method");
static_assert(std::is_same<sdbus::function_arguments_t<Fnc>, std::tuple<double&, const char*, int>>::value, "Incorrectly detected free function parameters");
static_assert(std::is_same<sdbus::tuple_of_function_input_arg_types_t<Fnc>, std::tuple<double, const char*, int>>::value, "Incorrectly detected tuple of free function parameters");
static_assert(std::is_same<sdbus::tuple_of_function_output_arg_types_t<Fnc>, std::tuple<char, int>>::value, "Incorrectly detected tuple of free function return types");
static_assert(sdbus::function_argument_count_v<Fnc> == 3, "Incorrectly detected free function parameter count");
static_assert(std::is_same<sdbus::function_result_t<Fnc>, std::tuple<char, int>>::value, "Incorrectly detected free function return type");
}
TEST(FreeFunctionTypeTraits, DetectsTraitsOfAsyncFunction)
{
void f(sdbus::Result<char, int>, double&, const char*, int);
using Fnc = decltype(f);
static_assert(sdbus::is_async_method_v<Fnc>, "Free async function incorrectly detected as sync method");
static_assert(std::is_same<sdbus::function_arguments_t<Fnc>, std::tuple<double&, const char*, int>>::value, "Incorrectly detected free function parameters");
static_assert(std::is_same<sdbus::tuple_of_function_input_arg_types_t<Fnc>, std::tuple<double, const char*, int>>::value, "Incorrectly detected tuple of free function parameters");
static_assert(std::is_same<sdbus::tuple_of_function_output_arg_types_t<Fnc>, std::tuple<char, int>>::value, "Incorrectly detected tuple of free function return types");
static_assert(sdbus::function_argument_count_v<Fnc> == 3, "Incorrectly detected free function parameter count");
static_assert(std::is_same<sdbus::function_result_t<Fnc>, std::tuple<char, int>>::value, "Incorrectly detected free function return type");
}