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
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>
$<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/src>
$<BUILD_INTERFACE:${SYSTEMD_INCLUDE_DIRS}>)

View File

@ -50,6 +50,8 @@ set(SYSTEMD_INCLUDE_DIRS ${SOURCE_DIR}/src)
ExternalProject_Get_property(LibsystemdBuildProject BINARY_DIR)
set(SYSTEMD_LIBRARY_DIRS ${BINARY_DIR})
set(SYSTEMD_VERSION ${LIBSYSTEMD_VERSION})
add_library(Systemd::Libsystemd STATIC IMPORTED)
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})

View File

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

View File

@ -40,6 +40,10 @@
namespace sdbus {
/*** ----------------- ***/
/*** MethodRegistrator ***/
/*** ----------------- ***/
// Moved into the library to isolate from C++17 dependency
/*
inline MethodRegistrator::MethodRegistrator(IObject& object, const std::string& methodName)
@ -149,6 +153,9 @@ namespace sdbus {
return *this;
}
/*** ----------------- ***/
/*** SignalRegistrator ***/
/*** ----------------- ***/
// Moved into the library to isolate from C++17 dependency
/*
@ -203,6 +210,9 @@ namespace sdbus {
return *this;
}
/*** ------------------- ***/
/*** PropertyRegistrator ***/
/*** ------------------- ***/
// Moved into the library to isolate from C++17 dependency
/*
@ -309,6 +319,9 @@ namespace sdbus {
return *this;
}
/*** -------------------- ***/
/*** InterfaceFlagsSetter ***/
/*** -------------------- ***/
// Moved into the library to isolate from C++17 dependency
/*
@ -369,6 +382,9 @@ namespace sdbus {
return *this;
}
/*** ------------- ***/
/*** SignalEmitter ***/
/*** ------------- ***/
// Moved into the library to isolate from C++17 dependency
/*
@ -416,6 +432,9 @@ namespace sdbus {
detail::serialize_pack(signal_, std::forward<_Args>(args)...);
}
/*** ------------- ***/
/*** MethodInvoker ***/
/*** ------------- ***/
// Moved into the library to isolate from C++17 dependency
/*
@ -456,6 +475,20 @@ namespace sdbus {
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>
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);
auto reply = proxy_.callMethod(method_);
auto reply = proxy_.callMethod(method_, timeout_);
methodCalled_ = true;
detail::deserialize_pack(reply, args...);
@ -484,6 +517,9 @@ namespace sdbus {
method_.dontExpectReply();
}
/*** ------------------ ***/
/*** AsyncMethodInvoker ***/
/*** ------------------ ***/
inline AsyncMethodInvoker::AsyncMethodInvoker(IProxy& proxy, const std::string& methodName)
: proxy_(proxy)
@ -498,6 +534,20 @@ namespace sdbus {
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>
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);
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
// 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.
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)
: proxy_(proxy)
@ -563,6 +618,9 @@ namespace sdbus {
});
}
/*** -------------- ***/
/*** PropertyGetter ***/
/*** -------------- ***/
inline PropertyGetter::PropertyGetter(IProxy& proxy, const std::string& propertyName)
: proxy_(proxy)
@ -581,6 +639,9 @@ namespace sdbus {
return var;
}
/*** -------------- ***/
/*** PropertySetter ***/
/*** -------------- ***/
inline PropertySetter::PropertySetter(IProxy& proxy, const std::string& propertyName)
: proxy_(proxy)

View File

@ -27,9 +27,10 @@
#ifndef SDBUS_CXX_ICONNECTION_H_
#define SDBUS_CXX_ICONNECTION_H_
//#include <cstdint>
#include <string>
#include <memory>
#include <chrono>
#include <cstdint>
namespace sdbus {
@ -145,8 +146,46 @@ namespace sdbus {
* @throws sdbus::Error in case of failure
*/
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
*

View File

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

View File

@ -31,6 +31,7 @@
#include <string>
#include <memory>
#include <functional>
#include <chrono>
// Forward declarations
namespace sdbus {
@ -95,6 +96,7 @@ namespace sdbus {
* @brief Calls method on the proxied D-Bus object
*
* @param[in] message Message representing a method call
* @param[in] timeout Timeout for dbus call in microseconds
* @return A method reply message
*
* 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
*/
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
*
* @param[in] message Message representing an async method call
* @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 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
*/
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
@ -264,6 +279,22 @@ namespace sdbus {
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)
{
return MethodInvoker(*this, methodName);

View File

@ -172,14 +172,14 @@ namespace sdbus {
public:
MethodCall() = default;
MethodReply send() const;
MethodReply send(uint64_t timeout = 0) const;
MethodReply createReply() const;
MethodReply createErrorReply(const sdbus::Error& error) const;
void dontExpectReply();
bool doesntExpectReply() const;
private:
MethodReply sendWithReply() const;
MethodReply sendWithReply(uint64_t timeout) const;
MethodReply sendWithNoReply() const;
};
@ -193,7 +193,7 @@ namespace sdbus {
AsyncMethodCall() = default;
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

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); }};
}
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
, const std::string& interfaceName
, const sd_bus_vtable* vtable

View File

@ -63,6 +63,9 @@ namespace sdbus { namespace internal {
void addObjectManager(const std::string& objectPath) 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;
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.
// 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.
proxy_.callMethod(method_);
proxy_.callMethod(method_, timeout_);
}
}

View File

@ -42,6 +42,8 @@ namespace sdbus { namespace internal {
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_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_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_object_added(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 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;
}
MethodReply MethodCall::send() const
MethodReply MethodCall::send(uint64_t timeout) const
{
if (!doesntExpectReply())
return sendWithReply();
return sendWithReply(timeout);
else
return sendWithNoReply();
}
MethodReply MethodCall::sendWithReply() const
MethodReply MethodCall::sendWithReply(uint64_t timeout) const
{
sd_bus_error sdbusError = SD_BUS_ERROR_NULL;
SCOPE_EXIT{ sd_bus_error_free(&sdbusError); };
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))
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;
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);
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)};
}
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 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));
}

View File

@ -52,8 +52,8 @@ namespace internal {
MethodCall createMethodCall(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;
void callMethod(const AsyncMethodCall& message, async_reply_handler asyncReplyCallback) override;
MethodReply callMethod(const MethodCall& message, uint64_t timeout) override;
void callMethod(const AsyncMethodCall& message, async_reply_handler asyncReplyCallback, uint64_t timeout) override;
void registerSignalHandler( const std::string& interfaceName
, const std::string& signalName

View File

@ -26,6 +26,7 @@
*/
#include "SdBus.h"
#include <sdbus-c++/Error.h>
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);
}
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)
{
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_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_object_added(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>)
target_compile_definitions(sdbus-c++-unit-tests PRIVATE LIBSYSTEMD_VERSION=${SYSTEMD_VERSION})
target_include_directories(sdbus-c++-unit-tests PRIVATE ${SYSTEMD_INCLUDE_DIRS}
${CMAKE_SOURCE_DIR}/src
${CMAKE_SOURCE_DIR}/include)
target_link_libraries(sdbus-c++-unit-tests ${SYSTEMD_LIBRARIES} gmock gmock_main)
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)
# 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));
}
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)
{
try
@ -368,6 +392,20 @@ TEST_F(SdbusTestObject, FailsCallingMethodOnNonexistentObject)
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
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;
};
#endif /* SDBUS_CPP_INTEGRATIONTESTS_TESTINGPROXY_H_ */

View File

@ -136,6 +136,14 @@ public:
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 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_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_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));

View File

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

View File

@ -34,7 +34,6 @@
#include <algorithm>
#include <iterator>
using std::endl;
using sdbuscpp::xml::Document;
@ -142,6 +141,8 @@ std::tuple<std::string, std::string> ProxyGenerator::processMethods(const Nodes&
bool dontExpectReply{false};
bool async{false};
std::string timeoutValue;
Nodes annotations = (*method)["annotation"];
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"
&& (annotation->get("value") == "client" || annotation->get("value") == "clientserver"))
async = true;
if (annotation->get("name") == "org.freedesktop.DBus.Method.Timeout")
timeoutValue = annotation->get("value");
}
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;
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);
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
<< tab << "{" << endl;
if (!timeoutValue.empty())
{
definitionSS << tab << tab << "using namespace std::chrono_literals;" << endl;
}
if (outArgs.size() > 0 && !async)
{
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 << "\")"
".onInterface(INTERFACE_NAME)";
if (!timeoutValue.empty())
{
definitionSS << ".withTimeout(" << timeoutValue << "us)";
}
if (inArgs.size() > 0)
{
definitionSS << ".withArguments(" << inArgStr << ")";