forked from Kistler-Group/sdbus-cpp
fix timeout handling * Despite what is documented in sd_bus_get_timeout(3), the timeout returned is actually an absolute time point of Linux's CLOCK_MONOTONIC clock. Hence, we first have to subtract the current time from the timeout in order to get a relative time that can be passed to poll. * For async call timeouts to reliably work, we need a way to notify the event loop of a connection that is currently blocked waiting in poll. I.e. assume the event loop thread entered poll with a timeout set to T1. Afterwards, the main thread starts an async call C with a timeout T2 < T1. In order for C to be canceled after its timeout T1 has elapsed, we have to be able to notify the event loop so that it can update its poll data. Co-authored-by: Urs Ritzmann <ursritzmann@protonmail.ch> Co-authored-by: Lukasz Marcul <lukasz.marcul@onemeter.com>
This commit is contained in:
committed by
GitHub
parent
0b8f2d9752
commit
bb0f3f0242
@ -42,6 +42,7 @@
|
||||
using ::testing::Eq;
|
||||
using ::testing::DoubleEq;
|
||||
using ::testing::Gt;
|
||||
using ::testing::Le;
|
||||
using ::testing::AnyOf;
|
||||
using ::testing::ElementsAre;
|
||||
using ::testing::SizeIs;
|
||||
@ -56,6 +57,7 @@ using SdbusTestObject = TestFixture;
|
||||
|
||||
TEST_F(SdbusTestObject, ThrowsTimeoutErrorWhenClientSideAsyncMethodTimesOut)
|
||||
{
|
||||
std::chrono::time_point<std::chrono::steady_clock> start;
|
||||
try
|
||||
{
|
||||
std::promise<uint32_t> promise;
|
||||
@ -68,7 +70,8 @@ TEST_F(SdbusTestObject, ThrowsTimeoutErrorWhenClientSideAsyncMethodTimesOut)
|
||||
promise.set_exception(std::make_exception_ptr(*err));
|
||||
});
|
||||
|
||||
m_proxy->doOperationClientSideAsyncWith500msTimeout(1000); // The operation will take 1s, but the timeout is 500ms, so we should time out
|
||||
start = std::chrono::steady_clock::now();
|
||||
m_proxy->doOperationClientSideAsyncWithTimeout(1us, 1000); // The operation will take 1s, but the timeout is 500ms, so we should time out
|
||||
future.get();
|
||||
|
||||
FAIL() << "Expected sdbus::Error exception";
|
||||
@ -77,6 +80,8 @@ TEST_F(SdbusTestObject, ThrowsTimeoutErrorWhenClientSideAsyncMethodTimesOut)
|
||||
{
|
||||
ASSERT_THAT(e.getName(), AnyOf("org.freedesktop.DBus.Error.Timeout", "org.freedesktop.DBus.Error.NoReply"));
|
||||
ASSERT_THAT(e.getMessage(), AnyOf("Connection timed out", "Method call timed out"));
|
||||
auto measuredTimeout = std::chrono::steady_clock::now() - start;
|
||||
ASSERT_THAT(measuredTimeout, Le(50ms));
|
||||
}
|
||||
catch(...)
|
||||
{
|
||||
|
@ -37,9 +37,11 @@
|
||||
|
||||
// STL
|
||||
#include <thread>
|
||||
#include <chrono>
|
||||
|
||||
using ::testing::Eq;
|
||||
using namespace sdbus::test;
|
||||
using namespace std::chrono_literals;
|
||||
|
||||
/*-------------------------------------*/
|
||||
/* -- TEST CASES -- */
|
||||
@ -88,3 +90,44 @@ TEST(Connection, CanEnterAndLeaveEventLoop)
|
||||
|
||||
t.join();
|
||||
}
|
||||
|
||||
TEST(Connection, PollDataGetZeroTimeout)
|
||||
{
|
||||
sdbus::IConnection::PollData pd{};
|
||||
pd.timeout_usec = 0;
|
||||
ASSERT_TRUE(pd.getRelativeTimeout().has_value());
|
||||
EXPECT_THAT(pd.getRelativeTimeout().value(), Eq(std::chrono::microseconds::zero()));
|
||||
EXPECT_THAT(pd.getPollTimeout(), Eq(0));
|
||||
}
|
||||
|
||||
TEST(Connection, PollDataGetInfiniteTimeout)
|
||||
{
|
||||
sdbus::IConnection::PollData pd{};
|
||||
pd.timeout_usec = UINT64_MAX;
|
||||
ASSERT_FALSE(pd.getRelativeTimeout().has_value());
|
||||
EXPECT_THAT(pd.getPollTimeout(), Eq(-1));
|
||||
}
|
||||
|
||||
TEST(Connection, PollDataGetZeroRelativeTimeoutForPast)
|
||||
{
|
||||
sdbus::IConnection::PollData pd{};
|
||||
auto past = std::chrono::steady_clock::now() - 10s;
|
||||
pd.timeout_usec = std::chrono::duration_cast<std::chrono::microseconds>(past.time_since_epoch()).count();
|
||||
ASSERT_TRUE(pd.getRelativeTimeout().has_value());
|
||||
EXPECT_THAT(pd.getRelativeTimeout().value(), Eq(0us));
|
||||
EXPECT_THAT(pd.getPollTimeout(), Eq(0));
|
||||
}
|
||||
|
||||
TEST(Connection, PollDataGetRelativeTimeoutInTolerance)
|
||||
{
|
||||
sdbus::IConnection::PollData pd{};
|
||||
constexpr auto TIMEOUT = 1s;
|
||||
constexpr auto TOLERANCE = 100ms;
|
||||
auto future = std::chrono::steady_clock::now() + TIMEOUT;
|
||||
pd.timeout_usec = std::chrono::duration_cast<std::chrono::microseconds>(future.time_since_epoch()).count();
|
||||
ASSERT_TRUE(pd.getRelativeTimeout().has_value());
|
||||
EXPECT_GE(pd.getRelativeTimeout().value(), TIMEOUT - TOLERANCE);
|
||||
EXPECT_LE(pd.getRelativeTimeout().value(), TIMEOUT + TOLERANCE);
|
||||
EXPECT_GE(pd.getPollTimeout(), 900);
|
||||
EXPECT_LE(pd.getPollTimeout(), 1100);
|
||||
}
|
@ -42,6 +42,7 @@
|
||||
using ::testing::Eq;
|
||||
using ::testing::DoubleEq;
|
||||
using ::testing::Gt;
|
||||
using ::testing::Le;
|
||||
using ::testing::AnyOf;
|
||||
using ::testing::ElementsAre;
|
||||
using ::testing::SizeIs;
|
||||
@ -162,21 +163,24 @@ TEST_F(SdbusTestObject, CallsMultiplyMethodWithNoReplyFlag)
|
||||
|
||||
TEST_F(SdbusTestObject, CallsMethodWithCustomTimeoutSuccessfully)
|
||||
{
|
||||
auto res = m_proxy->doOperationWith500msTimeout(20); // The operation will take 20ms, but the timeout is 500ms, so we are fine
|
||||
auto res = m_proxy->doOperationWithTimeout(500ms, 20); // The operation will take 20ms, but the timeout is 500ms, so we are fine
|
||||
ASSERT_THAT(res, Eq(20));
|
||||
}
|
||||
|
||||
TEST_F(SdbusTestObject, ThrowsTimeoutErrorWhenMethodTimesOut)
|
||||
{
|
||||
auto start = std::chrono::steady_clock::now();
|
||||
try
|
||||
{
|
||||
m_proxy->doOperationWith500msTimeout(1000); // The operation will take 1s, but the timeout is 500ms, so we should time out
|
||||
m_proxy->doOperationWithTimeout(1us, 1000); // The operation will take 1s, but the timeout is 1us, so we should time out
|
||||
FAIL() << "Expected sdbus::Error exception";
|
||||
}
|
||||
catch (const sdbus::Error& e)
|
||||
{
|
||||
ASSERT_THAT(e.getName(), AnyOf("org.freedesktop.DBus.Error.Timeout", "org.freedesktop.DBus.Error.NoReply"));
|
||||
ASSERT_THAT(e.getMessage(), AnyOf("Connection timed out", "Method call timed out"));
|
||||
auto measuredTimeout = std::chrono::steady_clock::now() - start;
|
||||
ASSERT_THAT(measuredTimeout, Le(50ms));
|
||||
}
|
||||
catch(...)
|
||||
{
|
||||
|
@ -28,6 +28,8 @@
|
||||
#define SDBUS_CPP_INTEGRATIONTESTS_DEFS_H_
|
||||
|
||||
#include "sdbus-c++/Types.h"
|
||||
#include <chrono>
|
||||
#include <ostream>
|
||||
|
||||
namespace sdbus { namespace test {
|
||||
|
||||
@ -54,6 +56,20 @@ const bool DEFAULT_BLOCKING_VALUE{true};
|
||||
|
||||
constexpr const double DOUBLE_VALUE{3.24L};
|
||||
|
||||
/** Duration stream operator for human readable gtest value output.
|
||||
*
|
||||
* Note that the conversion to double is lossy if the input type has 64 or more bits.
|
||||
* This is ok for our integration tests because they don't have very
|
||||
* accurate timing requirements.
|
||||
*
|
||||
* @return human readable duration in seconds
|
||||
*/
|
||||
template< class Rep, class Period >
|
||||
static std::ostream& operator<<(std::ostream& os, const std::chrono::duration<Rep, Period>& d)
|
||||
{
|
||||
auto seconds = std::chrono::duration_cast<std::chrono::duration<double>>(d);
|
||||
return os << seconds.count() << " s";
|
||||
}
|
||||
}}
|
||||
|
||||
#endif /* SDBUS_CPP_INTEGRATIONTESTS_DEFS_H_ */
|
||||
|
@ -97,11 +97,11 @@ void TestProxy::installDoOperationClientSideAsyncReplyHandler(std::function<void
|
||||
m_DoOperationClientSideAsyncReplyHandler = std::move(handler);
|
||||
}
|
||||
|
||||
uint32_t TestProxy::doOperationWith500msTimeout(uint32_t param)
|
||||
uint32_t TestProxy::doOperationWithTimeout(const std::chrono::microseconds &timeout, uint32_t param)
|
||||
{
|
||||
using namespace std::chrono_literals;
|
||||
uint32_t result;
|
||||
getProxy().callMethod("doOperation").onInterface(sdbus::test::INTERFACE_NAME).withTimeout(500000us).withArguments(param).storeResultsTo(result);
|
||||
getProxy().callMethod("doOperation").onInterface(sdbus::test::INTERFACE_NAME).withTimeout(timeout).withArguments(param).storeResultsTo(result);
|
||||
return result;
|
||||
}
|
||||
|
||||
@ -126,12 +126,12 @@ void TestProxy::doErroneousOperationClientSideAsync()
|
||||
});
|
||||
}
|
||||
|
||||
void TestProxy::doOperationClientSideAsyncWith500msTimeout(uint32_t param)
|
||||
void TestProxy::doOperationClientSideAsyncWithTimeout(const std::chrono::microseconds &timeout, uint32_t param)
|
||||
{
|
||||
using namespace std::chrono_literals;
|
||||
getProxy().callMethodAsync("doOperation")
|
||||
.onInterface(sdbus::test::INTERFACE_NAME)
|
||||
.withTimeout(500000us)
|
||||
.withTimeout(timeout)
|
||||
.withArguments(param)
|
||||
.uponReplyInvoke([this](const sdbus::Error* error, uint32_t returnValue)
|
||||
{
|
||||
|
@ -91,10 +91,10 @@ protected:
|
||||
|
||||
public:
|
||||
void installDoOperationClientSideAsyncReplyHandler(std::function<void(uint32_t res, const sdbus::Error* err)> handler);
|
||||
uint32_t doOperationWith500msTimeout(uint32_t param);
|
||||
uint32_t doOperationWithTimeout(const std::chrono::microseconds &timeout, uint32_t param);
|
||||
sdbus::PendingAsyncCall doOperationClientSideAsync(uint32_t param);
|
||||
void doErroneousOperationClientSideAsync();
|
||||
void doOperationClientSideAsyncWith500msTimeout(uint32_t param);
|
||||
void doOperationClientSideAsyncWithTimeout(const std::chrono::microseconds &timeout, uint32_t param);
|
||||
int32_t callNonexistentMethod();
|
||||
int32_t callMethodOnNonexistentInterface();
|
||||
void setStateProperty(const std::string& value);
|
||||
|
Reference in New Issue
Block a user