diff --git a/src/cli/CallAfterTimeout.cpp b/src/cli/CallAfterTimeout.cpp new file mode 100644 index 00000000..bd5ef1d5 --- /dev/null +++ b/src/cli/CallAfterTimeout.cpp @@ -0,0 +1 @@ +#include "CallAfterTimeout.h" diff --git a/src/cli/CallAfterTimeout.h b/src/cli/CallAfterTimeout.h new file mode 100644 index 00000000..30a70a70 --- /dev/null +++ b/src/cli/CallAfterTimeout.h @@ -0,0 +1,57 @@ +#pragma once +#ifndef MESSMER_CRYFS_SRC_CLI_CALLAFTERTIMEOUT_H +#define MESSMER_CRYFS_SRC_CLI_CALLAFTERTIMEOUT_H + +#include +#include +#include + +namespace cryfs { + class CallAfterTimeout final { + public: + CallAfterTimeout(boost::chrono::milliseconds timeout, std::function callback); + void resetTimer(); + private: + bool _checkTimeoutThreadIteration(); + boost::chrono::time_point _targetTime(); + bool _callCallbackIfTimeout(); + + std::function _callback; + boost::chrono::milliseconds _timeout; + boost::chrono::time_point _start; + cpputils::LoopThread _checkTimeoutThread; + std::mutex _mutex; + }; + + inline CallAfterTimeout::CallAfterTimeout(boost::chrono::milliseconds timeout, std::function callback) + :_callback(callback), _timeout(timeout), _start(), _checkTimeoutThread(std::bind(&CallAfterTimeout::_checkTimeoutThreadIteration, this)) { + resetTimer(); + _checkTimeoutThread.start(); + } + + inline void CallAfterTimeout::resetTimer() { + std::unique_lock lock(_mutex); + _start = boost::chrono::steady_clock::now(); + } + + inline bool CallAfterTimeout::_checkTimeoutThreadIteration() { + boost::this_thread::sleep_until(_targetTime()); + return _callCallbackIfTimeout(); + } + + inline boost::chrono::time_point CallAfterTimeout::_targetTime() { + std::unique_lock lock(_mutex); + return _start + _timeout; + } + + inline bool CallAfterTimeout::_callCallbackIfTimeout() { + std::unique_lock lock(_mutex); + if (boost::chrono::steady_clock::now() >= _start + _timeout) { + _callback(); + return false; // Stop thread + } + return true; // Continue thread + } +} + +#endif diff --git a/test/cli/CallAfterTimeoutTest.cpp b/test/cli/CallAfterTimeoutTest.cpp new file mode 100644 index 00000000..f6eb8b4f --- /dev/null +++ b/test/cli/CallAfterTimeoutTest.cpp @@ -0,0 +1,67 @@ +#include +#include +#include "../../src/cli/CallAfterTimeout.h" + +using cpputils::unique_ref; +using cpputils::make_unique_ref; +using boost::chrono::milliseconds; +using boost::chrono::minutes; +using boost::chrono::duration_cast; +using boost::this_thread::sleep_for; +using namespace cryfs; + +class CallAfterTimeoutTest : public ::testing::Test { +public: + unique_ref callAfterTimeout(milliseconds timeout) { + return make_unique_ref(timeout, [this] {called = true;}); + } + + bool called = false; +}; + +TEST_F(CallAfterTimeoutTest, NoReset_1) { + auto obj = callAfterTimeout(milliseconds(50)); + sleep_for(milliseconds(40)); + EXPECT_FALSE(called); + sleep_for(milliseconds(20)); + EXPECT_TRUE(called); +} + +TEST_F(CallAfterTimeoutTest, NoReset_2) { + auto obj = callAfterTimeout(milliseconds(100)); + sleep_for(milliseconds(90)); + EXPECT_FALSE(called); + sleep_for(milliseconds(20)); + EXPECT_TRUE(called); +} + +TEST_F(CallAfterTimeoutTest, DoesntCallTwice) { + auto obj = callAfterTimeout(milliseconds(50)); + sleep_for(milliseconds(60)); + EXPECT_TRUE(called); + called = false; + sleep_for(milliseconds(60)); + EXPECT_FALSE(called); +} + +TEST_F(CallAfterTimeoutTest, OneReset) { + auto obj = callAfterTimeout(milliseconds(50)); + sleep_for(milliseconds(40)); + obj->resetTimer(); + sleep_for(milliseconds(40)); + EXPECT_FALSE(called); + sleep_for(milliseconds(20)); + EXPECT_TRUE(called); +} + +TEST_F(CallAfterTimeoutTest, TwoResets) { + auto obj = callAfterTimeout(milliseconds(50)); + sleep_for(milliseconds(20)); + obj->resetTimer(); + sleep_for(milliseconds(40)); + obj->resetTimer(); + sleep_for(milliseconds(40)); + EXPECT_FALSE(called); + sleep_for(milliseconds(20)); + EXPECT_TRUE(called); +}