Add support for custom timeout value for D-Bus method calls (#72)

This commit is contained in:
lubo-svk
2019-11-03 13:54:13 +01:00
committed by Stanislav Angelovič
parent e7155c5506
commit c139110112
24 changed files with 296 additions and 29 deletions

View File

@@ -98,7 +98,7 @@ option(BUILD_SHARED_LIBS "Build shared libraries (.so) instead of static ones (.
# Having an object target allows unit tests to reuse already built sources without re-building # Having an object target allows unit tests to reuse already built sources without re-building
add_library(sdbus-c++-objlib OBJECT ${SDBUSCPP_SRCS}) add_library(sdbus-c++-objlib OBJECT ${SDBUSCPP_SRCS})
target_compile_definitions(sdbus-c++-objlib PRIVATE BUILDLIB=1) target_compile_definitions(sdbus-c++-objlib PRIVATE BUILD_LIB=1 LIBSYSTEMD_VERSION=${SYSTEMD_VERSION})
target_include_directories(sdbus-c++-objlib PUBLIC $<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/include> target_include_directories(sdbus-c++-objlib PUBLIC $<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/include>
$<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/src> $<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/src>
$<BUILD_INTERFACE:${SYSTEMD_INCLUDE_DIRS}>) $<BUILD_INTERFACE:${SYSTEMD_INCLUDE_DIRS}>)

View File

@@ -50,6 +50,8 @@ set(SYSTEMD_INCLUDE_DIRS ${SOURCE_DIR}/src)
ExternalProject_Get_property(LibsystemdBuildProject BINARY_DIR) ExternalProject_Get_property(LibsystemdBuildProject BINARY_DIR)
set(SYSTEMD_LIBRARY_DIRS ${BINARY_DIR}) set(SYSTEMD_LIBRARY_DIRS ${BINARY_DIR})
set(SYSTEMD_VERSION ${LIBSYSTEMD_VERSION})
add_library(Systemd::Libsystemd STATIC IMPORTED) add_library(Systemd::Libsystemd STATIC IMPORTED)
set_target_properties(Systemd::Libsystemd PROPERTIES IMPORTED_LOCATION ${SYSTEMD_LIBRARY_DIRS}/libsystemd.a) set_target_properties(Systemd::Libsystemd PROPERTIES IMPORTED_LOCATION ${SYSTEMD_LIBRARY_DIRS}/libsystemd.a)
set(SYSTEMD_LIBRARIES Systemd::Libsystemd ${CAP_LIBRARIES} ${GLIBC_RT_LIBRARY} ${MOUNT_LIBRARIES}) set(SYSTEMD_LIBRARIES Systemd::Libsystemd ${CAP_LIBRARIES} ${GLIBC_RT_LIBRARY} ${MOUNT_LIBRARIES})

View File

@@ -32,6 +32,8 @@
#include <sdbus-c++/Flags.h> #include <sdbus-c++/Flags.h>
#include <string> #include <string>
#include <type_traits> #include <type_traits>
#include <chrono>
#include <cstdint>
// Forward declarations // Forward declarations
namespace sdbus { namespace sdbus {
@@ -164,6 +166,9 @@ namespace sdbus {
~MethodInvoker() noexcept(false); ~MethodInvoker() noexcept(false);
MethodInvoker& onInterface(const std::string& interfaceName); MethodInvoker& onInterface(const std::string& interfaceName);
MethodInvoker& withTimeout(uint64_t usec);
template <typename _Rep, typename _Period>
MethodInvoker& withTimeout(const std::chrono::duration<_Rep, _Period>& timeout);
template <typename... _Args> MethodInvoker& withArguments(_Args&&... args); template <typename... _Args> MethodInvoker& withArguments(_Args&&... args);
template <typename... _Args> void storeResultsTo(_Args&... args); template <typename... _Args> void storeResultsTo(_Args&... args);
@@ -172,6 +177,7 @@ namespace sdbus {
private: private:
IProxy& proxy_; IProxy& proxy_;
const std::string& methodName_; const std::string& methodName_;
uint64_t timeout_{};
MethodCall method_; MethodCall method_;
int exceptions_{}; // Number of active exceptions when MethodInvoker is constructed int exceptions_{}; // Number of active exceptions when MethodInvoker is constructed
bool methodCalled_{}; bool methodCalled_{};
@@ -182,12 +188,16 @@ namespace sdbus {
public: public:
AsyncMethodInvoker(IProxy& proxy, const std::string& methodName); AsyncMethodInvoker(IProxy& proxy, const std::string& methodName);
AsyncMethodInvoker& onInterface(const std::string& interfaceName); AsyncMethodInvoker& onInterface(const std::string& interfaceName);
AsyncMethodInvoker& withTimeout(uint64_t usec);
template <typename _Rep, typename _Period>
AsyncMethodInvoker& withTimeout(const std::chrono::duration<_Rep, _Period>& timeout);
template <typename... _Args> AsyncMethodInvoker& withArguments(_Args&&... args); template <typename... _Args> AsyncMethodInvoker& withArguments(_Args&&... args);
template <typename _Function> void uponReplyInvoke(_Function&& callback); template <typename _Function> void uponReplyInvoke(_Function&& callback);
private: private:
IProxy& proxy_; IProxy& proxy_;
const std::string& methodName_; const std::string& methodName_;
uint64_t timeout_{};
AsyncMethodCall method_; AsyncMethodCall method_;
}; };

View File

@@ -40,6 +40,10 @@
namespace sdbus { namespace sdbus {
/*** ----------------- ***/
/*** MethodRegistrator ***/
/*** ----------------- ***/
// Moved into the library to isolate from C++17 dependency // Moved into the library to isolate from C++17 dependency
/* /*
inline MethodRegistrator::MethodRegistrator(IObject& object, const std::string& methodName) inline MethodRegistrator::MethodRegistrator(IObject& object, const std::string& methodName)
@@ -149,6 +153,9 @@ namespace sdbus {
return *this; return *this;
} }
/*** ----------------- ***/
/*** SignalRegistrator ***/
/*** ----------------- ***/
// Moved into the library to isolate from C++17 dependency // Moved into the library to isolate from C++17 dependency
/* /*
@@ -203,6 +210,9 @@ namespace sdbus {
return *this; return *this;
} }
/*** ------------------- ***/
/*** PropertyRegistrator ***/
/*** ------------------- ***/
// Moved into the library to isolate from C++17 dependency // Moved into the library to isolate from C++17 dependency
/* /*
@@ -309,6 +319,9 @@ namespace sdbus {
return *this; return *this;
} }
/*** -------------------- ***/
/*** InterfaceFlagsSetter ***/
/*** -------------------- ***/
// Moved into the library to isolate from C++17 dependency // Moved into the library to isolate from C++17 dependency
/* /*
@@ -369,6 +382,9 @@ namespace sdbus {
return *this; return *this;
} }
/*** ------------- ***/
/*** SignalEmitter ***/
/*** ------------- ***/
// Moved into the library to isolate from C++17 dependency // Moved into the library to isolate from C++17 dependency
/* /*
@@ -416,6 +432,9 @@ namespace sdbus {
detail::serialize_pack(signal_, std::forward<_Args>(args)...); detail::serialize_pack(signal_, std::forward<_Args>(args)...);
} }
/*** ------------- ***/
/*** MethodInvoker ***/
/*** ------------- ***/
// Moved into the library to isolate from C++17 dependency // Moved into the library to isolate from C++17 dependency
/* /*
@@ -456,6 +475,20 @@ namespace sdbus {
return *this; return *this;
} }
inline MethodInvoker& MethodInvoker::withTimeout(uint64_t usec)
{
timeout_ = usec;
return *this;
}
template <typename _Rep, typename _Period>
inline MethodInvoker& MethodInvoker::withTimeout(const std::chrono::duration<_Rep, _Period>& timeout)
{
auto microsecs = std::chrono::duration_cast<std::chrono::microseconds>(timeout);
return withTimeout(microsecs.count());
}
template <typename... _Args> template <typename... _Args>
inline MethodInvoker& MethodInvoker::withArguments(_Args&&... args) inline MethodInvoker& MethodInvoker::withArguments(_Args&&... args)
{ {
@@ -471,7 +504,7 @@ namespace sdbus {
{ {
SDBUS_THROW_ERROR_IF(!method_.isValid(), "DBus interface not specified when calling a DBus method", EINVAL); SDBUS_THROW_ERROR_IF(!method_.isValid(), "DBus interface not specified when calling a DBus method", EINVAL);
auto reply = proxy_.callMethod(method_); auto reply = proxy_.callMethod(method_, timeout_);
methodCalled_ = true; methodCalled_ = true;
detail::deserialize_pack(reply, args...); detail::deserialize_pack(reply, args...);
@@ -484,6 +517,9 @@ namespace sdbus {
method_.dontExpectReply(); method_.dontExpectReply();
} }
/*** ------------------ ***/
/*** AsyncMethodInvoker ***/
/*** ------------------ ***/
inline AsyncMethodInvoker::AsyncMethodInvoker(IProxy& proxy, const std::string& methodName) inline AsyncMethodInvoker::AsyncMethodInvoker(IProxy& proxy, const std::string& methodName)
: proxy_(proxy) : proxy_(proxy)
@@ -498,6 +534,20 @@ namespace sdbus {
return *this; return *this;
} }
inline AsyncMethodInvoker& AsyncMethodInvoker::withTimeout(uint64_t usec)
{
timeout_ = usec;
return *this;
}
template <typename _Rep, typename _Period>
inline AsyncMethodInvoker& AsyncMethodInvoker::withTimeout(const std::chrono::duration<_Rep, _Period>& timeout)
{
auto microsecs = std::chrono::duration_cast<std::chrono::microseconds>(timeout);
return withTimeout(microsecs.count());
}
template <typename... _Args> template <typename... _Args>
inline AsyncMethodInvoker& AsyncMethodInvoker::withArguments(_Args&&... args) inline AsyncMethodInvoker& AsyncMethodInvoker::withArguments(_Args&&... args)
{ {
@@ -513,7 +563,7 @@ namespace sdbus {
{ {
SDBUS_THROW_ERROR_IF(!method_.isValid(), "DBus interface not specified when calling a DBus method", EINVAL); SDBUS_THROW_ERROR_IF(!method_.isValid(), "DBus interface not specified when calling a DBus method", EINVAL);
proxy_.callMethod(method_, [callback = std::forward<_Function>(callback)](MethodReply& reply, const Error* error) auto asyncReplyHandler = [callback = std::forward<_Function>(callback)](MethodReply& reply, const Error* error)
{ {
// Create a tuple of callback input arguments' types, which will be used // Create a tuple of callback input arguments' types, which will be used
// as a storage for the argument values deserialized from the message. // as a storage for the argument values deserialized from the message.
@@ -525,9 +575,14 @@ namespace sdbus {
// Invoke callback with input arguments from the tuple. // Invoke callback with input arguments from the tuple.
sdbus::apply(callback, error, args); // TODO: Use std::apply when switching to full C++17 support sdbus::apply(callback, error, args); // TODO: Use std::apply when switching to full C++17 support
}); };
proxy_.callMethod(method_, std::move(asyncReplyHandler), timeout_);
} }
/*** ---------------- ***/
/*** SignalSubscriber ***/
/*** ---------------- ***/
inline SignalSubscriber::SignalSubscriber(IProxy& proxy, const std::string& signalName) inline SignalSubscriber::SignalSubscriber(IProxy& proxy, const std::string& signalName)
: proxy_(proxy) : proxy_(proxy)
@@ -563,6 +618,9 @@ namespace sdbus {
}); });
} }
/*** -------------- ***/
/*** PropertyGetter ***/
/*** -------------- ***/
inline PropertyGetter::PropertyGetter(IProxy& proxy, const std::string& propertyName) inline PropertyGetter::PropertyGetter(IProxy& proxy, const std::string& propertyName)
: proxy_(proxy) : proxy_(proxy)
@@ -581,6 +639,9 @@ namespace sdbus {
return var; return var;
} }
/*** -------------- ***/
/*** PropertySetter ***/
/*** -------------- ***/
inline PropertySetter::PropertySetter(IProxy& proxy, const std::string& propertyName) inline PropertySetter::PropertySetter(IProxy& proxy, const std::string& propertyName)
: proxy_(proxy) : proxy_(proxy)

View File

@@ -27,9 +27,10 @@
#ifndef SDBUS_CXX_ICONNECTION_H_ #ifndef SDBUS_CXX_ICONNECTION_H_
#define SDBUS_CXX_ICONNECTION_H_ #define SDBUS_CXX_ICONNECTION_H_
//#include <cstdint>
#include <string> #include <string>
#include <memory> #include <memory>
#include <chrono>
#include <cstdint>
namespace sdbus { namespace sdbus {
@@ -145,8 +146,46 @@ namespace sdbus {
* @throws sdbus::Error in case of failure * @throws sdbus::Error in case of failure
*/ */
virtual bool processPendingRequest() = 0; virtual bool processPendingRequest() = 0;
/*!
* @brief Sets general method call timeout
*
* @param[in] timeout Timeout value in microseconds
*
* General method call timeout is used for all method calls upon this connection.
* Method call-specific timeout overrides this general setting.
*
* Supported by libsystemd>=v240.
*
* @throws sdbus::Error in case of failure
*/
virtual void setMethodCallTimeout(uint64_t timeout) = 0;
/*!
* @copydoc IConnection::setMethodCallTimeout(uint64_t)
*/
template <typename _Rep, typename _Period>
void setMethodCallTimeout(const std::chrono::duration<_Rep, _Period>& timeout);
/*!
* @brief Gets general method call timeout
*
* @return Timeout value in microseconds
*
* Supported by libsystemd>=v240.
*
* @throws sdbus::Error in case of failure
*/
virtual uint64_t getMethodCallTimeout() const = 0;
}; };
template <typename _Rep, typename _Period>
inline void IConnection::setMethodCallTimeout(const std::chrono::duration<_Rep, _Period>& timeout)
{
auto microsecs = std::chrono::duration_cast<std::chrono::microseconds>(timeout);
return setMethodCallTimeout(microsecs.count());
}
/*! /*!
* @brief Creates/opens D-Bus system connection * @brief Creates/opens D-Bus system connection
* *

View File

@@ -387,6 +387,8 @@ namespace sdbus {
SignalEmitter emitSignal(const std::string& signalName); SignalEmitter emitSignal(const std::string& signalName);
}; };
// Out-of-line member definitions
inline MethodRegistrator IObject::registerMethod(const std::string& methodName) inline MethodRegistrator IObject::registerMethod(const std::string& methodName)
{ {
return MethodRegistrator(*this, methodName); return MethodRegistrator(*this, methodName);

View File

@@ -31,6 +31,7 @@
#include <string> #include <string>
#include <memory> #include <memory>
#include <functional> #include <functional>
#include <chrono>
// Forward declarations // Forward declarations
namespace sdbus { namespace sdbus {
@@ -95,6 +96,7 @@ namespace sdbus {
* @brief Calls method on the proxied D-Bus object * @brief Calls method on the proxied D-Bus object
* *
* @param[in] message Message representing a method call * @param[in] message Message representing a method call
* @param[in] timeout Timeout for dbus call in microseconds
* @return A method reply message * @return A method reply message
* *
* Normally, the call is blocking, i.e. it waits for the remote method to finish with either * Normally, the call is blocking, i.e. it waits for the remote method to finish with either
@@ -108,13 +110,20 @@ namespace sdbus {
* *
* @throws sdbus::Error in case of failure * @throws sdbus::Error in case of failure
*/ */
virtual MethodReply callMethod(const MethodCall& message) = 0; virtual MethodReply callMethod(const MethodCall& message, uint64_t timeout = 0) = 0;
/*!
* @copydoc IProxy::callMethod(const MethodCall&,uint64_t)
*/
template <typename _Rep, typename _Period>
MethodReply callMethod(const MethodCall& message, const std::chrono::duration<_Rep, _Period>& timeout);
/*! /*!
* @brief Calls method on the proxied D-Bus object asynchronously * @brief Calls method on the proxied D-Bus object asynchronously
* *
* @param[in] message Message representing an async method call * @param[in] message Message representing an async method call
* @param[in] asyncReplyCallback Handler for the async reply * @param[in] asyncReplyCallback Handler for the async reply
* @param[in] timeout Timeout for dbus call in microseconds
* *
* The call is non-blocking. It doesn't wait for the reply. Once the reply arrives, * The call is non-blocking. It doesn't wait for the reply. Once the reply arrives,
* the provided async reply handler will get invoked from the context of the connection * the provided async reply handler will get invoked from the context of the connection
@@ -124,7 +133,13 @@ namespace sdbus {
* *
* @throws sdbus::Error in case of failure * @throws sdbus::Error in case of failure
*/ */
virtual void callMethod(const AsyncMethodCall& message, async_reply_handler asyncReplyCallback) = 0; virtual void callMethod(const AsyncMethodCall& message, async_reply_handler asyncReplyCallback, uint64_t timeout = 0) = 0;
/*!
* @copydoc IProxy::callMethod(const AsyncMethodCall&,async_reply_handler,uint64_t)
*/
template <typename _Rep, typename _Period>
void callMethod(const AsyncMethodCall& message, async_reply_handler asyncReplyCallback, const std::chrono::duration<_Rep, _Period>& timeout);
/*! /*!
* @brief Registers a handler for the desired signal emitted by the proxied D-Bus object * @brief Registers a handler for the desired signal emitted by the proxied D-Bus object
@@ -264,6 +279,22 @@ namespace sdbus {
PropertySetter setProperty(const std::string& propertyName); PropertySetter setProperty(const std::string& propertyName);
}; };
// Out-of-line member definitions
template <typename _Rep, typename _Period>
inline MethodReply IProxy::callMethod(const MethodCall& message, const std::chrono::duration<_Rep, _Period>& timeout)
{
auto microsecs = std::chrono::duration_cast<std::chrono::microseconds>(timeout);
return callMethod(message, microsecs.count());
}
template <typename _Rep, typename _Period>
inline void IProxy::callMethod(const AsyncMethodCall& message, async_reply_handler asyncReplyCallback, const std::chrono::duration<_Rep, _Period>& timeout)
{
auto microsecs = std::chrono::duration_cast<std::chrono::microseconds>(timeout);
callMethod(message, std::move(asyncReplyCallback), microsecs.count());
}
inline MethodInvoker IProxy::callMethod(const std::string& methodName) inline MethodInvoker IProxy::callMethod(const std::string& methodName)
{ {
return MethodInvoker(*this, methodName); return MethodInvoker(*this, methodName);

View File

@@ -172,14 +172,14 @@ namespace sdbus {
public: public:
MethodCall() = default; MethodCall() = default;
MethodReply send() const; MethodReply send(uint64_t timeout = 0) const;
MethodReply createReply() const; MethodReply createReply() const;
MethodReply createErrorReply(const sdbus::Error& error) const; MethodReply createErrorReply(const sdbus::Error& error) const;
void dontExpectReply(); void dontExpectReply();
bool doesntExpectReply() const; bool doesntExpectReply() const;
private: private:
MethodReply sendWithReply() const; MethodReply sendWithReply(uint64_t timeout) const;
MethodReply sendWithNoReply() const; MethodReply sendWithNoReply() const;
}; };
@@ -193,7 +193,7 @@ namespace sdbus {
AsyncMethodCall() = default; AsyncMethodCall() = default;
explicit AsyncMethodCall(MethodCall&& call) noexcept; explicit AsyncMethodCall(MethodCall&& call) noexcept;
Slot send(void* callback, void* userData) const; Slot send(void* callback, void* userData, uint64_t timeout = 0) const;
}; };
class MethodReply : public Message class MethodReply : public Message

View File

@@ -145,6 +145,24 @@ SlotPtr Connection::addObjectManager(const std::string& objectPath, void* /*dumm
return {slot, [this](void *slot){ iface_->sd_bus_slot_unref((sd_bus_slot*)slot); }}; return {slot, [this](void *slot){ iface_->sd_bus_slot_unref((sd_bus_slot*)slot); }};
} }
void Connection::setMethodCallTimeout(uint64_t timeout)
{
auto r = iface_->sd_bus_set_method_call_timeout(bus_.get(), timeout);
SDBUS_THROW_ERROR_IF(r < 0, "Failed to set method call timeout", -r);
}
uint64_t Connection::getMethodCallTimeout() const
{
uint64_t timeout;
auto r = iface_->sd_bus_get_method_call_timeout(bus_.get(), &timeout);
SDBUS_THROW_ERROR_IF(r < 0, "Failed to get method call timeout", -r);
return timeout;
}
SlotPtr Connection::addObjectVTable( const std::string& objectPath SlotPtr Connection::addObjectVTable( const std::string& objectPath
, const std::string& interfaceName , const std::string& interfaceName
, const sd_bus_vtable* vtable , const sd_bus_vtable* vtable

View File

@@ -63,6 +63,9 @@ namespace sdbus { namespace internal {
void addObjectManager(const std::string& objectPath) override; void addObjectManager(const std::string& objectPath) override;
SlotPtr addObjectManager(const std::string& objectPath, void* /*dummy*/) override; SlotPtr addObjectManager(const std::string& objectPath, void* /*dummy*/) override;
void setMethodCallTimeout(uint64_t timeout) override;
uint64_t getMethodCallTimeout() const override;
const ISdBus& getSdBusInterface() const override; const ISdBus& getSdBusInterface() const override;
ISdBus& getSdBusInterface() override; ISdBus& getSdBusInterface() override;

View File

@@ -203,7 +203,7 @@ MethodInvoker::~MethodInvoker() noexcept(false) // since C++11, destructors must
// Therefore, we can allow callMethod() to throw even if we are in the destructor. // Therefore, we can allow callMethod() to throw even if we are in the destructor.
// Bottomline is, to be on the safe side, the caller must take care of catching and reacting // Bottomline is, to be on the safe side, the caller must take care of catching and reacting
// to the exception thrown from here if the caller is a destructor itself. // to the exception thrown from here if the caller is a destructor itself.
proxy_.callMethod(method_); proxy_.callMethod(method_, timeout_);
} }
} }

View File

@@ -42,6 +42,8 @@ namespace sdbus { namespace internal {
uint64_t timeout_usec; uint64_t timeout_usec;
}; };
virtual ~ISdBus() = default;
virtual sd_bus_message* sd_bus_message_ref(sd_bus_message *m) = 0; virtual sd_bus_message* sd_bus_message_ref(sd_bus_message *m) = 0;
virtual sd_bus_message* sd_bus_message_unref(sd_bus_message *m) = 0; virtual sd_bus_message* sd_bus_message_unref(sd_bus_message *m) = 0;
@@ -54,6 +56,9 @@ namespace sdbus { namespace internal {
virtual int sd_bus_message_new_method_return(sd_bus_message *call, sd_bus_message **m) = 0; virtual int sd_bus_message_new_method_return(sd_bus_message *call, sd_bus_message **m) = 0;
virtual int sd_bus_message_new_method_error(sd_bus_message *call, sd_bus_message **m, const sd_bus_error *e) = 0; virtual int sd_bus_message_new_method_error(sd_bus_message *call, sd_bus_message **m, const sd_bus_error *e) = 0;
virtual int sd_bus_set_method_call_timeout(sd_bus *bus, uint64_t usec) = 0;
virtual int sd_bus_get_method_call_timeout(sd_bus *bus, uint64_t *ret) = 0;
virtual int sd_bus_emit_properties_changed_strv(sd_bus *bus, const char *path, const char *interface, char **names) = 0; virtual int sd_bus_emit_properties_changed_strv(sd_bus *bus, const char *path, const char *interface, char **names) = 0;
virtual int sd_bus_emit_object_added(sd_bus *bus, const char *path) = 0; virtual int sd_bus_emit_object_added(sd_bus *bus, const char *path) = 0;
virtual int sd_bus_emit_object_removed(sd_bus *bus, const char *path) = 0; virtual int sd_bus_emit_object_removed(sd_bus *bus, const char *path) = 0;
@@ -74,8 +79,6 @@ namespace sdbus { namespace internal {
virtual int sd_bus_flush(sd_bus *bus) = 0; virtual int sd_bus_flush(sd_bus *bus) = 0;
virtual sd_bus *sd_bus_flush_close_unref(sd_bus *bus) = 0; virtual sd_bus *sd_bus_flush_close_unref(sd_bus *bus) = 0;
virtual ~ISdBus() = default;
}; };
}} }}

View File

@@ -628,21 +628,21 @@ bool MethodCall::doesntExpectReply() const
return r == 0; return r == 0;
} }
MethodReply MethodCall::send() const MethodReply MethodCall::send(uint64_t timeout) const
{ {
if (!doesntExpectReply()) if (!doesntExpectReply())
return sendWithReply(); return sendWithReply(timeout);
else else
return sendWithNoReply(); return sendWithNoReply();
} }
MethodReply MethodCall::sendWithReply() const MethodReply MethodCall::sendWithReply(uint64_t timeout) const
{ {
sd_bus_error sdbusError = SD_BUS_ERROR_NULL; sd_bus_error sdbusError = SD_BUS_ERROR_NULL;
SCOPE_EXIT{ sd_bus_error_free(&sdbusError); }; SCOPE_EXIT{ sd_bus_error_free(&sdbusError); };
sd_bus_message* sdbusReply{}; sd_bus_message* sdbusReply{};
auto r = sdbus_->sd_bus_call(nullptr, (sd_bus_message*)msg_, 0, &sdbusError, &sdbusReply); auto r = sdbus_->sd_bus_call(nullptr, (sd_bus_message*)msg_, timeout, &sdbusError, &sdbusReply);
if (sd_bus_error_is_set(&sdbusError)) if (sd_bus_error_is_set(&sdbusError))
throw sdbus::Error(sdbusError.name, sdbusError.message); throw sdbus::Error(sdbusError.name, sdbusError.message);
@@ -687,11 +687,11 @@ AsyncMethodCall::AsyncMethodCall(MethodCall&& call) noexcept
{ {
} }
AsyncMethodCall::Slot AsyncMethodCall::send(void* callback, void* userData) const AsyncMethodCall::Slot AsyncMethodCall::send(void* callback, void* userData, uint64_t timeout) const
{ {
sd_bus_slot* slot; sd_bus_slot* slot;
auto r = sdbus_->sd_bus_call_async(nullptr, &slot, (sd_bus_message*)msg_, (sd_bus_message_handler_t)callback, userData, 0); auto r = sdbus_->sd_bus_call_async(nullptr, &slot, (sd_bus_message*)msg_, (sd_bus_message_handler_t)callback, userData, timeout);
SDBUS_THROW_ERROR_IF(r < 0, "Failed to call method asynchronously", -r); SDBUS_THROW_ERROR_IF(r < 0, "Failed to call method asynchronously", -r);
return Slot{slot, [sdbus_ = sdbus_](void *slot){ sdbus_->sd_bus_slot_unref((sd_bus_slot*)slot); }}; return Slot{slot, [sdbus_ = sdbus_](void *slot){ sdbus_->sd_bus_slot_unref((sd_bus_slot*)slot); }};

View File

@@ -69,17 +69,17 @@ AsyncMethodCall Proxy::createAsyncMethodCall(const std::string& interfaceName, c
return AsyncMethodCall{Proxy::createMethodCall(interfaceName, methodName)}; return AsyncMethodCall{Proxy::createMethodCall(interfaceName, methodName)};
} }
MethodReply Proxy::callMethod(const MethodCall& message) MethodReply Proxy::callMethod(const MethodCall& message, uint64_t timeout)
{ {
return message.send(); return message.send(timeout);
} }
void Proxy::callMethod(const AsyncMethodCall& message, async_reply_handler asyncReplyCallback) void Proxy::callMethod(const AsyncMethodCall& message, async_reply_handler asyncReplyCallback, uint64_t timeout)
{ {
auto callback = (void*)&Proxy::sdbus_async_reply_handler; auto callback = (void*)&Proxy::sdbus_async_reply_handler;
auto callData = std::make_unique<AsyncCalls::CallData>(AsyncCalls::CallData{*this, std::move(asyncReplyCallback), {}}); auto callData = std::make_unique<AsyncCalls::CallData>(AsyncCalls::CallData{*this, std::move(asyncReplyCallback), {}});
callData->slot = message.send(callback, callData.get()); callData->slot = message.send(callback, callData.get(), timeout);
pendingAsyncCalls_.addCall(callData->slot.get(), std::move(callData)); pendingAsyncCalls_.addCall(callData->slot.get(), std::move(callData));
} }

View File

@@ -52,8 +52,8 @@ namespace internal {
MethodCall createMethodCall(const std::string& interfaceName, const std::string& methodName) override; MethodCall createMethodCall(const std::string& interfaceName, const std::string& methodName) override;
AsyncMethodCall createAsyncMethodCall(const std::string& interfaceName, const std::string& methodName) override; AsyncMethodCall createAsyncMethodCall(const std::string& interfaceName, const std::string& methodName) override;
MethodReply callMethod(const MethodCall& message) override; MethodReply callMethod(const MethodCall& message, uint64_t timeout) override;
void callMethod(const AsyncMethodCall& message, async_reply_handler asyncReplyCallback) override; void callMethod(const AsyncMethodCall& message, async_reply_handler asyncReplyCallback, uint64_t timeout) override;
void registerSignalHandler( const std::string& interfaceName void registerSignalHandler( const std::string& interfaceName
, const std::string& signalName , const std::string& signalName

View File

@@ -26,6 +26,7 @@
*/ */
#include "SdBus.h" #include "SdBus.h"
#include <sdbus-c++/Error.h>
namespace sdbus { namespace internal { namespace sdbus { namespace internal {
@@ -92,6 +93,32 @@ int SdBus::sd_bus_message_new_method_error(sd_bus_message *call, sd_bus_message
return ::sd_bus_message_new_method_error(call, m, e); return ::sd_bus_message_new_method_error(call, m, e);
} }
int SdBus::sd_bus_set_method_call_timeout(sd_bus *bus, uint64_t usec)
{
#if LIBSYSTEMD_VERSION>=240
std::unique_lock<std::recursive_mutex> lock(sdbusMutex_);
return ::sd_bus_set_method_call_timeout(bus, usec);
#else
(void)bus;
(void)usec;
throw sdbus::Error(SD_BUS_ERROR_NOT_SUPPORTED, "Setting general method call timeout not supported by underlying version of libsystemd");
#endif
}
int SdBus::sd_bus_get_method_call_timeout(sd_bus *bus, uint64_t *ret)
{
#if LIBSYSTEMD_VERSION>=240
std::unique_lock<std::recursive_mutex> lock(sdbusMutex_);
return ::sd_bus_get_method_call_timeout(bus, ret);
#else
(void)bus;
(void)ret;
throw sdbus::Error(SD_BUS_ERROR_NOT_SUPPORTED, "Getting general method call timeout not supported by underlying version of libsystemd");
#endif
}
int SdBus::sd_bus_emit_properties_changed_strv(sd_bus *bus, const char *path, const char *interface, char **names) int SdBus::sd_bus_emit_properties_changed_strv(sd_bus *bus, const char *path, const char *interface, char **names)
{ {
std::unique_lock<std::recursive_mutex> lock(sdbusMutex_); std::unique_lock<std::recursive_mutex> lock(sdbusMutex_);

View File

@@ -48,6 +48,9 @@ public:
virtual int sd_bus_message_new_method_return(sd_bus_message *call, sd_bus_message **m) override; virtual int sd_bus_message_new_method_return(sd_bus_message *call, sd_bus_message **m) override;
virtual int sd_bus_message_new_method_error(sd_bus_message *call, sd_bus_message **m, const sd_bus_error *e) override; virtual int sd_bus_message_new_method_error(sd_bus_message *call, sd_bus_message **m, const sd_bus_error *e) override;
virtual int sd_bus_set_method_call_timeout(sd_bus *bus, uint64_t usec) override;
virtual int sd_bus_get_method_call_timeout(sd_bus *bus, uint64_t *ret) override;
virtual int sd_bus_emit_properties_changed_strv(sd_bus *bus, const char *path, const char *interface, char **names) override; virtual int sd_bus_emit_properties_changed_strv(sd_bus *bus, const char *path, const char *interface, char **names) override;
virtual int sd_bus_emit_object_added(sd_bus *bus, const char *path) override; virtual int sd_bus_emit_object_added(sd_bus *bus, const char *path) override;
virtual int sd_bus_emit_object_removed(sd_bus *bus, const char *path) override; virtual int sd_bus_emit_object_removed(sd_bus *bus, const char *path) override;

View File

@@ -85,12 +85,14 @@ include_directories(${CMAKE_CURRENT_SOURCE_DIR})
#---------------------------------- #----------------------------------
add_executable(sdbus-c++-unit-tests ${UNITTESTS_SRCS} $<TARGET_OBJECTS:sdbus-c++-objlib>) add_executable(sdbus-c++-unit-tests ${UNITTESTS_SRCS} $<TARGET_OBJECTS:sdbus-c++-objlib>)
target_compile_definitions(sdbus-c++-unit-tests PRIVATE LIBSYSTEMD_VERSION=${SYSTEMD_VERSION})
target_include_directories(sdbus-c++-unit-tests PRIVATE ${SYSTEMD_INCLUDE_DIRS} target_include_directories(sdbus-c++-unit-tests PRIVATE ${SYSTEMD_INCLUDE_DIRS}
${CMAKE_SOURCE_DIR}/src ${CMAKE_SOURCE_DIR}/src
${CMAKE_SOURCE_DIR}/include) ${CMAKE_SOURCE_DIR}/include)
target_link_libraries(sdbus-c++-unit-tests ${SYSTEMD_LIBRARIES} gmock gmock_main) target_link_libraries(sdbus-c++-unit-tests ${SYSTEMD_LIBRARIES} gmock gmock_main)
add_executable(sdbus-c++-integration-tests ${INTEGRATIONTESTS_SRCS}) add_executable(sdbus-c++-integration-tests ${INTEGRATIONTESTS_SRCS})
target_compile_definitions(sdbus-c++-integration-tests PRIVATE LIBSYSTEMD_VERSION=${SYSTEMD_VERSION})
target_link_libraries(sdbus-c++-integration-tests sdbus-c++ gmock gmock_main) target_link_libraries(sdbus-c++-integration-tests sdbus-c++ gmock gmock_main)
# Manual performance and stress tests # Manual performance and stress tests

View File

@@ -232,6 +232,30 @@ TEST_F(SdbusTestObject, CallsMultiplyMethodWithNoReplyFlag)
ASSERT_THAT(m_adaptor->m_multiplyResult, Eq(INT64_VALUE * DOUBLE_VALUE)); ASSERT_THAT(m_adaptor->m_multiplyResult, Eq(INT64_VALUE * DOUBLE_VALUE));
} }
TEST_F(SdbusTestObject, CallsMethodWithCustomTimeoutSuccessfully)
{
auto res = m_proxy->doOperationWith500msTimeout(20); // The operation will take 20ms, but the timeout is 500ms, so we are fine
ASSERT_THAT(res, Eq(20));
}
TEST_F(SdbusTestObject, ThrowsTimeoutErrorWhenMethodTimesOut)
{
try
{
m_proxy->doOperationWith500msTimeout(1000); // The operation will take 1s, but the timeout is 500ms, so we should time out
FAIL() << "Expected sdbus::Error exception";
}
catch (const sdbus::Error& e)
{
ASSERT_THAT(e.getName(), Eq("org.freedesktop.DBus.Error.Timeout"));
ASSERT_THAT(e.getMessage(), Eq("Connection timed out"));
}
catch(...)
{
FAIL() << "Expected sdbus::Error exception";
}
}
TEST_F(SdbusTestObject, CallsMethodThatThrowsError) TEST_F(SdbusTestObject, CallsMethodThatThrowsError)
{ {
try try
@@ -368,6 +392,20 @@ TEST_F(SdbusTestObject, FailsCallingMethodOnNonexistentObject)
ASSERT_THROW(proxy.getInt(), sdbus::Error); ASSERT_THROW(proxy.getInt(), sdbus::Error);
} }
#if LIBSYSTEMD_VERSION>=240
TEST_F(SdbusTestObject, CanSetGeneralMethodTimeoutWithLibsystemdVersionGreaterThan239)
{
s_connection->setMethodCallTimeout(5000000);
ASSERT_THAT(s_connection->getMethodCallTimeout(), Eq(5000000));
}
#else
TEST_F(SdbusTestObject, CannotSetGeneralMethodTimeoutWithLibsystemdVersionLessThan240)
{
ASSERT_THROW(s_connection->setMethodCallTimeout(5000000), sdbus::Error);
ASSERT_THROW(s_connection->getMethodCallTimeout(), sdbus::Error);
}
#endif
// Signals // Signals
TEST_F(SdbusTestObject, EmitsSimpleSignalSuccesfully) TEST_F(SdbusTestObject, EmitsSimpleSignalSuccesfully)

View File

@@ -122,6 +122,4 @@ public: // for tests
std::function<void(const sdbus::ObjectPath&, const std::vector<std::string>&)> m_onInterfacesRemovedHandler; std::function<void(const sdbus::ObjectPath&, const std::vector<std::string>&)> m_onInterfacesRemovedHandler;
}; };
#endif /* SDBUS_CPP_INTEGRATIONTESTS_TESTINGPROXY_H_ */ #endif /* SDBUS_CPP_INTEGRATIONTESTS_TESTINGPROXY_H_ */

View File

@@ -136,6 +136,14 @@ public:
return result; return result;
} }
uint32_t doOperationWith500msTimeout(uint32_t param)
{
using namespace std::chrono_literals;
uint32_t result;
object_.callMethod("doOperation").onInterface(INTERFACE_NAME).withTimeout(500000us).withArguments(param).storeResultsTo(result);
return result;
}
uint32_t doOperationAsync(uint32_t param) uint32_t doOperationAsync(uint32_t param)
{ {
uint32_t result; uint32_t result;

View File

@@ -47,6 +47,9 @@ public:
MOCK_METHOD2(sd_bus_message_new_method_return, int(sd_bus_message *call, sd_bus_message **m)); MOCK_METHOD2(sd_bus_message_new_method_return, int(sd_bus_message *call, sd_bus_message **m));
MOCK_METHOD3(sd_bus_message_new_method_error, int(sd_bus_message *call, sd_bus_message **m, const sd_bus_error *e)); MOCK_METHOD3(sd_bus_message_new_method_error, int(sd_bus_message *call, sd_bus_message **m, const sd_bus_error *e));
MOCK_METHOD2(sd_bus_set_method_call_timeout, int(sd_bus *bus, uint64_t usec));
MOCK_METHOD2(sd_bus_get_method_call_timeout, int(sd_bus *bus, uint64_t *ret));
MOCK_METHOD4(sd_bus_emit_properties_changed_strv, int(sd_bus *bus, const char *path, const char *interface, char **names)); MOCK_METHOD4(sd_bus_emit_properties_changed_strv, int(sd_bus *bus, const char *path, const char *interface, char **names));
MOCK_METHOD2(sd_bus_emit_object_added, int(sd_bus *bus, const char *path)); MOCK_METHOD2(sd_bus_emit_object_added, int(sd_bus *bus, const char *path));
MOCK_METHOD2(sd_bus_emit_object_removed, int(sd_bus *bus, const char *path)); MOCK_METHOD2(sd_bus_emit_object_removed, int(sd_bus *bus, const char *path));

View File

@@ -193,7 +193,7 @@ std::tuple<std::string, std::string> AdaptorGenerator::processMethods(const Node
if (annotationValue == "true") if (annotationValue == "true")
annotationRegistration += ".markAsPrivileged()"; annotationRegistration += ".markAsPrivileged()";
} }
else else if (annotationName != "org.freedesktop.DBus.Method.Timeout") // Whatever else...
{ {
std::cerr << "Node: " << methodName << ": " std::cerr << "Node: " << methodName << ": "
<< "Option '" << annotationName << "' not allowed or supported in this context! Option ignored..." << std::endl; << "Option '" << annotationName << "' not allowed or supported in this context! Option ignored..." << std::endl;

View File

@@ -34,7 +34,6 @@
#include <algorithm> #include <algorithm>
#include <iterator> #include <iterator>
using std::endl; using std::endl;
using sdbuscpp::xml::Document; using sdbuscpp::xml::Document;
@@ -142,6 +141,8 @@ std::tuple<std::string, std::string> ProxyGenerator::processMethods(const Nodes&
bool dontExpectReply{false}; bool dontExpectReply{false};
bool async{false}; bool async{false};
std::string timeoutValue;
Nodes annotations = (*method)["annotation"]; Nodes annotations = (*method)["annotation"];
for (const auto& annotation : annotations) for (const auto& annotation : annotations)
{ {
@@ -150,6 +151,8 @@ std::tuple<std::string, std::string> ProxyGenerator::processMethods(const Nodes&
else if (annotation->get("name") == "org.freedesktop.DBus.Method.Async" else if (annotation->get("name") == "org.freedesktop.DBus.Method.Async"
&& (annotation->get("value") == "client" || annotation->get("value") == "clientserver")) && (annotation->get("value") == "client" || annotation->get("value") == "clientserver"))
async = true; async = true;
if (annotation->get("name") == "org.freedesktop.DBus.Method.Timeout")
timeoutValue = annotation->get("value");
} }
if (dontExpectReply && outArgs.size() > 0) if (dontExpectReply && outArgs.size() > 0)
{ {
@@ -157,6 +160,12 @@ std::tuple<std::string, std::string> ProxyGenerator::processMethods(const Nodes&
std::cerr << "Option 'org.freedesktop.DBus.Method.NoReply' not allowed for methods with 'out' variables! Option ignored..." << std::endl; std::cerr << "Option 'org.freedesktop.DBus.Method.NoReply' not allowed for methods with 'out' variables! Option ignored..." << std::endl;
dontExpectReply = false; dontExpectReply = false;
} }
if (!timeoutValue.empty() && dontExpectReply)
{
std::cerr << "Function: " << name << ": ";
std::cerr << "Option 'org.freedesktop.DBus.Method.Timeout' not allowed for 'NoReply' methods! Option ignored..." << std::endl;
timeoutValue.clear();
}
auto retType = outArgsToType(outArgs); auto retType = outArgsToType(outArgs);
std::string inArgStr, inArgTypeStr; std::string inArgStr, inArgTypeStr;
@@ -167,6 +176,11 @@ std::tuple<std::string, std::string> ProxyGenerator::processMethods(const Nodes&
definitionSS << tab << (async ? "void" : retType) << " " << name << "(" << inArgTypeStr << ")" << endl definitionSS << tab << (async ? "void" : retType) << " " << name << "(" << inArgTypeStr << ")" << endl
<< tab << "{" << endl; << tab << "{" << endl;
if (!timeoutValue.empty())
{
definitionSS << tab << tab << "using namespace std::chrono_literals;" << endl;
}
if (outArgs.size() > 0 && !async) if (outArgs.size() > 0 && !async)
{ {
definitionSS << tab << tab << retType << " result;" << endl; definitionSS << tab << tab << retType << " result;" << endl;
@@ -175,6 +189,11 @@ std::tuple<std::string, std::string> ProxyGenerator::processMethods(const Nodes&
definitionSS << tab << tab << "proxy_.callMethod" << (async ? "Async" : "") << "(\"" << name << "\")" definitionSS << tab << tab << "proxy_.callMethod" << (async ? "Async" : "") << "(\"" << name << "\")"
".onInterface(INTERFACE_NAME)"; ".onInterface(INTERFACE_NAME)";
if (!timeoutValue.empty())
{
definitionSS << ".withTimeout(" << timeoutValue << "us)";
}
if (inArgs.size() > 0) if (inArgs.size() > 0)
{ {
definitionSS << ".withArguments(" << inArgStr << ")"; definitionSS << ".withArguments(" << inArgStr << ")";