[threading linux] Implement Timer
Test Manual Reset and Synchronization timers single threaded. Test Cancelling timers. Test WaitMultiple. Ignore real-time event 35 in .gdbinit which is used to signal timer. Callbacks don't seem to be called so testing them is difficult.
This commit is contained in:
parent
331bb0ea9a
commit
c2de074d5c
2
.gdbinit
2
.gdbinit
|
@ -1,2 +1,4 @@
|
||||||
# Ignore HighResolutionTimer custom event
|
# Ignore HighResolutionTimer custom event
|
||||||
handle SIG34 nostop noprint
|
handle SIG34 nostop noprint
|
||||||
|
# Ignore PosixTimer custom event
|
||||||
|
handle SIG35 nostop noprint
|
||||||
|
|
|
@ -552,8 +552,112 @@ TEST_CASE("Wait on Multiple Mutants", "Mutant") {
|
||||||
thread2.join();
|
thread2.join();
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST_CASE("Create and Trigger Timer", "Timer") {
|
TEST_CASE("Wait on Timer", "Timer") {
|
||||||
// TODO(bwrsandman):
|
WaitResult result;
|
||||||
|
std::unique_ptr<Timer> timer;
|
||||||
|
|
||||||
|
// Test Manual Reset
|
||||||
|
timer = Timer::CreateManualResetTimer();
|
||||||
|
result = Wait(timer.get(), false, 1ms);
|
||||||
|
REQUIRE(result == WaitResult::kTimeout);
|
||||||
|
REQUIRE(timer->SetOnce(1ms)); // Signals it
|
||||||
|
result = Wait(timer.get(), false, 2ms);
|
||||||
|
REQUIRE(result == WaitResult::kSuccess);
|
||||||
|
result = Wait(timer.get(), false, 1ms);
|
||||||
|
REQUIRE(result == WaitResult::kSuccess); // Did not reset
|
||||||
|
|
||||||
|
// Test Synchronization
|
||||||
|
timer = Timer::CreateSynchronizationTimer();
|
||||||
|
result = Wait(timer.get(), false, 1ms);
|
||||||
|
REQUIRE(result == WaitResult::kTimeout);
|
||||||
|
REQUIRE(timer->SetOnce(1ms)); // Signals it
|
||||||
|
result = Wait(timer.get(), false, 2ms);
|
||||||
|
REQUIRE(result == WaitResult::kSuccess);
|
||||||
|
result = Wait(timer.get(), false, 1ms);
|
||||||
|
REQUIRE(result == WaitResult::kTimeout); // Did reset
|
||||||
|
|
||||||
|
// TODO(bwrsandman): This test unexpectedly fails under windows
|
||||||
|
// Test long due time
|
||||||
|
// timer = Timer::CreateSynchronizationTimer();
|
||||||
|
// REQUIRE(timer->SetOnce(10s));
|
||||||
|
// result = Wait(timer.get(), false, 10ms); // Still signals under windows
|
||||||
|
// REQUIRE(result == WaitResult::kTimeout);
|
||||||
|
|
||||||
|
// Test Repeating
|
||||||
|
REQUIRE(timer->SetRepeating(1ms, 10ms));
|
||||||
|
for (int i = 0; i < 10; ++i) {
|
||||||
|
result = Wait(timer.get(), false, 20ms);
|
||||||
|
INFO(i);
|
||||||
|
REQUIRE(result == WaitResult::kSuccess);
|
||||||
|
}
|
||||||
|
MaybeYield();
|
||||||
|
Sleep(10ms); // Skip a few events
|
||||||
|
for (int i = 0; i < 10; ++i) {
|
||||||
|
result = Wait(timer.get(), false, 20ms);
|
||||||
|
REQUIRE(result == WaitResult::kSuccess);
|
||||||
|
}
|
||||||
|
// Cancel it
|
||||||
|
timer->Cancel();
|
||||||
|
result = Wait(timer.get(), false, 20ms);
|
||||||
|
REQUIRE(result == WaitResult::kTimeout);
|
||||||
|
MaybeYield();
|
||||||
|
Sleep(10ms); // Skip a few events
|
||||||
|
result = Wait(timer.get(), false, 20ms);
|
||||||
|
REQUIRE(result == WaitResult::kTimeout);
|
||||||
|
// Cancel with SetOnce
|
||||||
|
REQUIRE(timer->SetRepeating(1ms, 10ms));
|
||||||
|
for (int i = 0; i < 10; ++i) {
|
||||||
|
result = Wait(timer.get(), false, 20ms);
|
||||||
|
REQUIRE(result == WaitResult::kSuccess);
|
||||||
|
}
|
||||||
|
REQUIRE(timer->SetOnce(1ms));
|
||||||
|
result = Wait(timer.get(), false, 20ms);
|
||||||
|
REQUIRE(result == WaitResult::kSuccess); // Signal from Set Once
|
||||||
|
result = Wait(timer.get(), false, 20ms);
|
||||||
|
REQUIRE(result == WaitResult::kTimeout); // No more signals from repeating
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_CASE("Wait on Multiple Timers", "Timer") {
|
||||||
|
WaitResult all_result;
|
||||||
|
std::pair<WaitResult, size_t> any_result;
|
||||||
|
|
||||||
|
auto timer0 = Timer::CreateSynchronizationTimer();
|
||||||
|
auto timer1 = Timer::CreateManualResetTimer();
|
||||||
|
|
||||||
|
// None signaled
|
||||||
|
all_result = WaitAll({timer0.get(), timer1.get()}, false, 1ms);
|
||||||
|
REQUIRE(all_result == WaitResult::kTimeout);
|
||||||
|
any_result = WaitAny({timer0.get(), timer1.get()}, false, 1ms);
|
||||||
|
REQUIRE(any_result.first == WaitResult::kTimeout);
|
||||||
|
REQUIRE(any_result.second == 0);
|
||||||
|
|
||||||
|
// Some signaled
|
||||||
|
REQUIRE(timer1->SetOnce(1ms));
|
||||||
|
all_result = WaitAll({timer0.get(), timer1.get()}, false, 100ms);
|
||||||
|
REQUIRE(all_result == WaitResult::kTimeout);
|
||||||
|
any_result = WaitAny({timer0.get(), timer1.get()}, false, 100ms);
|
||||||
|
REQUIRE(any_result.first == WaitResult::kSuccess);
|
||||||
|
REQUIRE(any_result.second == 1);
|
||||||
|
|
||||||
|
// All signaled
|
||||||
|
REQUIRE(timer0->SetOnce(1ms));
|
||||||
|
all_result = WaitAll({timer0.get(), timer1.get()}, false, 100ms);
|
||||||
|
REQUIRE(all_result == WaitResult::kSuccess);
|
||||||
|
REQUIRE(timer0->SetOnce(1ms));
|
||||||
|
Sleep(1ms);
|
||||||
|
any_result = WaitAny({timer0.get(), timer1.get()}, false, 100ms);
|
||||||
|
REQUIRE(any_result.first == WaitResult::kSuccess);
|
||||||
|
REQUIRE(any_result.second == 0);
|
||||||
|
|
||||||
|
// Check that timer0 reset
|
||||||
|
any_result = WaitAny({timer0.get(), timer1.get()}, false, 100ms);
|
||||||
|
REQUIRE(any_result.first == WaitResult::kSuccess);
|
||||||
|
REQUIRE(any_result.second == 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_CASE("Create and Trigger Timer Callbacks", "Timer") {
|
||||||
|
// TODO(bwrsandman): Check which thread performs callback and timing of
|
||||||
|
// callback
|
||||||
REQUIRE(true);
|
REQUIRE(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -306,12 +306,12 @@ class Timer : public WaitHandle {
|
||||||
std::chrono::milliseconds period,
|
std::chrono::milliseconds period,
|
||||||
std::function<void()> opt_callback = nullptr) = 0;
|
std::function<void()> opt_callback = nullptr) = 0;
|
||||||
template <typename Rep, typename Period>
|
template <typename Rep, typename Period>
|
||||||
void SetRepeating(std::chrono::nanoseconds due_time,
|
bool SetRepeating(std::chrono::nanoseconds due_time,
|
||||||
std::chrono::duration<Rep, Period> period,
|
std::chrono::duration<Rep, Period> period,
|
||||||
std::function<void()> opt_callback = nullptr) {
|
std::function<void()> opt_callback = nullptr) {
|
||||||
SetRepeating(due_time,
|
return SetRepeating(
|
||||||
std::chrono::duration_cast<std::chrono::milliseconds>(period),
|
due_time, std::chrono::duration_cast<std::chrono::milliseconds>(period),
|
||||||
std::move(opt_callback));
|
std::move(opt_callback));
|
||||||
}
|
}
|
||||||
|
|
||||||
// Stops the timer before it can be set to the signaled state and cancels
|
// Stops the timer before it can be set to the signaled state and cancels
|
||||||
|
|
|
@ -37,7 +37,7 @@ inline timespec DurationToTimeSpec(
|
||||||
// This implementation uses the SIGRTMAX - SIGRTMIN to signal to a thread
|
// This implementation uses the SIGRTMAX - SIGRTMIN to signal to a thread
|
||||||
// gdb tip, for SIG = SIGRTMIN + SignalType : handle SIG nostop
|
// gdb tip, for SIG = SIGRTMIN + SignalType : handle SIG nostop
|
||||||
// lldb tip, for SIG = SIGRTMIN + SignalType : process handle SIG -s false
|
// lldb tip, for SIG = SIGRTMIN + SignalType : process handle SIG -s false
|
||||||
enum class SignalType { kHighResolutionTimer, k_Count };
|
enum class SignalType { kHighResolutionTimer, kTimer, k_Count };
|
||||||
|
|
||||||
int GetSystemSignal(SignalType num) {
|
int GetSystemSignal(SignalType num) {
|
||||||
auto result = SIGRTMIN + static_cast<int>(num);
|
auto result = SIGRTMIN + static_cast<int>(num);
|
||||||
|
@ -351,6 +351,82 @@ class PosixCondition<Mutant> : public PosixConditionBase {
|
||||||
std::thread::id owner_;
|
std::thread::id owner_;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
template <>
|
||||||
|
class PosixCondition<Timer> : public PosixConditionBase {
|
||||||
|
public:
|
||||||
|
explicit PosixCondition(bool manual_reset)
|
||||||
|
: callback_(),
|
||||||
|
timer_(nullptr),
|
||||||
|
signal_(false),
|
||||||
|
manual_reset_(manual_reset) {}
|
||||||
|
|
||||||
|
virtual ~PosixCondition() { Cancel(); }
|
||||||
|
|
||||||
|
// TODO(bwrsandman): due_times of under 1ms deadlock under travis
|
||||||
|
bool Set(std::chrono::nanoseconds due_time, std::chrono::milliseconds period,
|
||||||
|
std::function<void()> opt_callback = nullptr) {
|
||||||
|
std::lock_guard<std::mutex> lock(mutex_);
|
||||||
|
|
||||||
|
callback_ = std::move(opt_callback);
|
||||||
|
signal_ = false;
|
||||||
|
|
||||||
|
// Create timer
|
||||||
|
if (timer_ == nullptr) {
|
||||||
|
sigevent sev{};
|
||||||
|
sev.sigev_notify = SIGEV_SIGNAL;
|
||||||
|
sev.sigev_signo = GetSystemSignal(SignalType::kTimer);
|
||||||
|
sev.sigev_value.sival_ptr = this;
|
||||||
|
if (timer_create(CLOCK_REALTIME, &sev, &timer_) == -1) return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Start timer
|
||||||
|
itimerspec its{};
|
||||||
|
its.it_value = DurationToTimeSpec(due_time);
|
||||||
|
its.it_interval = DurationToTimeSpec(period);
|
||||||
|
return timer_settime(timer_, 0, &its, nullptr) == 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
void CompletionRoutine() {
|
||||||
|
// As the callback may reset the timer, store local.
|
||||||
|
std::function<void()> callback;
|
||||||
|
{
|
||||||
|
std::lock_guard<std::mutex> lock(mutex_);
|
||||||
|
// Store callback
|
||||||
|
if (callback_) callback = callback_;
|
||||||
|
signal_ = true;
|
||||||
|
if (manual_reset_) {
|
||||||
|
cond_.notify_all();
|
||||||
|
} else {
|
||||||
|
cond_.notify_one();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Call callback
|
||||||
|
if (callback) callback();
|
||||||
|
}
|
||||||
|
|
||||||
|
bool Cancel() {
|
||||||
|
std::lock_guard<std::mutex> lock(mutex_);
|
||||||
|
bool result = true;
|
||||||
|
if (timer_) {
|
||||||
|
result = timer_delete(timer_) == 0;
|
||||||
|
timer_ = nullptr;
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
inline bool signaled() const override { return signal_; }
|
||||||
|
inline void post_execution() override {
|
||||||
|
if (!manual_reset_) {
|
||||||
|
signal_ = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
std::function<void()> callback_;
|
||||||
|
timer_t timer_;
|
||||||
|
volatile bool signal_;
|
||||||
|
const bool manual_reset_;
|
||||||
|
};
|
||||||
|
|
||||||
// Native posix thread handle
|
// Native posix thread handle
|
||||||
template <typename T>
|
template <typename T>
|
||||||
class PosixThreadHandle : public T {
|
class PosixThreadHandle : public T {
|
||||||
|
@ -371,7 +447,7 @@ class PosixThreadHandle : public T {
|
||||||
template <typename T>
|
template <typename T>
|
||||||
class PosixConditionHandle : public T {
|
class PosixConditionHandle : public T {
|
||||||
public:
|
public:
|
||||||
explicit PosixConditionHandle(bool initial_owner);
|
explicit PosixConditionHandle(bool);
|
||||||
PosixConditionHandle(bool manual_reset, bool initial_state);
|
PosixConditionHandle(bool manual_reset, bool initial_state);
|
||||||
PosixConditionHandle(uint32_t initial_count, uint32_t maximum_count);
|
PosixConditionHandle(uint32_t initial_count, uint32_t maximum_count);
|
||||||
~PosixConditionHandle() override = default;
|
~PosixConditionHandle() override = default;
|
||||||
|
@ -394,9 +470,8 @@ PosixConditionHandle<Mutant>::PosixConditionHandle(bool initial_owner)
|
||||||
: handle_(initial_owner) {}
|
: handle_(initial_owner) {}
|
||||||
|
|
||||||
template <>
|
template <>
|
||||||
PosixConditionHandle<Timer>::PosixConditionHandle(bool manual_reset,
|
PosixConditionHandle<Timer>::PosixConditionHandle(bool manual_reset)
|
||||||
bool initial_state)
|
: handle_(manual_reset) {}
|
||||||
: handle_() {}
|
|
||||||
|
|
||||||
template <>
|
template <>
|
||||||
PosixConditionHandle<Event>::PosixConditionHandle(bool manual_reset,
|
PosixConditionHandle<Event>::PosixConditionHandle(bool manual_reset,
|
||||||
|
@ -488,35 +563,30 @@ std::unique_ptr<Mutant> Mutant::Create(bool initial_owner) {
|
||||||
return std::make_unique<PosixMutant>(initial_owner);
|
return std::make_unique<PosixMutant>(initial_owner);
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO(dougvj)
|
|
||||||
class PosixTimer : public PosixConditionHandle<Timer> {
|
class PosixTimer : public PosixConditionHandle<Timer> {
|
||||||
public:
|
public:
|
||||||
PosixTimer(bool manual_reset) : PosixConditionHandle(manual_reset, false) {
|
explicit PosixTimer(bool manual_reset) : PosixConditionHandle(manual_reset) {}
|
||||||
assert_always();
|
~PosixTimer() override = default;
|
||||||
}
|
|
||||||
~PosixTimer() = default;
|
|
||||||
bool SetOnce(std::chrono::nanoseconds due_time,
|
bool SetOnce(std::chrono::nanoseconds due_time,
|
||||||
std::function<void()> opt_callback) override {
|
std::function<void()> opt_callback) override {
|
||||||
assert_always();
|
return handle_.Set(due_time, std::chrono::milliseconds::zero(),
|
||||||
return false;
|
std::move(opt_callback));
|
||||||
}
|
}
|
||||||
bool SetRepeating(std::chrono::nanoseconds due_time,
|
bool SetRepeating(std::chrono::nanoseconds due_time,
|
||||||
std::chrono::milliseconds period,
|
std::chrono::milliseconds period,
|
||||||
std::function<void()> opt_callback) override {
|
std::function<void()> opt_callback) override {
|
||||||
assert_always();
|
return handle_.Set(due_time, period, std::move(opt_callback));
|
||||||
return false;
|
|
||||||
}
|
|
||||||
bool Cancel() override {
|
|
||||||
assert_always();
|
|
||||||
return false;
|
|
||||||
}
|
}
|
||||||
|
bool Cancel() override { return handle_.Cancel(); }
|
||||||
};
|
};
|
||||||
|
|
||||||
std::unique_ptr<Timer> Timer::CreateManualResetTimer() {
|
std::unique_ptr<Timer> Timer::CreateManualResetTimer() {
|
||||||
|
install_signal_handler(SignalType::kTimer);
|
||||||
return std::make_unique<PosixTimer>(true);
|
return std::make_unique<PosixTimer>(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
std::unique_ptr<Timer> Timer::CreateSynchronizationTimer() {
|
std::unique_ptr<Timer> Timer::CreateSynchronizationTimer() {
|
||||||
|
install_signal_handler(SignalType::kTimer);
|
||||||
return std::make_unique<PosixTimer>(false);
|
return std::make_unique<PosixTimer>(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -628,6 +698,12 @@ static void signal_handler(int signal, siginfo_t* info, void* /*context*/) {
|
||||||
*static_cast<std::function<void()>*>(info->si_value.sival_ptr);
|
*static_cast<std::function<void()>*>(info->si_value.sival_ptr);
|
||||||
callback();
|
callback();
|
||||||
} break;
|
} break;
|
||||||
|
case SignalType::kTimer: {
|
||||||
|
assert_not_null(info->si_value.sival_ptr);
|
||||||
|
auto pTimer =
|
||||||
|
static_cast<PosixCondition<Timer>*>(info->si_value.sival_ptr);
|
||||||
|
pTimer->CompletionRoutine();
|
||||||
|
} break;
|
||||||
default:
|
default:
|
||||||
assert_always();
|
assert_always();
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue