[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:
Sandy Carter 2018-12-09 18:02:36 -08:00 committed by Rick Gibbed
parent 331bb0ea9a
commit c2de074d5c
4 changed files with 206 additions and 24 deletions

View File

@ -1,2 +1,4 @@
# Ignore HighResolutionTimer custom event
handle SIG34 nostop noprint
# Ignore PosixTimer custom event
handle SIG35 nostop noprint

View File

@ -552,8 +552,112 @@ TEST_CASE("Wait on Multiple Mutants", "Mutant") {
thread2.join();
}
TEST_CASE("Create and Trigger Timer", "Timer") {
// TODO(bwrsandman):
TEST_CASE("Wait on Timer", "Timer") {
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);
}

View File

@ -306,12 +306,12 @@ class Timer : public WaitHandle {
std::chrono::milliseconds period,
std::function<void()> opt_callback = nullptr) = 0;
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::function<void()> opt_callback = nullptr) {
SetRepeating(due_time,
std::chrono::duration_cast<std::chrono::milliseconds>(period),
std::move(opt_callback));
return SetRepeating(
due_time, std::chrono::duration_cast<std::chrono::milliseconds>(period),
std::move(opt_callback));
}
// Stops the timer before it can be set to the signaled state and cancels

View File

@ -37,7 +37,7 @@ inline timespec DurationToTimeSpec(
// This implementation uses the SIGRTMAX - SIGRTMIN to signal to a thread
// gdb tip, for SIG = SIGRTMIN + SignalType : handle SIG nostop
// 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) {
auto result = SIGRTMIN + static_cast<int>(num);
@ -351,6 +351,82 @@ class PosixCondition<Mutant> : public PosixConditionBase {
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
template <typename T>
class PosixThreadHandle : public T {
@ -371,7 +447,7 @@ class PosixThreadHandle : public T {
template <typename T>
class PosixConditionHandle : public T {
public:
explicit PosixConditionHandle(bool initial_owner);
explicit PosixConditionHandle(bool);
PosixConditionHandle(bool manual_reset, bool initial_state);
PosixConditionHandle(uint32_t initial_count, uint32_t maximum_count);
~PosixConditionHandle() override = default;
@ -394,9 +470,8 @@ PosixConditionHandle<Mutant>::PosixConditionHandle(bool initial_owner)
: handle_(initial_owner) {}
template <>
PosixConditionHandle<Timer>::PosixConditionHandle(bool manual_reset,
bool initial_state)
: handle_() {}
PosixConditionHandle<Timer>::PosixConditionHandle(bool manual_reset)
: handle_(manual_reset) {}
template <>
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);
}
// TODO(dougvj)
class PosixTimer : public PosixConditionHandle<Timer> {
public:
PosixTimer(bool manual_reset) : PosixConditionHandle(manual_reset, false) {
assert_always();
}
~PosixTimer() = default;
explicit PosixTimer(bool manual_reset) : PosixConditionHandle(manual_reset) {}
~PosixTimer() override = default;
bool SetOnce(std::chrono::nanoseconds due_time,
std::function<void()> opt_callback) override {
assert_always();
return false;
return handle_.Set(due_time, std::chrono::milliseconds::zero(),
std::move(opt_callback));
}
bool SetRepeating(std::chrono::nanoseconds due_time,
std::chrono::milliseconds period,
std::function<void()> opt_callback) override {
assert_always();
return false;
}
bool Cancel() override {
assert_always();
return false;
return handle_.Set(due_time, period, std::move(opt_callback));
}
bool Cancel() override { return handle_.Cancel(); }
};
std::unique_ptr<Timer> Timer::CreateManualResetTimer() {
install_signal_handler(SignalType::kTimer);
return std::make_unique<PosixTimer>(true);
}
std::unique_ptr<Timer> Timer::CreateSynchronizationTimer() {
install_signal_handler(SignalType::kTimer);
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);
callback();
} 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:
assert_always();
}