[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
|
||||
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();
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue