From b5ea68647561377403515ff4e1585d7124b4abe0 Mon Sep 17 00:00:00 2001 From: Sandy Carter Date: Mon, 3 Dec 2018 22:20:56 -0800 Subject: [PATCH] [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 --- .gdbinit | 2 + src/xenia/base/testing/threading_test.cc | 54 ++++++++++++++- src/xenia/base/threading_posix.cc | 84 +++++++++++++++++++++--- 3 files changed, 129 insertions(+), 11 deletions(-) create mode 100644 .gdbinit diff --git a/.gdbinit b/.gdbinit new file mode 100644 index 000000000..872fae6b0 --- /dev/null +++ b/.gdbinit @@ -0,0 +1,2 @@ +# Ignore HighResolutionTimer custom event +handle SIG34 nostop noprint diff --git a/src/xenia/base/testing/threading_test.cc b/src/xenia/base/testing/threading_test.cc index 18c39899b..37af92c80 100644 --- a/src/xenia/base/testing/threading_test.cc +++ b/src/xenia/base/testing/threading_test.cc @@ -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 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(duration / interval); + REQUIRE(counter >= ratio - 1); + REQUIRE(counter <= ratio + 1); + } + + // Test concurrent timers + { + const auto interval1 = 100ms; + const auto interval2 = 200ms; + std::atomic counter1; + std::atomic 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(duration / interval1); + auto ratio2 = static_cast(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") { diff --git a/src/xenia/base/threading_posix.cc b/src/xenia/base/threading_posix.cc index 1ee68795c..3fdb4bdcb 100644 --- a/src/xenia/base/threading_posix.cc +++ b/src/xenia/base/threading_posix.cc @@ -13,12 +13,13 @@ #include "xenia/base/logging.h" #include +#include #include #include #include #include -#include #include +#include 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(num); + assert_true(result < SIGRTMAX); + return result; +} + +SignalType GetSystemSignalType(int num) { + return static_cast(num - SIGRTMIN); +} + +thread_local std::array(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(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(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 callback) - : callback_(callback) {} - ~PosixHighResolutionTimer() override {} + explicit PosixHighResolutionTimer(std::function 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 callback_; + timer_t timer_; }; std::unique_ptr HighResolutionTimer::CreateRepeating( std::chrono::milliseconds period, std::function callback) { + install_signal_handler(SignalType::kHighResolutionTimer); auto timer = std::make_unique(std::move(callback)); if (!timer->Initialize(period)) { return nullptr; @@ -467,5 +520,18 @@ void Thread::Exit(int exit_code) { pthread_exit(reinterpret_cast(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*>(info->si_value.sival_ptr); + callback(); + } break; + default: + assert_always(); + } +} + } // namespace threading } // namespace xe