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
+1
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
+8 -3
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_{};
};
+27 -3
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.
+25 -3
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
+4 -3
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
+32 -13
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
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_ */
+91 -29
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_ */
+1
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>