[threading] Implement Posix HighResolutionTimer

Implement HighResolutionTimer for Posix by using native timers.
Callbacks are triggered with realtime interrupts if they are supported.
Create an enum to track user-defined interrupts as well as an initializer and
handler to register these interrupts per thread.
Add test cases for timers for both single and multiple.
Fix Sleep function to continue sleeping if interrupted by system.
Add local .gdbinit to ignore signal 34 which is used by high res timer
This commit is contained in:
Sandy Carter 2018-12-03 22:20:56 -08:00 committed by Rick Gibbed
parent d8d8a7dbb8
commit b5ea686475
3 changed files with 129 additions and 11 deletions

2
.gdbinit Normal file
View File

@ -0,0 +1,2 @@
# Ignore HighResolutionTimer custom event
handle SIG34 nostop noprint

View File

@ -62,8 +62,58 @@ TEST_CASE("TlsHandle") {
}
TEST_CASE("HighResolutionTimer") {
// TODO(bwrsandman):
REQUIRE(true);
// The wait time is 500ms with an interval of 50ms
// Smaller values are not as precise and fail the test
const auto wait_time = 500ms;
// Time the actual sleep duration
{
const auto interval = 50ms;
std::atomic<uint64_t> counter;
auto start = std::chrono::steady_clock::now();
auto cb = [&counter] { ++counter; };
auto pTimer = HighResolutionTimer::CreateRepeating(interval, cb);
Sleep(wait_time);
pTimer.reset();
auto duration = std::chrono::steady_clock::now() - start;
// Should have run as many times as wait_time / timer_interval plus or
// minus 1 due to imprecision of Sleep
REQUIRE(duration.count() >= wait_time.count());
auto ratio = static_cast<uint64_t>(duration / interval);
REQUIRE(counter >= ratio - 1);
REQUIRE(counter <= ratio + 1);
}
// Test concurrent timers
{
const auto interval1 = 100ms;
const auto interval2 = 200ms;
std::atomic<uint64_t> counter1;
std::atomic<uint64_t> counter2;
auto start = std::chrono::steady_clock::now();
auto cb1 = [&counter1] { ++counter1; };
auto cb2 = [&counter2] { ++counter2; };
auto pTimer1 = HighResolutionTimer::CreateRepeating(interval1, cb1);
auto pTimer2 = HighResolutionTimer::CreateRepeating(interval2, cb2);
Sleep(wait_time);
pTimer1.reset();
pTimer2.reset();
auto duration = std::chrono::steady_clock::now() - start;
// Should have run as many times as wait_time / timer_interval plus or
// minus 1 due to imprecision of Sleep
REQUIRE(duration.count() >= wait_time.count());
auto ratio1 = static_cast<uint64_t>(duration / interval1);
auto ratio2 = static_cast<uint64_t>(duration / interval2);
REQUIRE(counter1 >= ratio1 - 1);
REQUIRE(counter1 <= ratio1 + 1);
REQUIRE(counter2 >= ratio2 - 1);
REQUIRE(counter2 <= ratio2 + 1);
}
// TODO(bwrsandman): Check on which thread callbacks are executed when
// spawned from differing threads
}
TEST_CASE("Wait on Multiple Handles", "Wait") {

View File

@ -13,12 +13,13 @@
#include "xenia/base/logging.h"
#include <pthread.h>
#include <signal.h>
#include <sys/eventfd.h>
#include <sys/syscall.h>
#include <sys/time.h>
#include <sys/types.h>
#include <time.h>
#include <unistd.h>
#include <ctime>
namespace xe {
namespace threading {
@ -32,6 +33,37 @@ inline timespec DurationToTimeSpec(
return timespec{div.quot, div.rem};
}
// Thread interruption is done using user-defined signals
// 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 };
int GetSystemSignal(SignalType num) {
auto result = SIGRTMIN + static_cast<int>(num);
assert_true(result < SIGRTMAX);
return result;
}
SignalType GetSystemSignalType(int num) {
return static_cast<SignalType>(num - SIGRTMIN);
}
thread_local std::array<bool, static_cast<size_t>(SignalType::k_Count)>
signal_handler_installed = {};
static void signal_handler(int signal, siginfo_t* info, void* context);
void install_signal_handler(SignalType type) {
if (signal_handler_installed[static_cast<size_t>(type)]) return;
struct sigaction action {};
action.sa_flags = SA_SIGINFO;
action.sa_sigaction = signal_handler;
sigemptyset(&action.sa_mask);
if (sigaction(GetSystemSignal(type), &action, nullptr) == -1)
signal_handler_installed[static_cast<size_t>(type)] = true;
}
// TODO(dougvj)
void EnableAffinityConfiguration() {}
@ -57,8 +89,16 @@ void SyncMemory() { __sync_synchronize(); }
void Sleep(std::chrono::microseconds duration) {
timespec rqtp = DurationToTimeSpec(duration);
nanosleep(&rqtp, nullptr);
// TODO(benvanik): spin while rmtp >0?
timespec rmtp = {};
auto p_rqtp = &rqtp;
auto p_rmtp = &rmtp;
int ret = 0;
do {
ret = nanosleep(p_rqtp, p_rmtp);
// Swap requested for remaining in case of signal interruption
// in which case, we start sleeping again for the remainder
std::swap(p_rqtp, p_rmtp);
} while (ret == -1 && errno == EINTR);
}
// TODO(dougvj) Not sure how to implement the equivalent of this on POSIX.
@ -86,24 +126,37 @@ bool SetTlsValue(TlsHandle handle, uintptr_t value) {
return false;
}
// TODO(dougvj)
class PosixHighResolutionTimer : public HighResolutionTimer {
public:
PosixHighResolutionTimer(std::function<void()> callback)
: callback_(callback) {}
~PosixHighResolutionTimer() override {}
explicit PosixHighResolutionTimer(std::function<void()> callback)
: callback_(std::move(callback)), timer_(nullptr) {}
~PosixHighResolutionTimer() override {
if (timer_) timer_delete(timer_);
}
bool Initialize(std::chrono::milliseconds period) {
assert_always();
return false;
// Create timer
sigevent sev{};
sev.sigev_notify = SIGEV_SIGNAL;
sev.sigev_signo = GetSystemSignal(SignalType::kHighResolutionTimer);
sev.sigev_value.sival_ptr = (void*)&callback_;
if (timer_create(CLOCK_REALTIME, &sev, &timer_) == -1) return false;
// Start timer
itimerspec its{};
its.it_value = DurationToTimeSpec(period);
its.it_interval = its.it_value;
return timer_settime(timer_, 0, &its, nullptr) != -1;
}
private:
std::function<void()> callback_;
timer_t timer_;
};
std::unique_ptr<HighResolutionTimer> HighResolutionTimer::CreateRepeating(
std::chrono::milliseconds period, std::function<void()> callback) {
install_signal_handler(SignalType::kHighResolutionTimer);
auto timer = std::make_unique<PosixHighResolutionTimer>(std::move(callback));
if (!timer->Initialize(period)) {
return nullptr;
@ -467,5 +520,18 @@ void Thread::Exit(int exit_code) {
pthread_exit(reinterpret_cast<void*>(exit_code));
}
static void signal_handler(int signal, siginfo_t* info, void* /*context*/) {
switch (GetSystemSignalType(signal)) {
case SignalType::kHighResolutionTimer: {
assert_not_null(info->si_value.sival_ptr);
auto callback =
*static_cast<std::function<void()>*>(info->si_value.sival_ptr);
callback();
} break;
default:
assert_always();
}
}
} // namespace threading
} // namespace xe