diff --git a/ChangeLog b/ChangeLog index ccf8887d..a75888c7 100644 --- a/ChangeLog +++ b/ChangeLog @@ -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++. diff --git a/configure.ac b/configure.ac index 23521a4c..36419213 100644 --- a/configure.ac +++ b/configure.ac @@ -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]) diff --git a/doc/using-sdbus-c++.md b/doc/using-sdbus-c++.md index 5f661e63..43bc0e1b 100644 --- a/doc/using-sdbus-c++.md +++ b/doc/using-sdbus-c++.md @@ -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 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 #include -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 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`, 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& numbers, const std::string& separator)` to `void concatenate(sdbus::Result result, const std::vector& 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 + + + + + + + + + + + + + + + +``` + Using D-Bus properties ---------------------- diff --git a/include/Makefile.am b/include/Makefile.am index cc92c51d..b4f4236e 100644 --- a/include/Makefile.am +++ b/include/Makefile.am @@ -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 diff --git a/include/sdbus-c++/ConvenienceClasses.h b/include/sdbus-c++/ConvenienceClasses.h index 25fd7424..8b15f524 100644 --- a/include/sdbus-c++/ConvenienceClasses.h +++ b/include/sdbus-c++/ConvenienceClasses.h @@ -27,7 +27,9 @@ #define SDBUS_CXX_CONVENIENCECLASSES_H_ #include +#include #include +#include // 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 void implementedAs(_Function&& callback); + template + std::enable_if_t> implementedAs(_Function&& callback); + template + std::enable_if_t> 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_{}; }; diff --git a/include/sdbus-c++/ConvenienceClasses.inl b/include/sdbus-c++/ConvenienceClasses.inl index 84b68769..d9637aef 100644 --- a/include/sdbus-c++/ConvenienceClasses.inl +++ b/include/sdbus-c++/ConvenienceClasses.inl @@ -29,6 +29,7 @@ #include #include #include +#include #include #include #include @@ -52,7 +53,7 @@ namespace sdbus { } template - inline void MethodRegistrator::implementedAs(_Function&& callback) + inline std::enable_if_t> 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 + inline std::enable_if_t> 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>::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. diff --git a/include/sdbus-c++/IObject.h b/include/sdbus-c++/IObject.h index 75ccd7cf..0ef0d3a1 100755 --- a/include/sdbus-c++/IObject.h +++ b/include/sdbus-c++/IObject.h @@ -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 diff --git a/include/sdbus-c++/IObjectProxy.h b/include/sdbus-c++/IObjectProxy.h index 50e21642..81464dff 100755 --- a/include/sdbus-c++/IObjectProxy.h +++ b/include/sdbus-c++/IObjectProxy.h @@ -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 diff --git a/include/sdbus-c++/Message.h b/include/sdbus-c++/Message.h index a4d630dd..97bb0388 100644 --- a/include/sdbus-c++/Message.h +++ b/include/sdbus-c++/Message.h @@ -44,6 +44,12 @@ namespace sdbus { class ObjectPath; class Signature; template class Struct; + + class Message; + class MethodCall; + class MethodReply; + class Signal; + template 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 inline Message& operator<<(Message& msg, const std::vector<_Element>& items) { diff --git a/include/sdbus-c++/MethodResult.h b/include/sdbus-c++/MethodResult.h new file mode 100755 index 00000000..4a0378c9 --- /dev/null +++ b/include/sdbus-c++/MethodResult.h @@ -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 . + */ + +#ifndef SDBUS_CXX_METHODRESULT_H_ +#define SDBUS_CXX_METHODRESULT_H_ + +#include +#include + +// 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 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 + 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; + (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 + class Result : protected MethodResult + { + public: + Result() = default; + Result(MethodResult result); + void returnResults(const _Results&... results) const; + void returnError(const Error& error) const; + }; + + template + inline Result<_Results...>::Result(MethodResult result) + : MethodResult(std::move(result)) + { + } + + template + inline void Result<_Results...>::returnResults(const _Results&... results) const + { + MethodResult::returnResults(results...); + } + + template + inline void Result<_Results...>::returnError(const Error& error) const + { + MethodResult::returnError(error); + } + +} + +#endif /* SDBUS_CXX_METHODRESULT_H_ */ diff --git a/include/sdbus-c++/TypeTraits.h b/include/sdbus-c++/TypeTraits.h index 0b2aebde..ab898ace 100644 --- a/include/sdbus-c++/TypeTraits.h +++ b/include/sdbus-c++/TypeTraits.h @@ -26,6 +26,7 @@ #ifndef SDBUS_CXX_TYPETRAITS_H_ #define SDBUS_CXX_TYPETRAITS_H_ +#include #include #include #include @@ -40,16 +41,21 @@ namespace sdbus { class ObjectPath; class Signature; class Message; + class MethodCall; + class MethodReply; + class Signal; + class MethodResult; + template class Result; } namespace sdbus { - using method_callback = std::function; - using signal_handler = std::function; + using method_callback = std::function; + using async_method_callback = std::function; + using signal_handler = std::function; using property_set_callback = std::function; using property_get_callback = std::function; - // Primary template template 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 struct function_traits : public function_traits @@ -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 - struct function_traits<_ReturnType(_Args...)> + struct function_traits_base { typedef _ReturnType result_type; typedef std::tuple<_Args...> arguments_type; + typedef std::tuple...> decayed_arguments_type; typedef _ReturnType function_type(_Args...); static constexpr std::size_t arity = sizeof...(_Args); +// template +// struct arg; +// +// template +// struct arg<_Idx, std::enable_if_t<(_Idx < arity)>> +// { +// typedef std::tuple_element_t<_Idx, arguments_type> type; +// }; +// +// template +// struct arg<_Idx, std::enable_if_t> +// { +// typedef void type; +// }; + template struct arg { @@ -338,6 +361,20 @@ namespace sdbus { using arg_t = typename arg<_Idx>::type; }; + template + struct function_traits<_ReturnType(_Args...)> + : public function_traits_base<_ReturnType, _Args...> + { + static constexpr bool is_async = false; + }; + + template + struct function_traits, _Args...)> + : public function_traits_base, _Args...> + { + static constexpr bool is_async = true; + }; + template struct function_traits<_ReturnType(*)(_Args...)> : public function_traits<_ReturnType(_Args...)> @@ -376,12 +413,39 @@ namespace sdbus { : public function_traits {}; + template + constexpr auto is_async_method_v = function_traits<_Function>::is_async; + + template + using function_arguments_t = typename function_traits<_FunctionType>::arguments_type; + template using function_argument_t = typename function_traits<_FunctionType>::template arg_t<_Idx>; + template + constexpr auto function_argument_count_v = function_traits<_FunctionType>::arity; + template using function_result_t = typename function_traits<_FunctionType>::result_type; + template + struct tuple_of_function_input_arg_types + { + typedef typename function_traits<_Function>::decayed_arguments_type type; + }; + + template + using tuple_of_function_input_arg_types_t = typename tuple_of_function_input_arg_types<_Function>::type; + + template + struct tuple_of_function_output_arg_types + { + typedef typename function_traits<_Function>::result_type type; + }; + + template + using tuple_of_function_output_arg_types_t = typename tuple_of_function_output_arg_types<_Function>::type; + template 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 signatures{signature_of>::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 - struct tuple_of_function_input_arg_types - : public tuple_of_function_input_arg_types::function_type> - {}; - - // Get a tuple of function input argument types from function signature. - // Function signature is expected in the standardized form `out(in...)'. - template - 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 v, double d) - // the `type' will be `std::tuple, double>'. - typedef std::tuple...> type; - }; - - template - using tuple_of_function_input_arg_types_t = typename tuple_of_function_input_arg_types<_Function>::type; - template struct signature_of_function_input_arguments { @@ -440,12 +483,21 @@ namespace sdbus { { static const std::string str() { - return aggregate_signature>::str(); + return aggregate_signature>::str(); } }; namespace detail { + template + 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 @@ -480,6 +532,16 @@ namespace sdbus { , std::make_index_sequence>::value>{} ); } + // Convert tuple `t' of values into a list of arguments + // and invoke function `f' with those arguments. + template + 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>::value>{} ); + } } #endif /* SDBUS_CXX_TYPETRAITS_H_ */ diff --git a/include/sdbus-c++/sdbus-c++.h b/include/sdbus-c++/sdbus-c++.h index e17b8488..0ff8afac 100644 --- a/include/sdbus-c++/sdbus-c++.h +++ b/include/sdbus-c++/sdbus-c++.h @@ -28,6 +28,7 @@ #include #include #include +#include #include #include #include diff --git a/src/Connection.cpp b/src/Connection.cpp index 75985910..1f81d791 100755 --- a/src/Connection.cpp +++ b/src/Connection.cpp @@ -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 guard(mutex_); + asyncReplies_.push(reply); + notifyProcessingLoop(); +} + std::unique_ptr Connection::clone() const { return std::make_unique(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::notifyProcessingLoopToExit() +void Connection::notifyProcessingLoop() { - assert(exitLoopFd_ >= 0); + assert(notificationFd_ >= 0); uint64_t value = 1; - write(exitLoopFd_, &value, sizeof(value)); + write(notificationFd_, &value, sizeof(value)); +} + +void Connection::notifyProcessingLoopToExit() +{ + 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 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 + + // Otherwise we have some async messages to process + return {false, true}; + } - return true; + return {true, false}; } std::string Connection::composeSignalMatchFilter( const std::string& objectPath diff --git a/src/Connection.h b/src/Connection.h index bcb52106..40abc124 100755 --- a/src/Connection.h +++ b/src/Connection.h @@ -27,12 +27,14 @@ #define SDBUS_CXX_INTERNAL_CONNECTION_H_ #include +#include +#include "IConnection.h" #include #include -#include #include - -#include "IConnection.h" +#include +#include +#include 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 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 bus_{nullptr, &sd_bus_flush_close_unref}; std::thread asyncLoopThread_; - std::atomic exitLoopFd_{-1}; + std::mutex mutex_; + std::queue asyncReplies_; + std::atomic exitLoopThread_; + int notificationFd_{-1}; BusType busType_; static constexpr const uint64_t POLL_TIMEOUT_USEC = 500000; diff --git a/src/IConnection.h b/src/IConnection.h index f7183fbc..dba264c2 100755 --- a/src/IConnection.h +++ b/src/IConnection.h @@ -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 clone() const = 0; virtual ~IConnection() = default; diff --git a/src/Makefile.am b/src/Makefile.am index 810449c2..e8d45255 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -5,6 +5,7 @@ libsdbus_c___la_SOURCES = \ Connection.cpp \ ConvenienceClasses.cpp \ Message.cpp \ + MethodResult.cpp \ Object.cpp \ ObjectProxy.cpp \ Types.cpp \ diff --git a/src/Message.cpp b/src/Message.cpp index a43603b3..6aefc8c7 100755 --- a/src/Message.cpp +++ b/src/Message.cpp @@ -31,11 +31,10 @@ #include #include -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 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 { - return type_; + 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); } -/*}*/} +} diff --git a/src/MethodResult.cpp b/src/MethodResult.cpp new file mode 100644 index 00000000..9dd91499 --- /dev/null +++ b/src/MethodResult.cpp @@ -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 . + */ + +#include +#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); +} + +} diff --git a/src/Object.cpp b/src/Object.cpp index 6cff62c8..b35dd5d5 100755 --- a/src/Object.cpp +++ b/src/Object.cpp @@ -27,6 +27,7 @@ #include #include #include +#include #include "IConnection.h" #include "VTableUtils.h" #include @@ -48,9 +49,36 @@ void Object::registerMethod( const std::string& interfaceName { SDBUS_THROW_ERROR_IF(!methodCallback, "Invalid method callback provided", EINVAL); + auto syncCallback = [callback = std::move(methodCallback)](MethodCall& msg) + { + auto reply = msg.createReply(); + callback(msg, reply); + reply.send(); + }; + 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); - InterfaceData::MethodData methodData{inputSignature, outputSignature, std::move(methodCallback)}; + 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& 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(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(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(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; diff --git a/src/Object.h b/src/Object.h index 5c254d32..f3e3a3ae 100644 --- a/src/Object.h +++ b/src/Object.h @@ -32,6 +32,7 @@ #include #include #include +#include #include #include @@ -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 callback_; }; std::map methods_; using SignalName = std::string; diff --git a/src/ObjectProxy.cpp b/src/ObjectProxy.cpp index 65643f34..c3983b9a 100755 --- a/src/ObjectProxy.cpp +++ b/src/ObjectProxy.cpp @@ -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(userData); // Note: The lookup can be optimized by using sorted vectors instead of associative containers diff --git a/src/ObjectProxy.h b/src/ObjectProxy.h index b80beac7..df59a769 100755 --- a/src/ObjectProxy.h +++ b/src/ObjectProxy.h @@ -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 diff --git a/stub-generator/AdaptorGenerator.cpp b/stub-generator/AdaptorGenerator.cpp index f50acad9..92e3e935 100644 --- a/stub-generator/AdaptorGenerator.cpp +++ b/stub-generator/AdaptorGenerator.cpp @@ -136,25 +136,47 @@ std::tuple 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()); diff --git a/stub-generator/BaseGenerator.cpp b/stub-generator/BaseGenerator.cpp index 2d3db2fc..fe90ef2e 100644 --- a/stub-generator/BaseGenerator.cpp +++ b/stub-generator/BaseGenerator.cpp @@ -126,12 +126,15 @@ std::tuple 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(); diff --git a/stub-generator/BaseGenerator.h b/stub-generator/BaseGenerator.h index 9f42b719..ce482e95 100644 --- a/stub-generator/BaseGenerator.h +++ b/stub-generator/BaseGenerator.h @@ -94,7 +94,7 @@ class BaseGenerator * @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; }; diff --git a/stub-generator/CMakeLists.txt b/stub-generator/CMakeLists.txt index e68f252d..5dfab3c1 100644 --- a/stub-generator/CMakeLists.txt +++ b/stub-generator/CMakeLists.txt @@ -1,4 +1,4 @@ -cmake_minimum_required (VERSION 3.3) +cmake_minimum_required (VERSION 3.4) add_definitions(-std=c++14) diff --git a/test/Makefile.am b/test/Makefile.am index a060920d..a1fbe03e 100644 --- a/test/Makefile.am +++ b/test/Makefile.am @@ -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 diff --git a/test/integrationtests/AdaptorAndProxy_test.cpp b/test/integrationtests/AdaptorAndProxy_test.cpp index 3e914a16..ee4e0216 100644 --- a/test/integrationtests/AdaptorAndProxy_test.cpp +++ b/test/integrationtests/AdaptorAndProxy_test.cpp @@ -40,9 +40,12 @@ #include #include #include +#include using ::testing::Eq; using ::testing::Gt; +using ::testing::ElementsAre; +using namespace std::chrono_literals; namespace { @@ -67,7 +70,7 @@ class AdaptorAndProxyFixture : public ::testing::Test { m_adaptor = std::make_unique(m_connection); m_proxy = std::make_unique(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 results; + std::atomic invoke{}; + std::atomic startedCount{}; + auto call = [&](uint32_t param) + { + TestingProxy proxy{INTERFACE_NAME, OBJECT_PATH}; + ++startedCount; + while (!invoke) ; + auto result = proxy.doOperationAsync(param); + std::lock_guard 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); diff --git a/test/integrationtests/TestingAdaptor.h b/test/integrationtests/TestingAdaptor.h index db102776..f5136fe9 100644 --- a/test/integrationtests/TestingAdaptor.h +++ b/test/integrationtests/TestingAdaptor.h @@ -27,6 +27,8 @@ #define SDBUS_CPP_INTEGRATIONTESTS_TESTINGADAPTOR_H_ #include "adaptor-glue.h" +#include +#include class TestingAdaptor : public sdbus::Interfaces { @@ -97,6 +99,22 @@ class TestingAdaptor : public sdbus::Interfaces 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 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; } diff --git a/test/integrationtests/adaptor-glue.h b/test/integrationtests/adaptor-glue.h index b23d3671..641cc81b 100644 --- a/test/integrationtests/adaptor-glue.h +++ b/test/integrationtests/adaptor-glue.h @@ -82,6 +82,16 @@ class testing_adaptor 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 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 @@ class testing_adaptor virtual sdbus::Struct>> getStructInStruct() const = 0; virtual int32_t sumStructItems(const sdbus::Struct& a, const sdbus::Struct& b) = 0; virtual uint32_t sumVectorItems(const std::vector& a, const std::vector& b) = 0; + virtual uint32_t doOperationSync(uint32_t param) = 0; + virtual void doOperationAsync(uint32_t param, sdbus::Result result) = 0; virtual sdbus::Signature getSignature() const = 0; virtual sdbus::ObjectPath getObjectPath() const = 0; virtual ComplexType getComplex() const = 0; diff --git a/test/integrationtests/proxy-glue.h b/test/integrationtests/proxy-glue.h index 172a72d4..8db4d230 100644 --- a/test/integrationtests/proxy-glue.h +++ b/test/integrationtests/proxy-glue.h @@ -120,6 +120,20 @@ class testing_proxy 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; diff --git a/test/unittests/Message_test.cpp b/test/unittests/Message_test.cpp index ca57da56..91a94d6f 100644 --- a/test/unittests/Message_test.cpp +++ b/test/unittests/Message_test.cpp @@ -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()}; diff --git a/test/unittests/TypeTraits_test.cpp b/test/unittests/TypeTraits_test.cpp index a811f514..db95db04 100644 --- a/test/unittests/TypeTraits_test.cpp +++ b/test/unittests/TypeTraits_test.cpp @@ -28,6 +28,7 @@ #include #include #include +#include using ::testing::Eq; @@ -127,3 +128,42 @@ TYPED_TEST(Type2DBusTypeSignatureConversion, ConvertsTypeToProperDBusSignature) { ASSERT_THAT(sdbus::signature_of::str(), Eq(this->dbusTypeSignature_)); } + +TEST(FreeFunctionTypeTraits, DetectsTraitsOfTrivialSignatureFunction) +{ + void f(); + using Fnc = decltype(f); + + static_assert(!sdbus::is_async_method_v, "Free function incorrectly detected as async method"); + static_assert(std::is_same, std::tuple<>>::value, "Incorrectly detected free function parameters"); + static_assert(std::is_same, std::tuple<>>::value, "Incorrectly detected tuple of free function parameters"); + static_assert(std::is_same, void>::value, "Incorrectly detected tuple of free function return types"); + static_assert(sdbus::function_argument_count_v == 0, "Incorrectly detected free function parameter count"); + static_assert(std::is_void>::value, "Incorrectly detected free function return type"); +} + +TEST(FreeFunctionTypeTraits, DetectsTraitsOfNontrivialSignatureFunction) +{ + std::tuple f(double&, const char*, int); + using Fnc = decltype(f); + + static_assert(!sdbus::is_async_method_v, "Free function incorrectly detected as async method"); + static_assert(std::is_same, std::tuple>::value, "Incorrectly detected free function parameters"); + static_assert(std::is_same, std::tuple>::value, "Incorrectly detected tuple of free function parameters"); + static_assert(std::is_same, std::tuple>::value, "Incorrectly detected tuple of free function return types"); + static_assert(sdbus::function_argument_count_v == 3, "Incorrectly detected free function parameter count"); + static_assert(std::is_same, std::tuple>::value, "Incorrectly detected free function return type"); +} + +TEST(FreeFunctionTypeTraits, DetectsTraitsOfAsyncFunction) +{ + void f(sdbus::Result, double&, const char*, int); + using Fnc = decltype(f); + + static_assert(sdbus::is_async_method_v, "Free async function incorrectly detected as sync method"); + static_assert(std::is_same, std::tuple>::value, "Incorrectly detected free function parameters"); + static_assert(std::is_same, std::tuple>::value, "Incorrectly detected tuple of free function parameters"); + static_assert(std::is_same, std::tuple>::value, "Incorrectly detected tuple of free function return types"); + static_assert(sdbus::function_argument_count_v == 3, "Incorrectly detected free function parameter count"); + static_assert(std::is_same, std::tuple>::value, "Incorrectly detected free function return type"); +}