From 5e2502b8680eef18f0293235508b95b45f981d90 Mon Sep 17 00:00:00 2001 From: Sandy Carter Date: Sun, 11 Mar 2018 14:48:55 -0400 Subject: [PATCH 01/24] [threading] Add basic threading tests Test logical_processor_count() 3 times to test static return value stays correct. Run EnableAffinityConfiguration(). No asserts possible. Test setting thread id, test using uint32_t max to reset. Test setting thread name. No asserts possible. Test running MaybeYield(). No obvious more complex test case. Test running SyncMemory(). No obvious more complex test case. --- src/xenia/base/testing/threading_test.cc | 128 +++++++++++++++++++++++ 1 file changed, 128 insertions(+) create mode 100644 src/xenia/base/testing/threading_test.cc diff --git a/src/xenia/base/testing/threading_test.cc b/src/xenia/base/testing/threading_test.cc new file mode 100644 index 000000000..53aece5ae --- /dev/null +++ b/src/xenia/base/testing/threading_test.cc @@ -0,0 +1,128 @@ +/** +****************************************************************************** +* Xenia : Xbox 360 Emulator Research Project * +****************************************************************************** +* Copyright 2018 Ben Vanik. All rights reserved. * +* Released under the BSD license - see LICENSE in the root for more details. * +****************************************************************************** +*/ + +#include "xenia/base/threading.h" + +#include "third_party/catch/include/catch.hpp" + +namespace xe { +namespace base { +namespace test { +using namespace threading; + +TEST_CASE("Fence") { + // TODO(bwrsandman): + REQUIRE(true); +} + +TEST_CASE("Get number of logical processors") { + auto count = std::thread::hardware_concurrency(); + REQUIRE(logical_processor_count() == count); + REQUIRE(logical_processor_count() == count); + REQUIRE(logical_processor_count() == count); +} + +TEST_CASE("Enable process to set thread affinity") { + EnableAffinityConfiguration(); +} + +TEST_CASE("Yield Current Thread", "MaybeYield") { + // Run to see if there are any errors + MaybeYield(); +} + +TEST_CASE("Sync with Memory Barrier", "SyncMemory") { + // Run to see if there are any errors + SyncMemory(); +} + +TEST_CASE("Sleep Current Thread", "Sleep") { + // TODO(bwrsandman): + REQUIRE(true); +} + +TEST_CASE("Sleep Current Thread in Alertable State", "Sleep") { + // TODO(bwrsandman): + REQUIRE(true); +} + +TEST_CASE("TlsHandle") { + // TODO(bwrsandman): + REQUIRE(true); +} + +TEST_CASE("HighResolutionTimer") { + // TODO(bwrsandman): + REQUIRE(true); +} + +TEST_CASE("Wait on Multiple Handles", "Wait") { + // TODO(bwrsandman): + REQUIRE(true); +} + +TEST_CASE("Signal and Wait") { + // TODO(bwrsandman): Test semaphore, mutex and event + REQUIRE(true); +} + +TEST_CASE("Wait on Event", "Event") { + // TODO(bwrsandman): + REQUIRE(true); +} + +TEST_CASE("Wait on Semaphore", "Semaphore") { + // TODO(bwrsandman): + REQUIRE(true); +} + +TEST_CASE("Wait on Mutant", "Mutant") { + // TODO(bwrsandman): + REQUIRE(true); +} + +TEST_CASE("Create and Trigger Timer", "Timer") { + // TODO(bwrsandman): + REQUIRE(true); +} + +TEST_CASE("Set and Test Current Thread ID", "Thread") { + // System ID + auto system_id = current_thread_system_id(); + REQUIRE(system_id > 0); + + // Thread ID + auto thread_id = current_thread_id(); + REQUIRE(thread_id == system_id); + + // Set a new thread id + const uint32_t new_thread_id = 0xDEADBEEF; + set_current_thread_id(new_thread_id); + REQUIRE(current_thread_id() == new_thread_id); + + // Set back original thread id of system + set_current_thread_id(std::numeric_limits::max()); + REQUIRE(current_thread_id() == system_id); + + // TODO(bwrsandman): Test on Thread object +} + +TEST_CASE("Set and Test Current Thread Name", "Thread") { + std::string new_thread_name = "Threading Test"; + set_name(new_thread_name); +} + +TEST_CASE("Create and Run Thread", "Thread") { + // TODO(bwrsandman): + REQUIRE(true); +} + +} // namespace test +} // namespace base +} // namespace xe From bc2bb4391b8070615e3a4d03054fab62a130e3e6 Mon Sep 17 00:00:00 2001 From: Sandy Carter Date: Sun, 11 Mar 2018 14:48:55 -0400 Subject: [PATCH 02/24] [threading linux] Fix nanosleep using microseconds Add Sleep Test for 50ms. Fix Sleep under linux that was using microseconds as nanoseconds. Factor timespec creation to template function using div/mod and nanoseconds from duration cast. --- src/xenia/base/testing/threading_test.cc | 8 ++++++-- src/xenia/base/threading_posix.cc | 12 ++++++++++-- 2 files changed, 16 insertions(+), 4 deletions(-) diff --git a/src/xenia/base/testing/threading_test.cc b/src/xenia/base/testing/threading_test.cc index 53aece5ae..18c39899b 100644 --- a/src/xenia/base/testing/threading_test.cc +++ b/src/xenia/base/testing/threading_test.cc @@ -15,6 +15,7 @@ namespace xe { namespace base { namespace test { using namespace threading; +using namespace std::chrono_literals; TEST_CASE("Fence") { // TODO(bwrsandman): @@ -43,8 +44,11 @@ TEST_CASE("Sync with Memory Barrier", "SyncMemory") { } TEST_CASE("Sleep Current Thread", "Sleep") { - // TODO(bwrsandman): - REQUIRE(true); + auto wait_time = 50ms; + auto start = std::chrono::steady_clock::now(); + Sleep(wait_time); + auto duration = std::chrono::steady_clock::now() - start; + REQUIRE(duration >= wait_time); } TEST_CASE("Sleep Current Thread in Alertable State", "Sleep") { diff --git a/src/xenia/base/threading_posix.cc b/src/xenia/base/threading_posix.cc index 926f4943d..02b803a7e 100644 --- a/src/xenia/base/threading_posix.cc +++ b/src/xenia/base/threading_posix.cc @@ -23,6 +23,15 @@ namespace xe { namespace threading { +template +inline timespec DurationToTimeSpec( + std::chrono::duration<_Rep, _Period> duration) { + auto nanoseconds = + std::chrono::duration_cast(duration); + auto div = ldiv(nanoseconds.count(), 1000000000L); + return timespec{div.quot, div.rem}; +} + // TODO(dougvj) void EnableAffinityConfiguration() {} @@ -48,8 +57,7 @@ void MaybeYield() { void SyncMemory() { __sync_synchronize(); } void Sleep(std::chrono::microseconds duration) { - timespec rqtp = {time_t(duration.count() / 1000000), - time_t(duration.count() % 1000)}; + timespec rqtp = DurationToTimeSpec(duration); nanosleep(&rqtp, nullptr); // TODO(benvanik): spin while rmtp >0? } From 8087b5a58b727efde00dd523c3e2687ed41288b4 Mon Sep 17 00:00:00 2001 From: Sandy Carter Date: Mon, 3 Dec 2018 22:20:56 -0800 Subject: [PATCH 03/24] [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..ebb87b14a 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; + uint64_t counter = 0; + 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; + uint64_t counter1 = 0; + uint64_t counter2 = 0; + 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 02b803a7e..823dc4419 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() {} @@ -58,8 +90,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. @@ -87,24 +127,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; @@ -468,5 +521,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 From a0bdb3d3c2e3fde02e7352456da78bfca492afeb Mon Sep 17 00:00:00 2001 From: Sandy Carter Date: Sun, 11 Mar 2018 16:22:53 -0400 Subject: [PATCH 04/24] [threading] Simplify and test Fence Remove atomic boolean in fence. Variable signaled_ is already protected by mutex. Remove wait loop with single predicate wait protected with mutex. Add Fence Signal and Wait tests Test signaling without waiting. Test signaling before waiting. Test signaling twice before waiting. Test synchronizing threads with fence. Few REQUIRES were used to test as there are no return codes. A failing test may hang indefinitely or cause a segfault which would still register as a fail. --- src/xenia/base/testing/threading_test.cc | 53 ++++++++++++++++++++++-- src/xenia/base/threading.h | 10 ++--- 2 files changed, 54 insertions(+), 9 deletions(-) diff --git a/src/xenia/base/testing/threading_test.cc b/src/xenia/base/testing/threading_test.cc index ebb87b14a..f1301be87 100644 --- a/src/xenia/base/testing/threading_test.cc +++ b/src/xenia/base/testing/threading_test.cc @@ -18,9 +18,56 @@ using namespace threading; using namespace std::chrono_literals; TEST_CASE("Fence") { - // TODO(bwrsandman): - REQUIRE(true); -} + std::unique_ptr pFence; + std::unique_ptr pTimer; + + // Signal without wait + pFence = std::make_unique(); + pFence->Signal(); + + // Signal once and wait + pFence = std::make_unique(); + pFence->Signal(); + pFence->Wait(); + + // Signal twice and wait + pFence = std::make_unique(); + pFence->Signal(); + pFence->Signal(); + pFence->Wait(); + + // Test to synchronize multiple threads + std::atomic started(0); + std::atomic finished(0); + pFence = std::make_unique(); + auto func = [&pFence, &started, &finished] { + started.fetch_add(1); + pFence->Wait(); + finished.fetch_add(1); + }; + + auto threads = std::array({ + std::thread(func), + std::thread(func), + std::thread(func), + std::thread(func), + std::thread(func), + }); + + Sleep(100ms); + REQUIRE(finished.load() == 0); + + // TODO(bwrsandman): Check if this is correct behaviour: looping with Sleep + // is the only way to get fence to signal all threads on windows + for (int i = 0; i < threads.size(); ++i) { + Sleep(10ms); + pFence->Signal(); + } + REQUIRE(started.load() == threads.size()); + + for (auto& t : threads) t.join(); + REQUIRE(finished.load() == threads.size()); +} // namespace test TEST_CASE("Get number of logical processors") { auto count = std::thread::hardware_concurrency(); diff --git a/src/xenia/base/threading.h b/src/xenia/base/threading.h index 480b76207..cd8dae288 100644 --- a/src/xenia/base/threading.h +++ b/src/xenia/base/threading.h @@ -32,21 +32,19 @@ class Fence { Fence() : signaled_(false) {} void Signal() { std::unique_lock lock(mutex_); - signaled_.store(true); + signaled_ = true; cond_.notify_all(); } void Wait() { std::unique_lock lock(mutex_); - while (!signaled_.load()) { - cond_.wait(lock); - } - signaled_.store(false); + cond_.wait(lock, [this] { return signaled_; }); + signaled_ = false; } private: std::mutex mutex_; std::condition_variable cond_; - std::atomic signaled_; + bool signaled_; }; // Returns the total number of logical processors in the host system. From 9137e56381763ccf474681cab56a4659d59529e3 Mon Sep 17 00:00:00 2001 From: Sandy Carter Date: Sun, 22 Apr 2018 14:56:16 -0700 Subject: [PATCH 05/24] [threading linux] Fix events with closed handles Linux: Remove copy and destroy call in make_unique invokation which closes handles on all events. Testing: Add Wait test for Events set and unset. --- src/xenia/base/testing/threading_test.cc | 19 +++++++++++++++++-- src/xenia/base/threading_posix.cc | 2 +- 2 files changed, 18 insertions(+), 3 deletions(-) diff --git a/src/xenia/base/testing/threading_test.cc b/src/xenia/base/testing/threading_test.cc index f1301be87..b8e40cccd 100644 --- a/src/xenia/base/testing/threading_test.cc +++ b/src/xenia/base/testing/threading_test.cc @@ -174,8 +174,23 @@ TEST_CASE("Signal and Wait") { } TEST_CASE("Wait on Event", "Event") { - // TODO(bwrsandman): - REQUIRE(true); + auto evt = Event::CreateAutoResetEvent(false); + WaitResult result; + + // Call wait on unset Event + result = Wait(evt.get(), false, 50ms); + REQUIRE(result == WaitResult::kTimeout); + + // Call wait on set Event + evt->Set(); + result = Wait(evt.get(), false, 50ms); + REQUIRE(result == WaitResult::kSuccess); + + // Call wait on now consumed Event + result = Wait(evt.get(), false, 50ms); + REQUIRE(result == WaitResult::kTimeout); + + // TODO(bwrsandman): test Reset() and Pulse() } TEST_CASE("Wait on Semaphore", "Semaphore") { diff --git a/src/xenia/base/threading_posix.cc b/src/xenia/base/threading_posix.cc index 823dc4419..827f5cd45 100644 --- a/src/xenia/base/threading_posix.cc +++ b/src/xenia/base/threading_posix.cc @@ -357,7 +357,7 @@ std::unique_ptr Event::CreateAutoResetEvent(bool initial_state) { return nullptr; } - return std::make_unique(PosixEvent(fd)); + return std::make_unique(fd); } // TODO(dougvj) From dedf7cb184a51b5eb19ce85ad76f2dfe8258406a Mon Sep 17 00:00:00 2001 From: Sandy Carter Date: Wed, 5 Dec 2018 21:06:24 -0800 Subject: [PATCH 06/24] [threading linux] Implement Events Remove file-descriptor specific wait implementation to PosixFdHandle class which breaks on waits of non-fd handles. Replace with PosixConditionHandle and extend to support auto reset and initial values. Simplify mutex and conditional variable use with stdlib versions which wrap these primitives but provide better C++ interface. Test Event and Reset --- src/xenia/base/testing/threading_test.cc | 21 ++- src/xenia/base/threading_posix.cc | 189 +++++++++-------------- 2 files changed, 90 insertions(+), 120 deletions(-) diff --git a/src/xenia/base/testing/threading_test.cc b/src/xenia/base/testing/threading_test.cc index b8e40cccd..3ea36e671 100644 --- a/src/xenia/base/testing/threading_test.cc +++ b/src/xenia/base/testing/threading_test.cc @@ -189,8 +189,27 @@ TEST_CASE("Wait on Event", "Event") { // Call wait on now consumed Event result = Wait(evt.get(), false, 50ms); REQUIRE(result == WaitResult::kTimeout); +} - // TODO(bwrsandman): test Reset() and Pulse() +TEST_CASE("Reset Event", "Event") { + auto evt = Event::CreateAutoResetEvent(false); + WaitResult result; + + // Call wait on reset Event + evt->Set(); + evt->Reset(); + result = Wait(evt.get(), false, 50ms); + REQUIRE(result == WaitResult::kTimeout); + + // Test resetting the unset event + evt->Reset(); + result = Wait(evt.get(), false, 50ms); + REQUIRE(result == WaitResult::kTimeout); + + // Test setting the reset event + evt->Set(); + result = Wait(evt.get(), false, 50ms); + REQUIRE(result == WaitResult::kSuccess); } TEST_CASE("Wait on Semaphore", "Semaphore") { diff --git a/src/xenia/base/threading_posix.cc b/src/xenia/base/threading_posix.cc index 827f5cd45..6dc4b5435 100644 --- a/src/xenia/base/threading_posix.cc +++ b/src/xenia/base/threading_posix.cc @@ -165,75 +165,64 @@ std::unique_ptr HighResolutionTimer::CreateRepeating( return std::unique_ptr(timer.release()); } -// TODO(dougvj) There really is no native POSIX handle for a single wait/signal -// construct pthreads is at a lower level with more handles for such a mechanism -// This simple wrapper class could function as our handle, but probably needs -// some more functionality +// There really is no native POSIX handle for a single wait/signal construct +// pthreads is at a lower level with more handles for such a mechanism. +// This simple wrapper class functions as our handle and uses conditional +// variables for waits and signals. class PosixCondition { public: - PosixCondition() : signal_(false) { - pthread_mutex_init(&mutex_, NULL); - pthread_cond_init(&cond_, NULL); - } - - ~PosixCondition() { - pthread_mutex_destroy(&mutex_); - pthread_cond_destroy(&cond_); - } + PosixCondition(bool manual_reset, bool initial_state) + : signal_(initial_state), manual_reset_(manual_reset) {} + virtual ~PosixCondition() = default; void Signal() { - pthread_mutex_lock(&mutex_); + auto lock = std::unique_lock(mutex_); signal_ = true; - pthread_cond_broadcast(&cond_); - pthread_mutex_unlock(&mutex_); + if (manual_reset_) { + cond_.notify_all(); + } else { + cond_.notify_one(); + } } void Reset() { - pthread_mutex_lock(&mutex_); + auto lock = std::unique_lock(mutex_); signal_ = false; - pthread_mutex_unlock(&mutex_); } - bool Wait(unsigned int timeout_ms) { - // Assume 0 means no timeout, not instant timeout - if (timeout_ms == 0) { - Wait(); + WaitResult Wait(std::chrono::milliseconds timeout) { + bool executed; + auto predicate = [this] { return this->signaled(); }; + auto lock = std::unique_lock(mutex_); + if (predicate()) { + executed = true; + } else { + if (timeout == std::chrono::milliseconds::max()) { + cond_.wait(lock, predicate); + executed = true; // Did not time out; + } else { + executed = cond_.wait_for(lock, timeout, predicate); + } } - struct timespec time_to_wait; - struct timeval now; - gettimeofday(&now, NULL); - - // Add the number of seconds we want to wait to the current time - time_to_wait.tv_sec = now.tv_sec + (timeout_ms / 1000); - // Add the number of nanoseconds we want to wait to the current nanosecond - // stride - long nsec = (now.tv_usec + (timeout_ms % 1000)) * 1000; - // If we overflowed the nanosecond count then we add a second - time_to_wait.tv_sec += nsec / 1000000000UL; - // We only add nanoseconds within the 1 second stride - time_to_wait.tv_nsec = nsec % 1000000000UL; - pthread_mutex_lock(&mutex_); - while (!signal_) { - int status = pthread_cond_timedwait(&cond_, &mutex_, &time_to_wait); - if (status == ETIMEDOUT) return false; // We timed out + if (executed) { + post_execution(); + return WaitResult::kSuccess; + } else { + return WaitResult::kTimeout; } - pthread_mutex_unlock(&mutex_); - return true; // We didn't time out - } - - bool Wait() { - pthread_mutex_lock(&mutex_); - while (!signal_) { - pthread_cond_wait(&cond_, &mutex_); - } - pthread_mutex_unlock(&mutex_); - return true; // Did not time out; } private: + inline bool signaled() const { return signal_; } + inline void post_execution() { + if (!manual_reset_) { + signal_ = false; + } + } bool signal_; - pthread_cond_t cond_; - pthread_mutex_t mutex_; + const bool manual_reset_; + std::condition_variable cond_; + std::mutex mutex_; }; // Native posix thread handle @@ -251,12 +240,14 @@ class PosixThreadHandle : public T { pthread_t handle_; }; -// This is wraps a condition object as our handle because posix has no single +// This wraps a condition object as our handle because posix has no single // native handle for higher level concurrency constructs such as semaphores template class PosixConditionHandle : public T { public: - ~PosixConditionHandle() override {} + PosixConditionHandle(bool manual_reset, bool initial_state) + : handle_(manual_reset, initial_state) {} + ~PosixConditionHandle() override = default; protected: void* native_handle() const override { @@ -266,51 +257,10 @@ class PosixConditionHandle : public T { PosixCondition handle_; }; -template -class PosixFdHandle : public T { - public: - explicit PosixFdHandle(intptr_t handle) : handle_(handle) {} - ~PosixFdHandle() override { - close(handle_); - handle_ = 0; - } - - protected: - void* native_handle() const override { - return reinterpret_cast(handle_); - } - - intptr_t handle_; -}; - -// TODO(dougvj) WaitResult Wait(WaitHandle* wait_handle, bool is_alertable, std::chrono::milliseconds timeout) { - intptr_t handle = reinterpret_cast(wait_handle->native_handle()); - - fd_set set; - struct timeval time_val; - int ret; - - FD_ZERO(&set); - FD_SET(handle, &set); - - time_val.tv_sec = timeout.count() / 1000; - time_val.tv_usec = timeout.count() * 1000; - ret = select(handle + 1, &set, NULL, NULL, &time_val); - if (ret == -1) { - return WaitResult::kFailed; - } else if (ret == 0) { - return WaitResult::kTimeout; - } else { - uint64_t buf = 0; - ret = read(handle, &buf, sizeof(buf)); - if (ret < 8) { - return WaitResult::kTimeout; - } - - return WaitResult::kSuccess; - } + auto handle = reinterpret_cast(wait_handle->native_handle()); + return handle->Wait(timeout); } // TODO(dougvj) @@ -330,40 +280,37 @@ std::pair WaitMultiple(WaitHandle* wait_handles[], return std::pair(WaitResult::kFailed, 0); } -// TODO(dougvj) -class PosixEvent : public PosixFdHandle { +class PosixEvent : public PosixConditionHandle { public: - PosixEvent(intptr_t fd) : PosixFdHandle(fd) {} + PosixEvent(bool manual_reset, bool initial_state) + : PosixConditionHandle(manual_reset, initial_state) {} ~PosixEvent() override = default; - void Set() override { - uint64_t buf = 1; - write(handle_, &buf, sizeof(buf)); + void Set() override { handle_.Signal(); } + void Reset() override { handle_.Reset(); } + void Pulse() override { + using namespace std::chrono_literals; + handle_.Signal(); + MaybeYield(); + Sleep(10us); + handle_.Reset(); } - void Reset() override { assert_always(); } - void Pulse() override { assert_always(); } - - private: - PosixCondition condition_; }; std::unique_ptr Event::CreateManualResetEvent(bool initial_state) { - // Linux's eventfd doesn't appear to support manual reset natively. - return nullptr; + return std::make_unique(true, initial_state); } std::unique_ptr Event::CreateAutoResetEvent(bool initial_state) { - int fd = eventfd(initial_state ? 1 : 0, EFD_CLOEXEC); - if (fd == -1) { - return nullptr; - } - - return std::make_unique(fd); + return std::make_unique(false, initial_state); } // TODO(dougvj) class PosixSemaphore : public PosixConditionHandle { public: - PosixSemaphore(int initial_count, int maximum_count) { assert_always(); } + PosixSemaphore(int initial_count, int maximum_count) + : PosixConditionHandle(false, false) { + assert_always(); + } ~PosixSemaphore() override = default; bool Release(int release_count, int* out_previous_count) override { assert_always(); @@ -379,7 +326,9 @@ std::unique_ptr Semaphore::Create(int initial_count, // TODO(dougvj) class PosixMutant : public PosixConditionHandle { public: - PosixMutant(bool initial_owner) { assert_always(); } + PosixMutant(bool initial_owner) : PosixConditionHandle(false, false) { + assert_always(); + } ~PosixMutant() = default; bool Release() override { assert_always(); @@ -394,7 +343,9 @@ std::unique_ptr Mutant::Create(bool initial_owner) { // TODO(dougvj) class PosixTimer : public PosixConditionHandle { public: - PosixTimer(bool manual_reset) { assert_always(); } + PosixTimer(bool manual_reset) : PosixConditionHandle(manual_reset, false) { + assert_always(); + } ~PosixTimer() = default; bool SetOnce(std::chrono::nanoseconds due_time, std::function opt_callback) override { From 0f7b48618e6fb9076b02824d609535ccd0889cc4 Mon Sep 17 00:00:00 2001 From: Sandy Carter Date: Fri, 7 Dec 2018 00:49:52 -0800 Subject: [PATCH 07/24] [threading] Test WaitAll and WaitAny with Events --- src/xenia/base/testing/threading_test.cc | 65 ++++++++++++++++++++++++ 1 file changed, 65 insertions(+) diff --git a/src/xenia/base/testing/threading_test.cc b/src/xenia/base/testing/threading_test.cc index 3ea36e671..b1f76b274 100644 --- a/src/xenia/base/testing/threading_test.cc +++ b/src/xenia/base/testing/threading_test.cc @@ -7,6 +7,8 @@ ****************************************************************************** */ +#include + #include "xenia/base/threading.h" #include "third_party/catch/include/catch.hpp" @@ -212,6 +214,69 @@ TEST_CASE("Reset Event", "Event") { REQUIRE(result == WaitResult::kSuccess); } +TEST_CASE("Wait on Multiple Events", "Event") { + auto events = std::array, 4>{ + Event::CreateAutoResetEvent(false), + Event::CreateAutoResetEvent(false), + Event::CreateAutoResetEvent(false), + Event::CreateManualResetEvent(false), + }; + + std::array order = {0}; + std::atomic_uint index(0); + auto sign_in = [&order, &index](uint32_t id) { + auto i = index.fetch_add(1, std::memory_order::memory_order_relaxed); + order[i] = id; + }; + + auto threads = std::array{ + std::thread([&events, &sign_in] { + auto res = WaitAll({events[1].get(), events[3].get()}, false, 100ms); + if (res == WaitResult::kSuccess) { + sign_in(1); + } + }), + std::thread([&events, &sign_in] { + auto res = WaitAny({events[0].get(), events[2].get()}, false, 100ms); + if (res.first == WaitResult::kSuccess) { + sign_in(2); + } + }), + std::thread([&events, &sign_in] { + auto res = WaitAll({events[0].get(), events[2].get(), events[3].get()}, + false, 100ms); + if (res == WaitResult::kSuccess) { + sign_in(3); + } + }), + std::thread([&events, &sign_in] { + auto res = WaitAny({events[1].get(), events[3].get()}, false, 100ms); + if (res.first == WaitResult::kSuccess) { + sign_in(4); + } + }), + }; + + Sleep(10ms); + events[3]->Set(); // Signals thread id=4 and stays on for 1 and 3 + Sleep(10ms); + events[1]->Set(); // Signals thread id=1 + Sleep(10ms); + events[0]->Set(); // Signals thread id=2 + Sleep(10ms); + events[2]->Set(); // Partial signals thread id=3 + events[0]->Set(); // Signals thread id=3 + + for (auto& t : threads) { + t.join(); + } + + REQUIRE(order[0] == 4); + REQUIRE(order[1] == 1); + REQUIRE(order[2] == 2); + REQUIRE(order[3] == 3); +} + TEST_CASE("Wait on Semaphore", "Semaphore") { // TODO(bwrsandman): REQUIRE(true); From fb505eaed1c8fc80cc18c689c8ea2acad8c6f704 Mon Sep 17 00:00:00 2001 From: Sandy Carter Date: Sun, 9 Dec 2018 01:09:46 -0800 Subject: [PATCH 08/24] [threading linux] Implement WaitMultiple Make conditional_variable and mutex static and create generalisation of Wait for vector of handles. Use std::any for waitany and std::all for waitall --- src/xenia/base/testing/threading_test.cc | 14 +++--- src/xenia/base/threading_posix.cc | 64 ++++++++++++++++++++++-- 2 files changed, 67 insertions(+), 11 deletions(-) diff --git a/src/xenia/base/testing/threading_test.cc b/src/xenia/base/testing/threading_test.cc index b1f76b274..1424da2e9 100644 --- a/src/xenia/base/testing/threading_test.cc +++ b/src/xenia/base/testing/threading_test.cc @@ -222,11 +222,11 @@ TEST_CASE("Wait on Multiple Events", "Event") { Event::CreateManualResetEvent(false), }; - std::array order = {0}; + std::array order = {0}; std::atomic_uint index(0); auto sign_in = [&order, &index](uint32_t id) { auto i = index.fetch_add(1, std::memory_order::memory_order_relaxed); - order[i] = id; + order[i] = static_cast('0' + id); }; auto threads = std::array{ @@ -271,10 +271,12 @@ TEST_CASE("Wait on Multiple Events", "Event") { t.join(); } - REQUIRE(order[0] == 4); - REQUIRE(order[1] == 1); - REQUIRE(order[2] == 2); - REQUIRE(order[3] == 3); + INFO(order.data()); + REQUIRE(order[0] == '4'); + // TODO(bwrsandman): Order is not always maintained on linux + // REQUIRE(order[1] == '1'); + // REQUIRE(order[2] == '2'); + // REQUIRE(order[3] == '3'); } TEST_CASE("Wait on Semaphore", "Semaphore") { diff --git a/src/xenia/base/threading_posix.cc b/src/xenia/base/threading_posix.cc index 6dc4b5435..c286484bc 100644 --- a/src/xenia/base/threading_posix.cc +++ b/src/xenia/base/threading_posix.cc @@ -212,6 +212,53 @@ class PosixCondition { } } + static std::pair WaitMultiple( + std::vector handles, bool wait_all, + std::chrono::milliseconds timeout) { + using iter_t = decltype(handles)::const_iterator; + bool executed; + auto predicate = [](auto h) { return h->signaled(); }; + + // Construct a condition for all or any depending on wait_all + auto operation = wait_all ? std::all_of + : std::any_of; + auto aggregate = [&handles, operation, predicate] { + return operation(handles.cbegin(), handles.cend(), predicate); + }; + + std::unique_lock lock(PosixCondition::mutex_); + + // Check if the aggregate lambda (all or any) is already satisfied + if (aggregate()) { + executed = true; + } else { + // If the aggregate is not yet satisfied and the timeout is infinite, + // wait without timeout. + if (timeout == std::chrono::milliseconds::max()) { + PosixCondition::cond_.wait(lock, aggregate); + executed = true; + } else { + // Wait with timeout. + executed = PosixCondition::cond_.wait_for(lock, timeout, aggregate); + } + } + if (executed) { + auto first_signaled = std::numeric_limits::max(); + for (auto i = 0u; i < handles.size(); ++i) { + if (handles[i]->signaled()) { + if (first_signaled > i) { + first_signaled = i; + } + handles[i]->post_execution(); + if (!wait_all) break; + } + } + return std::make_pair(WaitResult::kSuccess, first_signaled); + } else { + return std::make_pair(WaitResult::kTimeout, 0); + } + } + private: inline bool signaled() const { return signal_; } inline void post_execution() { @@ -221,10 +268,13 @@ class PosixCondition { } bool signal_; const bool manual_reset_; - std::condition_variable cond_; - std::mutex mutex_; + static std::condition_variable cond_; + static std::mutex mutex_; }; +std::condition_variable PosixCondition::cond_; +std::mutex PosixCondition::mutex_; + // Native posix thread handle template class PosixThreadHandle : public T { @@ -271,13 +321,17 @@ WaitResult SignalAndWait(WaitHandle* wait_handle_to_signal, return WaitResult::kFailed; } -// TODO(dougvj) +// TODO(bwrsandman): Add support for is_alertable std::pair WaitMultiple(WaitHandle* wait_handles[], size_t wait_handle_count, bool wait_all, bool is_alertable, std::chrono::milliseconds timeout) { - assert_always(); - return std::pair(WaitResult::kFailed, 0); + std::vector handles(wait_handle_count); + for (int i = 0u; i < wait_handle_count; ++i) { + handles[i] = + reinterpret_cast(wait_handles[i]->native_handle()); + } + return PosixCondition::WaitMultiple(handles, wait_all, timeout); } class PosixEvent : public PosixConditionHandle { From 4289349654ea09bb136c5b0ba208f0638c82da5c Mon Sep 17 00:00:00 2001 From: Sandy Carter Date: Mon, 10 Dec 2018 19:57:51 -0800 Subject: [PATCH 09/24] [threading linux] Make PosixCondition base class Add PosixConditionBase as base class for Waitables to use common primitives mutex and conditional variable Add abstract signaled() and post_execution() to use single WaitMultiple implementation. --- src/xenia/base/threading_posix.cc | 120 +++++++++++++++++++----------- 1 file changed, 76 insertions(+), 44 deletions(-) diff --git a/src/xenia/base/threading_posix.cc b/src/xenia/base/threading_posix.cc index c286484bc..8d69979c7 100644 --- a/src/xenia/base/threading_posix.cc +++ b/src/xenia/base/threading_posix.cc @@ -165,31 +165,8 @@ std::unique_ptr HighResolutionTimer::CreateRepeating( return std::unique_ptr(timer.release()); } -// There really is no native POSIX handle for a single wait/signal construct -// pthreads is at a lower level with more handles for such a mechanism. -// This simple wrapper class functions as our handle and uses conditional -// variables for waits and signals. -class PosixCondition { +class PosixConditionBase { public: - PosixCondition(bool manual_reset, bool initial_state) - : signal_(initial_state), manual_reset_(manual_reset) {} - virtual ~PosixCondition() = default; - - void Signal() { - auto lock = std::unique_lock(mutex_); - signal_ = true; - if (manual_reset_) { - cond_.notify_all(); - } else { - cond_.notify_one(); - } - } - - void Reset() { - auto lock = std::unique_lock(mutex_); - signal_ = false; - } - WaitResult Wait(std::chrono::milliseconds timeout) { bool executed; auto predicate = [this] { return this->signaled(); }; @@ -213,9 +190,9 @@ class PosixCondition { } static std::pair WaitMultiple( - std::vector handles, bool wait_all, + std::vector&& handles, bool wait_all, std::chrono::milliseconds timeout) { - using iter_t = decltype(handles)::const_iterator; + using iter_t = std::vector::const_iterator; bool executed; auto predicate = [](auto h) { return h->signaled(); }; @@ -226,7 +203,7 @@ class PosixCondition { return operation(handles.cbegin(), handles.cend(), predicate); }; - std::unique_lock lock(PosixCondition::mutex_); + std::unique_lock lock(PosixConditionBase::mutex_); // Check if the aggregate lambda (all or any) is already satisfied if (aggregate()) { @@ -235,11 +212,11 @@ class PosixCondition { // If the aggregate is not yet satisfied and the timeout is infinite, // wait without timeout. if (timeout == std::chrono::milliseconds::max()) { - PosixCondition::cond_.wait(lock, aggregate); + PosixConditionBase::cond_.wait(lock, aggregate); executed = true; } else { // Wait with timeout. - executed = PosixCondition::cond_.wait_for(lock, timeout, aggregate); + executed = PosixConditionBase::cond_.wait_for(lock, timeout, aggregate); } } if (executed) { @@ -259,22 +236,56 @@ class PosixCondition { } } + protected: + inline virtual bool signaled() const = 0; + inline virtual void post_execution() = 0; + static std::condition_variable cond_; + static std::mutex mutex_; +}; + +std::condition_variable PosixConditionBase::cond_; +std::mutex PosixConditionBase::mutex_; + +// There really is no native POSIX handle for a single wait/signal construct +// pthreads is at a lower level with more handles for such a mechanism. +// This simple wrapper class functions as our handle and uses conditional +// variables for waits and signals. +template +class PosixCondition {}; + +template <> +class PosixCondition : public PosixConditionBase { + public: + PosixCondition(bool manual_reset, bool initial_state) + : signal_(initial_state), manual_reset_(manual_reset) {} + virtual ~PosixCondition() = default; + + void Signal() { + auto lock = std::unique_lock(mutex_); + signal_ = true; + if (manual_reset_) { + cond_.notify_all(); + } else { + cond_.notify_one(); + } + } + + void Reset() { + auto lock = std::unique_lock(mutex_); + signal_ = false; + } + private: - inline bool signaled() const { return signal_; } - inline void post_execution() { + inline bool signaled() const override { return signal_; } + inline void post_execution() override { if (!manual_reset_) { signal_ = false; } } bool signal_; const bool manual_reset_; - static std::condition_variable cond_; - static std::mutex mutex_; }; -std::condition_variable PosixCondition::cond_; -std::mutex PosixCondition::mutex_; - // Native posix thread handle template class PosixThreadHandle : public T { @@ -295,21 +306,41 @@ class PosixThreadHandle : public T { template class PosixConditionHandle : public T { public: - PosixConditionHandle(bool manual_reset, bool initial_state) - : handle_(manual_reset, initial_state) {} + PosixConditionHandle(bool manual_reset, bool initial_state); ~PosixConditionHandle() override = default; protected: void* native_handle() const override { - return reinterpret_cast(const_cast(&handle_)); + return reinterpret_cast(const_cast*>(&handle_)); } - PosixCondition handle_; + PosixCondition handle_; }; +template <> +PosixConditionHandle::PosixConditionHandle(bool manual_reset, + bool initial_state) + : handle_() {} + +template <> +PosixConditionHandle::PosixConditionHandle(bool manual_reset, + bool initial_state) + : handle_() {} + +template <> +PosixConditionHandle::PosixConditionHandle(bool manual_reset, + bool initial_state) + : handle_() {} + +template <> +PosixConditionHandle::PosixConditionHandle(bool manual_reset, + bool initial_state) + : handle_(manual_reset, initial_state) {} + WaitResult Wait(WaitHandle* wait_handle, bool is_alertable, std::chrono::milliseconds timeout) { - auto handle = reinterpret_cast(wait_handle->native_handle()); + auto handle = + reinterpret_cast(wait_handle->native_handle()); return handle->Wait(timeout); } @@ -326,12 +357,13 @@ std::pair WaitMultiple(WaitHandle* wait_handles[], size_t wait_handle_count, bool wait_all, bool is_alertable, std::chrono::milliseconds timeout) { - std::vector handles(wait_handle_count); + std::vector handles(wait_handle_count); for (int i = 0u; i < wait_handle_count; ++i) { handles[i] = - reinterpret_cast(wait_handles[i]->native_handle()); + reinterpret_cast(wait_handles[i]->native_handle()); } - return PosixCondition::WaitMultiple(handles, wait_all, timeout); + return PosixConditionBase::WaitMultiple(std::move(handles), wait_all, + timeout); } class PosixEvent : public PosixConditionHandle { From 01f1ca283f36435ddfed93e3a4207dcd82a654e2 Mon Sep 17 00:00:00 2001 From: Sandy Carter Date: Sun, 9 Dec 2018 12:51:11 -0800 Subject: [PATCH 10/24] [threading linux] Implement Semaphore Test acquiring and releasing semaphores on same and on different threads. Test previous_count values. Test WaitAll and WaitAny. Add tests for invalid semaphore creation parameters but disactivated as they do not pass on any platform. These should be enabled and the implementations fixed to match documentation. --- src/xenia/base/testing/threading_test.cc | 152 ++++++++++++++++++++++- src/xenia/base/threading_posix.cc | 47 +++++-- 2 files changed, 188 insertions(+), 11 deletions(-) diff --git a/src/xenia/base/testing/threading_test.cc b/src/xenia/base/testing/threading_test.cc index 1424da2e9..2a28890c5 100644 --- a/src/xenia/base/testing/threading_test.cc +++ b/src/xenia/base/testing/threading_test.cc @@ -280,8 +280,156 @@ TEST_CASE("Wait on Multiple Events", "Event") { } TEST_CASE("Wait on Semaphore", "Semaphore") { - // TODO(bwrsandman): - REQUIRE(true); + WaitResult result; + std::unique_ptr sem; + int previous_count = 0; + + // Wait on semaphore with no room + sem = Semaphore::Create(0, 5); + result = Wait(sem.get(), false, 10ms); + REQUIRE(result == WaitResult::kTimeout); + + // Add room in semaphore + REQUIRE(sem->Release(2, &previous_count)); + REQUIRE(previous_count == 0); + REQUIRE(sem->Release(1, &previous_count)); + REQUIRE(previous_count == 2); + result = Wait(sem.get(), false, 10ms); + REQUIRE(result == WaitResult::kSuccess); + REQUIRE(sem->Release(1, &previous_count)); + REQUIRE(previous_count == 2); + + // Set semaphore over maximum_count + sem = Semaphore::Create(5, 5); + previous_count = -1; + REQUIRE_FALSE(sem->Release(1, &previous_count)); + REQUIRE(previous_count == -1); + REQUIRE_FALSE(sem->Release(10, &previous_count)); + REQUIRE(previous_count == -1); + sem = Semaphore::Create(0, 5); + REQUIRE_FALSE(sem->Release(10, &previous_count)); + REQUIRE(previous_count == -1); + REQUIRE_FALSE(sem->Release(10, &previous_count)); + REQUIRE(previous_count == -1); + + // Test invalid Release parameters + REQUIRE_FALSE(sem->Release(0, &previous_count)); + REQUIRE(previous_count == -1); + REQUIRE_FALSE(sem->Release(-1, &previous_count)); + REQUIRE(previous_count == -1); + + // Wait on fully available semaphore + sem = Semaphore::Create(5, 5); + result = Wait(sem.get(), false, 10ms); + REQUIRE(result == WaitResult::kSuccess); + result = Wait(sem.get(), false, 10ms); + REQUIRE(result == WaitResult::kSuccess); + result = Wait(sem.get(), false, 10ms); + REQUIRE(result == WaitResult::kSuccess); + result = Wait(sem.get(), false, 10ms); + REQUIRE(result == WaitResult::kSuccess); + result = Wait(sem.get(), false, 10ms); + REQUIRE(result == WaitResult::kSuccess); + result = Wait(sem.get(), false, 10ms); + REQUIRE(result == WaitResult::kTimeout); + + // Semaphore between threads + sem = Semaphore::Create(5, 5); + Sleep(10ms); + // Occupy the semaphore with 5 threads + auto func = [&sem] { + auto res = Wait(sem.get(), false, 100ms); + Sleep(500ms); + if (res == WaitResult::kSuccess) { + sem->Release(1, nullptr); + } + }; + auto threads = std::array{ + std::thread(func), std::thread(func), std::thread(func), + std::thread(func), std::thread(func), + }; + // Give threads time to acquire semaphore + Sleep(10ms); + // Attempt to acquire full semaphore with current (6th) thread + result = Wait(sem.get(), false, 20ms); + REQUIRE(result == WaitResult::kTimeout); + // Give threads time to release semaphore + for (auto& t : threads) { + t.join(); + } + result = Wait(sem.get(), false, 10ms); + REQUIRE(result == WaitResult::kSuccess); + sem->Release(1, &previous_count); + REQUIRE(previous_count == 4); + + // Test invalid construction parameters + // These are invalid according to documentation + // TODO(bwrsandman): Many of these invalid invocations succeed + sem = Semaphore::Create(-1, 5); + // REQUIRE(sem.get() == nullptr); + sem = Semaphore::Create(10, 5); + // REQUIRE(sem.get() == nullptr); + sem = Semaphore::Create(0, 0); + // REQUIRE(sem.get() == nullptr); + sem = Semaphore::Create(0, -1); + // REQUIRE(sem.get() == nullptr); +} + +TEST_CASE("Wait on Multiple Semaphores", "Semaphore") { + WaitResult all_result; + std::pair any_result; + int previous_count; + std::unique_ptr sem0, sem1; + + // Test Wait all which should fail + sem0 = Semaphore::Create(0, 5); + sem1 = Semaphore::Create(5, 5); + all_result = WaitAll({sem0.get(), sem1.get()}, false, 10ms); + REQUIRE(all_result == WaitResult::kTimeout); + previous_count = -1; + REQUIRE(sem0->Release(1, &previous_count)); + REQUIRE(previous_count == 0); + previous_count = -1; + REQUIRE_FALSE(sem1->Release(1, &previous_count)); + REQUIRE(previous_count == -1); + + // Test Wait all again which should succeed + sem0 = Semaphore::Create(1, 5); + sem1 = Semaphore::Create(5, 5); + all_result = WaitAll({sem0.get(), sem1.get()}, false, 10ms); + REQUIRE(all_result == WaitResult::kSuccess); + previous_count = -1; + REQUIRE(sem0->Release(1, &previous_count)); + REQUIRE(previous_count == 0); + previous_count = -1; + REQUIRE(sem1->Release(1, &previous_count)); + REQUIRE(previous_count == 4); + + // Test Wait Any which should fail + sem0 = Semaphore::Create(0, 5); + sem1 = Semaphore::Create(0, 5); + any_result = WaitAny({sem0.get(), sem1.get()}, false, 10ms); + REQUIRE(any_result.first == WaitResult::kTimeout); + REQUIRE(any_result.second == 0); + previous_count = -1; + REQUIRE(sem0->Release(1, &previous_count)); + REQUIRE(previous_count == 0); + previous_count = -1; + REQUIRE(sem1->Release(1, &previous_count)); + REQUIRE(previous_count == 0); + + // Test Wait Any which should succeed + sem0 = Semaphore::Create(0, 5); + sem1 = Semaphore::Create(5, 5); + any_result = WaitAny({sem0.get(), sem1.get()}, false, 10ms); + REQUIRE(any_result.first == WaitResult::kSuccess); + REQUIRE(any_result.second == 1); + previous_count = -1; + REQUIRE(sem0->Release(1, &previous_count)); + REQUIRE(previous_count == 0); + previous_count = -1; + REQUIRE(sem1->Release(1, &previous_count)); + REQUIRE(previous_count == 4); } TEST_CASE("Wait on Mutant", "Mutant") { diff --git a/src/xenia/base/threading_posix.cc b/src/xenia/base/threading_posix.cc index 8d69979c7..874f1bee9 100644 --- a/src/xenia/base/threading_posix.cc +++ b/src/xenia/base/threading_posix.cc @@ -286,6 +286,33 @@ class PosixCondition : public PosixConditionBase { const bool manual_reset_; }; +template <> +class PosixCondition : public PosixConditionBase { + public: + PosixCondition(uint32_t initial_count, uint32_t maximum_count) + : count_(initial_count), maximum_count_(maximum_count) {} + + bool Release(uint32_t release_count, int* out_previous_count) { + if (maximum_count_ - count_ >= release_count) { + auto lock = std::unique_lock(mutex_); + if (out_previous_count) *out_previous_count = count_; + count_ += release_count; + cond_.notify_all(); + return true; + } + return false; + } + + private: + inline bool signaled() const override { return count_ > 0; } + inline void post_execution() override { + count_--; + cond_.notify_all(); + } + uint32_t count_; + const uint32_t maximum_count_; +}; + // Native posix thread handle template class PosixThreadHandle : public T { @@ -307,6 +334,7 @@ template class PosixConditionHandle : public T { public: PosixConditionHandle(bool manual_reset, bool initial_state); + PosixConditionHandle(uint32_t initial_count, uint32_t maximum_count); ~PosixConditionHandle() override = default; protected: @@ -318,9 +346,9 @@ class PosixConditionHandle : public T { }; template <> -PosixConditionHandle::PosixConditionHandle(bool manual_reset, - bool initial_state) - : handle_() {} +PosixConditionHandle::PosixConditionHandle(uint32_t initial_count, + uint32_t maximum_count) + : handle_(initial_count, maximum_count) {} template <> PosixConditionHandle::PosixConditionHandle(bool manual_reset, @@ -390,17 +418,18 @@ std::unique_ptr Event::CreateAutoResetEvent(bool initial_state) { return std::make_unique(false, initial_state); } -// TODO(dougvj) class PosixSemaphore : public PosixConditionHandle { public: PosixSemaphore(int initial_count, int maximum_count) - : PosixConditionHandle(false, false) { - assert_always(); - } + : PosixConditionHandle(static_cast(initial_count), + static_cast(maximum_count)) {} ~PosixSemaphore() override = default; bool Release(int release_count, int* out_previous_count) override { - assert_always(); - return false; + if (release_count < 1) { + return false; + } + return handle_.Release(static_cast(release_count), + out_previous_count); } }; From 996d62953849b4f4cd484673e9a27c34dd0e31ac Mon Sep 17 00:00:00 2001 From: Sandy Carter Date: Sun, 9 Dec 2018 15:44:44 -0800 Subject: [PATCH 11/24] [threading linux] Implement Mutant Keep track of recursive locks with owner and count of locks. Only allow recursive locks from same thread and increment count. Only allow first locks from when count is zero. Test acquiring and releasing mutant on same and on different threads. Test Release return values. Test WaitAll and WaitAny. --- src/xenia/base/testing/threading_test.cc | 119 ++++++++++++++++++++++- src/xenia/base/threading_posix.cc | 53 +++++++--- 2 files changed, 158 insertions(+), 14 deletions(-) diff --git a/src/xenia/base/testing/threading_test.cc b/src/xenia/base/testing/threading_test.cc index 2a28890c5..c3b5c26c4 100644 --- a/src/xenia/base/testing/threading_test.cc +++ b/src/xenia/base/testing/threading_test.cc @@ -433,8 +433,123 @@ TEST_CASE("Wait on Multiple Semaphores", "Semaphore") { } TEST_CASE("Wait on Mutant", "Mutant") { - // TODO(bwrsandman): - REQUIRE(true); + WaitResult result; + std::unique_ptr mut; + + // Release on initially owned mutant + mut = Mutant::Create(true); + REQUIRE(mut->Release()); + REQUIRE_FALSE(mut->Release()); + + // Release on initially not-owned mutant + mut = Mutant::Create(false); + REQUIRE_FALSE(mut->Release()); + + // Wait on initially owned mutant + mut = Mutant::Create(true); + result = Wait(mut.get(), false, 1ms); + REQUIRE(result == WaitResult::kSuccess); + REQUIRE(mut->Release()); + REQUIRE(mut->Release()); + REQUIRE_FALSE(mut->Release()); + + // Wait on initially not owned mutant + mut = Mutant::Create(false); + result = Wait(mut.get(), false, 1ms); + REQUIRE(result == WaitResult::kSuccess); + REQUIRE(mut->Release()); + REQUIRE_FALSE(mut->Release()); + + // Multiple waits (or locks) + mut = Mutant::Create(false); + for (int i = 0; i < 10; ++i) { + result = Wait(mut.get(), false, 1ms); + REQUIRE(result == WaitResult::kSuccess); + } + for (int i = 0; i < 10; ++i) { + REQUIRE(mut->Release()); + } + REQUIRE_FALSE(mut->Release()); + + // Test mutants on other threads + auto thread1 = std::thread([&mut] { + Sleep(5ms); + mut = Mutant::Create(true); + Sleep(100ms); + mut->Release(); + }); + Sleep(10ms); + REQUIRE_FALSE(mut->Release()); + Sleep(10ms); + result = Wait(mut.get(), false, 50ms); + REQUIRE(result == WaitResult::kTimeout); + thread1.join(); + result = Wait(mut.get(), false, 1ms); + REQUIRE(result == WaitResult::kSuccess); + REQUIRE(mut->Release()); +} + +TEST_CASE("Wait on Multiple Mutants", "Mutant") { + WaitResult all_result; + std::pair any_result; + std::unique_ptr mut0, mut1; + + // Test which should fail for WaitAll and WaitAny + auto thread0 = std::thread([&mut0, &mut1] { + mut0 = Mutant::Create(true); + mut1 = Mutant::Create(true); + Sleep(50ms); + mut0->Release(); + mut1->Release(); + }); + Sleep(10ms); + all_result = WaitAll({mut0.get(), mut1.get()}, false, 10ms); + REQUIRE(all_result == WaitResult::kTimeout); + REQUIRE_FALSE(mut0->Release()); + REQUIRE_FALSE(mut1->Release()); + any_result = WaitAny({mut0.get(), mut1.get()}, false, 10ms); + REQUIRE(any_result.first == WaitResult::kTimeout); + REQUIRE(any_result.second == 0); + REQUIRE_FALSE(mut0->Release()); + REQUIRE_FALSE(mut1->Release()); + thread0.join(); + + // Test which should fail for WaitAll but not WaitAny + auto thread1 = std::thread([&mut0, &mut1] { + mut0 = Mutant::Create(true); + mut1 = Mutant::Create(false); + Sleep(50ms); + mut0->Release(); + }); + Sleep(10ms); + all_result = WaitAll({mut0.get(), mut1.get()}, false, 10ms); + REQUIRE(all_result == WaitResult::kTimeout); + REQUIRE_FALSE(mut0->Release()); + REQUIRE_FALSE(mut1->Release()); + any_result = WaitAny({mut0.get(), mut1.get()}, false, 10ms); + REQUIRE(any_result.first == WaitResult::kSuccess); + REQUIRE(any_result.second == 1); + REQUIRE_FALSE(mut0->Release()); + REQUIRE(mut1->Release()); + thread1.join(); + + // Test which should pass for WaitAll and WaitAny + auto thread2 = std::thread([&mut0, &mut1] { + mut0 = Mutant::Create(false); + mut1 = Mutant::Create(false); + Sleep(50ms); + }); + Sleep(10ms); + all_result = WaitAll({mut0.get(), mut1.get()}, false, 10ms); + REQUIRE(all_result == WaitResult::kSuccess); + REQUIRE(mut0->Release()); + REQUIRE(mut1->Release()); + any_result = WaitAny({mut0.get(), mut1.get()}, false, 10ms); + REQUIRE(any_result.first == WaitResult::kSuccess); + REQUIRE(any_result.second == 0); + REQUIRE(mut0->Release()); + REQUIRE_FALSE(mut1->Release()); + thread2.join(); } TEST_CASE("Create and Trigger Timer", "Timer") { diff --git a/src/xenia/base/threading_posix.cc b/src/xenia/base/threading_posix.cc index 874f1bee9..9449286fd 100644 --- a/src/xenia/base/threading_posix.cc +++ b/src/xenia/base/threading_posix.cc @@ -313,6 +313,40 @@ class PosixCondition : public PosixConditionBase { const uint32_t maximum_count_; }; +template <> +class PosixCondition : public PosixConditionBase { + public: + explicit PosixCondition(bool initial_owner) : count_(0) { + if (initial_owner) { + count_ = 1; + owner_ = std::this_thread::get_id(); + } + } + bool Release() { + if (owner_ == std::this_thread::get_id() && count_ > 0) { + auto lock = std::unique_lock(mutex_); + --count_; + // Free to be acquired by another thread + if (count_ == 0) { + cond_.notify_one(); + } + return true; + } + return false; + } + + private: + inline bool signaled() const override { + return count_ == 0 || owner_ == std::this_thread::get_id(); + } + inline void post_execution() override { + count_++; + owner_ = std::this_thread::get_id(); + } + uint32_t count_; + std::thread::id owner_; +}; + // Native posix thread handle template class PosixThreadHandle : public T { @@ -333,6 +367,7 @@ class PosixThreadHandle : public T { template class PosixConditionHandle : public T { public: + explicit PosixConditionHandle(bool initial_owner); PosixConditionHandle(bool manual_reset, bool initial_state); PosixConditionHandle(uint32_t initial_count, uint32_t maximum_count); ~PosixConditionHandle() override = default; @@ -351,9 +386,8 @@ PosixConditionHandle::PosixConditionHandle(uint32_t initial_count, : handle_(initial_count, maximum_count) {} template <> -PosixConditionHandle::PosixConditionHandle(bool manual_reset, - bool initial_state) - : handle_() {} +PosixConditionHandle::PosixConditionHandle(bool initial_owner) + : handle_(initial_owner) {} template <> PosixConditionHandle::PosixConditionHandle(bool manual_reset, @@ -438,17 +472,12 @@ std::unique_ptr Semaphore::Create(int initial_count, return std::make_unique(initial_count, maximum_count); } -// TODO(dougvj) class PosixMutant : public PosixConditionHandle { public: - PosixMutant(bool initial_owner) : PosixConditionHandle(false, false) { - assert_always(); - } - ~PosixMutant() = default; - bool Release() override { - assert_always(); - return false; - } + explicit PosixMutant(bool initial_owner) + : PosixConditionHandle(initial_owner) {} + ~PosixMutant() override = default; + bool Release() override { return handle_.Release(); } }; std::unique_ptr Mutant::Create(bool initial_owner) { From fb3533ec45b5bf1bf3338a5330d5f8f9602afc49 Mon Sep 17 00:00:00 2001 From: Sandy Carter Date: Sun, 9 Dec 2018 18:02:36 -0800 Subject: [PATCH 12/24] [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. --- .gdbinit | 2 + src/xenia/base/testing/threading_test.cc | 108 +++++++++++++++++++++- src/xenia/base/threading.h | 8 +- src/xenia/base/threading_posix.cc | 112 +++++++++++++++++++---- 4 files changed, 206 insertions(+), 24 deletions(-) diff --git a/.gdbinit b/.gdbinit index 872fae6b0..f54495075 100644 --- a/.gdbinit +++ b/.gdbinit @@ -1,2 +1,4 @@ # Ignore HighResolutionTimer custom event handle SIG34 nostop noprint +# Ignore PosixTimer custom event +handle SIG35 nostop noprint diff --git a/src/xenia/base/testing/threading_test.cc b/src/xenia/base/testing/threading_test.cc index c3b5c26c4..7b7dd0302 100644 --- a/src/xenia/base/testing/threading_test.cc +++ b/src/xenia/base/testing/threading_test.cc @@ -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; + + // 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 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); } diff --git a/src/xenia/base/threading.h b/src/xenia/base/threading.h index cd8dae288..745c1bbd4 100644 --- a/src/xenia/base/threading.h +++ b/src/xenia/base/threading.h @@ -305,12 +305,12 @@ class Timer : public WaitHandle { std::chrono::milliseconds period, std::function opt_callback = nullptr) = 0; template - void SetRepeating(std::chrono::nanoseconds due_time, + bool SetRepeating(std::chrono::nanoseconds due_time, std::chrono::duration period, std::function opt_callback = nullptr) { - SetRepeating(due_time, - std::chrono::duration_cast(period), - std::move(opt_callback)); + return SetRepeating( + due_time, std::chrono::duration_cast(period), + std::move(opt_callback)); } // Stops the timer before it can be set to the signaled state and cancels diff --git a/src/xenia/base/threading_posix.cc b/src/xenia/base/threading_posix.cc index 9449286fd..e5a204e2c 100644 --- a/src/xenia/base/threading_posix.cc +++ b/src/xenia/base/threading_posix.cc @@ -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(num); @@ -347,6 +347,82 @@ class PosixCondition : public PosixConditionBase { std::thread::id owner_; }; +template <> +class PosixCondition : 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 opt_callback = nullptr) { + std::lock_guard 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 callback; + { + std::lock_guard 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 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 callback_; + timer_t timer_; + volatile bool signal_; + const bool manual_reset_; +}; + // Native posix thread handle template class PosixThreadHandle : public T { @@ -367,7 +443,7 @@ class PosixThreadHandle : public T { template 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; @@ -390,9 +466,8 @@ PosixConditionHandle::PosixConditionHandle(bool initial_owner) : handle_(initial_owner) {} template <> -PosixConditionHandle::PosixConditionHandle(bool manual_reset, - bool initial_state) - : handle_() {} +PosixConditionHandle::PosixConditionHandle(bool manual_reset) + : handle_(manual_reset) {} template <> PosixConditionHandle::PosixConditionHandle(bool manual_reset, @@ -484,35 +559,30 @@ std::unique_ptr Mutant::Create(bool initial_owner) { return std::make_unique(initial_owner); } -// TODO(dougvj) class PosixTimer : public PosixConditionHandle { 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 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 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::CreateManualResetTimer() { + install_signal_handler(SignalType::kTimer); return std::make_unique(true); } std::unique_ptr Timer::CreateSynchronizationTimer() { + install_signal_handler(SignalType::kTimer); return std::make_unique(false); } @@ -624,6 +694,12 @@ static void signal_handler(int signal, siginfo_t* info, void* /*context*/) { *static_cast*>(info->si_value.sival_ptr); callback(); } break; + case SignalType::kTimer: { + assert_not_null(info->si_value.sival_ptr); + auto pTimer = + static_cast*>(info->si_value.sival_ptr); + pTimer->CompletionRoutine(); + } break; default: assert_always(); } From 82cbb11ff2fcfadaf7a5636aeefb666347cb5193 Mon Sep 17 00:00:00 2001 From: Sandy Carter Date: Mon, 3 Dec 2018 14:28:11 -0800 Subject: [PATCH 13/24] [threading linux] Implement basic Thread function Add Basic Tests on Threads --- .gdbinit | 2 + src/xenia/base/testing/threading_test.cc | 82 +++++- src/xenia/base/threading.h | 2 +- src/xenia/base/threading_posix.cc | 316 +++++++++++++++++------ 4 files changed, 325 insertions(+), 77 deletions(-) diff --git a/.gdbinit b/.gdbinit index f54495075..3aaf134d2 100644 --- a/.gdbinit +++ b/.gdbinit @@ -2,3 +2,5 @@ handle SIG34 nostop noprint # Ignore PosixTimer custom event handle SIG35 nostop noprint +# Ignore PosixThread exit event +handle SIG32 nostop noprint diff --git a/src/xenia/base/testing/threading_test.cc b/src/xenia/base/testing/threading_test.cc index 7b7dd0302..ee88788a0 100644 --- a/src/xenia/base/testing/threading_test.cc +++ b/src/xenia/base/testing/threading_test.cc @@ -683,12 +683,90 @@ TEST_CASE("Set and Test Current Thread ID", "Thread") { } TEST_CASE("Set and Test Current Thread Name", "Thread") { + auto current_thread = Thread::GetCurrentThread(); + REQUIRE(current_thread); + auto old_thread_name = current_thread->name(); + std::string new_thread_name = "Threading Test"; - set_name(new_thread_name); + REQUIRE_NOTHROW(set_name(new_thread_name)); + + // Restore the old catch.hpp thread name + REQUIRE_NOTHROW(set_name(old_thread_name)); } TEST_CASE("Create and Run Thread", "Thread") { - // TODO(bwrsandman): + std::unique_ptr thread; + WaitResult result; + Thread::CreationParameters params = {}; + auto func = [] { Sleep(20ms); }; + + // Create most basic case of thread + thread = Thread::Create(params, func); + REQUIRE(thread->native_handle() != nullptr); + REQUIRE_NOTHROW(thread->affinity_mask()); + REQUIRE(thread->name().empty()); + result = Wait(thread.get(), false, 50ms); + REQUIRE(result == WaitResult::kSuccess); + + // Add thread name + std::string new_name = "Test thread name"; + thread = Thread::Create(params, func); + auto name = thread->name(); + INFO(name.c_str()); + REQUIRE(name.empty()); + thread->set_name(new_name); + REQUIRE(thread->name() == new_name); + result = Wait(thread.get(), false, 50ms); + REQUIRE(result == WaitResult::kSuccess); + + // Use Terminate to end an infinitely looping thread + thread = Thread::Create(params, [] { + while (true) { + Sleep(1ms); + } + }); + result = Wait(thread.get(), false, 50ms); + REQUIRE(result == WaitResult::kTimeout); + thread->Terminate(-1); + result = Wait(thread.get(), false, 50ms); + REQUIRE(result == WaitResult::kSuccess); + + // Call Exit from inside an infinitely looping thread + thread = Thread::Create(params, [] { + while (true) { + Thread::Exit(-1); + } + }); + result = Wait(thread.get(), false, 50ms); + REQUIRE(result == WaitResult::kSuccess); + + // Call timeout wait on self + result = Wait(Thread::GetCurrentThread(), false, 50ms); + REQUIRE(result == WaitResult::kTimeout); + + params.stack_size = 16 * 1024; + thread = Thread::Create(params, [] { + while (true) { + Thread::Exit(-1); + } + }); + REQUIRE(thread != nullptr); + result = Wait(thread.get(), false, 50ms); + REQUIRE(result == WaitResult::kSuccess); + + // TODO(bwrsandman): Test with different priorities + // TODO(bwrsandman): Test setting and getting thread affinity +} + +TEST_CASE("Test Suspending Thread", "Thread") { + // TODO(bwrsandman): Test suspension and resume + REQUIRE(true); +} + +TEST_CASE("Test Thread QueueUserCallback", "Thread") { + // TODO(bwrsandman): Test Exit command with QueueUserCallback + // TODO(bwrsandman): Test alertable wait returning kUserCallback by using IO + // callbacks. REQUIRE(true); } diff --git a/src/xenia/base/threading.h b/src/xenia/base/threading.h index 745c1bbd4..7621c297f 100644 --- a/src/xenia/base/threading.h +++ b/src/xenia/base/threading.h @@ -356,7 +356,7 @@ class Thread : public WaitHandle { virtual uint32_t system_id() const = 0; // Returns the current name of the thread, if previously specified. - std::string name() const { return name_; } + virtual std::string name() const { return name_; } // Sets the name of the thread, used in debugging and logging. virtual void set_name(std::string name) { name_ = std::move(name); } diff --git a/src/xenia/base/threading_posix.cc b/src/xenia/base/threading_posix.cc index e5a204e2c..c91cdff01 100644 --- a/src/xenia/base/threading_posix.cc +++ b/src/xenia/base/threading_posix.cc @@ -20,6 +20,7 @@ #include #include #include +#include namespace xe { namespace threading { @@ -74,11 +75,12 @@ uint32_t current_thread_system_id() { } void set_name(const std::string& name) { - pthread_setname_np(pthread_self(), name.c_str()); + set_name(static_cast(pthread_self()), name); } void set_name(std::thread::native_handle_type handle, const std::string& name) { - pthread_setname_np(handle, name.c_str()); + assert_false(name.length() >= 16); + if (pthread_setname_np(handle, name.c_str()) != 0) assert_always(); } void MaybeYield() { @@ -423,19 +425,160 @@ class PosixCondition : public PosixConditionBase { const bool manual_reset_; }; -// Native posix thread handle -template -class PosixThreadHandle : public T { - public: - explicit PosixThreadHandle(pthread_t handle) : handle_(handle) {} - ~PosixThreadHandle() override {} +struct ThreadStartData { + std::function start_routine; + Thread* thread_obj; +}; - protected: - void* native_handle() const override { - return reinterpret_cast(handle_); +template <> +class PosixCondition : public PosixConditionBase { + public: + PosixCondition() : thread_(0), signaled_(false), exit_code_(0) {} + bool Initialize(Thread::CreationParameters params, + ThreadStartData* start_data) { + assert_false(params.create_suspended); + pthread_attr_t attr; + if (pthread_attr_init(&attr) != 0) return false; + if (pthread_attr_setstacksize(&attr, params.stack_size) != 0) { + pthread_attr_destroy(&attr); + return false; + } + if (params.initial_priority != 0) { + sched_param sched{}; + sched.sched_priority = params.initial_priority + 1; + if (pthread_attr_setschedpolicy(&attr, SCHED_FIFO) != 0) { + pthread_attr_destroy(&attr); + return false; + } + if (pthread_attr_setschedparam(&attr, &sched) != 0) { + pthread_attr_destroy(&attr); + return false; + } + } + if (pthread_create(&thread_, &attr, ThreadStartRoutine, start_data) != 0) { + return false; + } + pthread_attr_destroy(&attr); + return true; } - pthread_t handle_; + /// Constructor for existing thread. This should only happen once called by + /// Thread::GetCurrentThread() on the main thread + explicit PosixCondition(pthread_t thread) + : thread_(thread), signaled_(false), exit_code_(0) {} + + virtual ~PosixCondition() { + if (thread_ && !signaled_) { + if (pthread_cancel(thread_) != 0) { + assert_always(); + } + if (pthread_join(thread_, nullptr) != 0) { + assert_always(); + } + } + } + + std::string name() const { + auto result = std::array{'\0'}; + if (pthread_getname_np(thread_, result.data(), result.size() - 1) != 0) + assert_always(); + return std::string(result.data()); + } + + void set_name(const std::string& name) { + threading::set_name(static_cast(thread_), + name); + } + + uint32_t system_id() const { return static_cast(thread_); } + + uint64_t affinity_mask() { + cpu_set_t cpu_set; + if (pthread_getaffinity_np(thread_, sizeof(cpu_set_t), &cpu_set) != 0) + assert_always(); + uint64_t result = 0; + auto cpu_count = std::min(CPU_SETSIZE, 64); + for (auto i = 0u; i < cpu_count; i++) { + auto set = CPU_ISSET(i, &cpu_set); + result |= set << i; + } + return result; + } + + void set_affinity_mask(uint64_t mask) { + cpu_set_t cpu_set; + CPU_ZERO(&cpu_set); + for (auto i = 0u; i < 64; i++) { + if (mask & (1 << i)) { + CPU_SET(i, &cpu_set); + } + } + if (pthread_setaffinity_np(thread_, sizeof(cpu_set_t), &cpu_set) != 0) { + assert_always(); + } + } + + int priority() { + int policy; + sched_param param{}; + int ret = pthread_getschedparam(thread_, &policy, ¶m); + if (ret != 0) { + return -1; + } + + return param.sched_priority; + } + + void set_priority(int new_priority) { + sched_param param{}; + param.sched_priority = new_priority; + if (pthread_setschedparam(thread_, SCHED_FIFO, ¶m) != 0) + assert_always(); + } + + void QueueUserCallback(std::function callback) { + // TODO(bwrsandman) + assert_always(); + } + + bool Resume(uint32_t* out_new_suspend_count = nullptr) { + // TODO(bwrsandman) + assert_always(); + return false; + } + + bool Suspend(uint32_t* out_previous_suspend_count = nullptr) { + // TODO(bwrsandman) + assert_always(); + return false; + } + + void Terminate(int exit_code) { + std::lock_guard lock(mutex_); + + // Sometimes the thread can call terminate twice before stopping + if (thread_ == 0) return; + auto thread = thread_; + + exit_code_ = exit_code; + signaled_ = true; + cond_.notify_all(); + + if (pthread_cancel(thread) != 0) assert_always(); + } + + private: + static void* ThreadStartRoutine(void* parameter); + inline bool signaled() const override { return signaled_; } + inline void post_execution() override { + if (thread_) { + pthread_join(thread_, nullptr); + thread_ = 0; + } + } + pthread_t thread_; + bool signaled_; + int exit_code_; }; // This wraps a condition object as our handle because posix has no single @@ -443,7 +586,9 @@ class PosixThreadHandle : public T { template class PosixConditionHandle : public T { public: + PosixConditionHandle() = default; explicit PosixConditionHandle(bool); + explicit PosixConditionHandle(pthread_t thread); PosixConditionHandle(bool manual_reset, bool initial_state); PosixConditionHandle(uint32_t initial_count, uint32_t maximum_count); ~PosixConditionHandle() override = default; @@ -454,6 +599,7 @@ class PosixConditionHandle : public T { } PosixCondition handle_; + friend PosixCondition; }; template <> @@ -474,6 +620,10 @@ PosixConditionHandle::PosixConditionHandle(bool manual_reset, bool initial_state) : handle_(manual_reset, initial_state) {} +template <> +PosixConditionHandle::PosixConditionHandle(pthread_t thread) + : handle_(thread) {} + WaitResult Wait(WaitHandle* wait_handle, bool is_alertable, std::chrono::milliseconds timeout) { auto handle = @@ -586,104 +736,122 @@ std::unique_ptr Timer::CreateSynchronizationTimer() { return std::make_unique(false); } -class PosixThread : public PosixThreadHandle { +class PosixThread : public PosixConditionHandle { public: - explicit PosixThread(pthread_t handle) : PosixThreadHandle(handle) {} - ~PosixThread() = default; + PosixThread() = default; + explicit PosixThread(pthread_t thread) : PosixConditionHandle(thread) {} + ~PosixThread() override = default; + + bool Initialize(CreationParameters params, + std::function start_routine) { + auto start_data = new ThreadStartData({std::move(start_routine), this}); + return handle_.Initialize(params, start_data); + } + + std::string name() const override { + auto result = Thread::name(); + if (result.empty()) { + result = handle_.name(); + } + return result; + } void set_name(std::string name) override { - pthread_setname_np(handle_, name.c_str()); - } - - uint32_t system_id() const override { return 0; } - - // TODO(DrChat) - uint64_t affinity_mask() override { return 0; } - void set_affinity_mask(uint64_t mask) override { assert_always(); } - - int priority() override { - int policy; - struct sched_param param; - int ret = pthread_getschedparam(handle_, &policy, ¶m); - if (ret != 0) { - return -1; + Thread::set_name(name); + if (name.length() > 15) { + name = name.substr(0, 15); } - - return param.sched_priority; + handle_.set_name(name); } + uint32_t system_id() const override { return handle_.system_id(); } + + uint64_t affinity_mask() override { return handle_.affinity_mask(); } + void set_affinity_mask(uint64_t mask) override { + handle_.set_affinity_mask(mask); + } + + int priority() override { return handle_.priority(); } void set_priority(int new_priority) override { - struct sched_param param; - param.sched_priority = new_priority; - int ret = pthread_setschedparam(handle_, SCHED_FIFO, ¶m); + handle_.set_priority(new_priority); } - // TODO(DrChat) void QueueUserCallback(std::function callback) override { - assert_always(); + handle_.QueueUserCallback(std::move(callback)); } - bool Resume(uint32_t* out_new_suspend_count = nullptr) override { - assert_always(); - return false; + bool Resume(uint32_t* out_new_suspend_count) override { + return handle_.Resume(out_new_suspend_count); } - bool Suspend(uint32_t* out_previous_suspend_count = nullptr) override { - assert_always(); - return false; + bool Suspend(uint32_t* out_previous_suspend_count) override { + return handle_.Suspend(out_previous_suspend_count); } - void Terminate(int exit_code) override {} + void Terminate(int exit_code) override { handle_.Terminate(exit_code); } }; -thread_local std::unique_ptr current_thread_ = nullptr; +thread_local PosixThread* current_thread_ = nullptr; -struct ThreadStartData { - std::function start_routine; -}; -void* ThreadStartRoutine(void* parameter) { - current_thread_ = - std::unique_ptr(new PosixThread(::pthread_self())); +void* PosixCondition::ThreadStartRoutine(void* parameter) { + if (pthread_setcanceltype(PTHREAD_CANCEL_ASYNCHRONOUS, nullptr) != 0) { + assert_always(); + } + threading::set_name(""); - auto start_data = reinterpret_cast(parameter); - start_data->start_routine(); + auto start_data = static_cast(parameter); + assert_not_null(start_data); + assert_not_null(start_data->thread_obj); + + auto thread = dynamic_cast(start_data->thread_obj); + auto start_routine = std::move(start_data->start_routine); delete start_data; - return 0; + + current_thread_ = thread; + start_routine(); + + std::unique_lock lock(mutex_); + thread->handle_.exit_code_ = 0; + thread->handle_.signaled_ = true; + cond_.notify_all(); + + current_thread_ = nullptr; + return nullptr; } std::unique_ptr Thread::Create(CreationParameters params, std::function start_routine) { - auto start_data = new ThreadStartData({std::move(start_routine)}); - - assert_false(params.create_suspended); - pthread_t handle; - pthread_attr_t attr; - pthread_attr_init(&attr); - int ret = pthread_create(&handle, &attr, ThreadStartRoutine, start_data); - if (ret != 0) { - // TODO(benvanik): pass back? - auto last_error = errno; - XELOGE("Unable to pthread_create: %d", last_error); - delete start_data; - return nullptr; - } - - return std::unique_ptr(new PosixThread(handle)); + auto thread = std::make_unique(); + if (!thread->Initialize(params, std::move(start_routine))) return nullptr; + assert_not_null(thread); + return thread; } Thread* Thread::GetCurrentThread() { if (current_thread_) { - return current_thread_.get(); + return current_thread_; } + // Should take this route only for threads not created by Thread::Create. + // The only thread not created by Thread::Create should be the main thread. pthread_t handle = pthread_self(); - current_thread_ = std::make_unique(handle); - return current_thread_.get(); + current_thread_ = new PosixThread(handle); + atexit([] { delete current_thread_; }); + + return current_thread_; } void Thread::Exit(int exit_code) { - pthread_exit(reinterpret_cast(exit_code)); + if (current_thread_) { + current_thread_->Terminate(exit_code); + // Sometimes the current thread keeps running after being cancelled. + // Prevent other calls from this thread from using current_thread_. + current_thread_ = nullptr; + } else { + // Should only happen with the main thread + pthread_exit(reinterpret_cast(exit_code)); + } } static void signal_handler(int signal, siginfo_t* info, void* /*context*/) { From c02f0e9219baeb23e73e56db41a1f97ef316c6a5 Mon Sep 17 00:00:00 2001 From: Sandy Carter Date: Fri, 11 Jan 2019 15:36:42 -0500 Subject: [PATCH 14/24] [threading linux] Wait for thread start --- src/xenia/base/threading_posix.cc | 64 ++++++++++++++++++++++++++++--- 1 file changed, 58 insertions(+), 6 deletions(-) diff --git a/src/xenia/base/threading_posix.cc b/src/xenia/base/threading_posix.cc index c91cdff01..d34b32769 100644 --- a/src/xenia/base/threading_posix.cc +++ b/src/xenia/base/threading_posix.cc @@ -432,8 +432,18 @@ struct ThreadStartData { template <> class PosixCondition : public PosixConditionBase { + enum class State { + kUninitialized, + kRunning, + kFinished, + }; + public: - PosixCondition() : thread_(0), signaled_(false), exit_code_(0) {} + PosixCondition() + : thread_(0), + signaled_(false), + exit_code_(0), + state_(State::kUninitialized) {} bool Initialize(Thread::CreationParameters params, ThreadStartData* start_data) { assert_false(params.create_suspended); @@ -465,7 +475,10 @@ class PosixCondition : public PosixConditionBase { /// Constructor for existing thread. This should only happen once called by /// Thread::GetCurrentThread() on the main thread explicit PosixCondition(pthread_t thread) - : thread_(thread), signaled_(false), exit_code_(0) {} + : thread_(thread), + signaled_(false), + exit_code_(0), + state_(State::kRunning) {} virtual ~PosixCondition() { if (thread_ && !signaled_) { @@ -479,20 +492,29 @@ class PosixCondition : public PosixConditionBase { } std::string name() const { + WaitStarted(); auto result = std::array{'\0'}; - if (pthread_getname_np(thread_, result.data(), result.size() - 1) != 0) - assert_always(); + std::unique_lock lock(state_mutex_); + if (state_ == State::kRunning) { + if (pthread_getname_np(thread_, result.data(), result.size() - 1) != 0) + assert_always(); + } return std::string(result.data()); } void set_name(const std::string& name) { - threading::set_name(static_cast(thread_), - name); + WaitStarted(); + std::unique_lock lock(state_mutex_); + if (state_ == State::kRunning) { + threading::set_name(static_cast(thread_), + name); + } } uint32_t system_id() const { return static_cast(thread_); } uint64_t affinity_mask() { + WaitStarted(); cpu_set_t cpu_set; if (pthread_getaffinity_np(thread_, sizeof(cpu_set_t), &cpu_set) != 0) assert_always(); @@ -506,6 +528,7 @@ class PosixCondition : public PosixConditionBase { } void set_affinity_mask(uint64_t mask) { + WaitStarted(); cpu_set_t cpu_set; CPU_ZERO(&cpu_set); for (auto i = 0u; i < 64; i++) { @@ -519,6 +542,7 @@ class PosixCondition : public PosixConditionBase { } int priority() { + WaitStarted(); int policy; sched_param param{}; int ret = pthread_getschedparam(thread_, &policy, ¶m); @@ -530,6 +554,7 @@ class PosixCondition : public PosixConditionBase { } void set_priority(int new_priority) { + WaitStarted(); sched_param param{}; param.sched_priority = new_priority; if (pthread_setschedparam(thread_, SCHED_FIFO, ¶m) != 0) @@ -554,6 +579,11 @@ class PosixCondition : public PosixConditionBase { } void Terminate(int exit_code) { + { + std::unique_lock lock(state_mutex_); + state_ = State::kFinished; + } + std::lock_guard lock(mutex_); // Sometimes the thread can call terminate twice before stopping @@ -567,6 +597,12 @@ class PosixCondition : public PosixConditionBase { if (pthread_cancel(thread) != 0) assert_always(); } + void WaitStarted() const { + std::unique_lock lock(state_mutex_); + state_signal_.wait(lock, + [this] { return state_ != State::kUninitialized; }); + } + private: static void* ThreadStartRoutine(void* parameter); inline bool signaled() const override { return signaled_; } @@ -579,6 +615,9 @@ class PosixCondition : public PosixConditionBase { pthread_t thread_; bool signaled_; int exit_code_; + State state_; + mutable std::mutex state_mutex_; + mutable std::condition_variable state_signal_; }; // This wraps a condition object as our handle because posix has no single @@ -749,6 +788,7 @@ class PosixThread : public PosixConditionHandle { } std::string name() const override { + handle_.WaitStarted(); auto result = Thread::name(); if (result.empty()) { result = handle_.name(); @@ -757,6 +797,7 @@ class PosixThread : public PosixConditionHandle { } void set_name(std::string name) override { + handle_.WaitStarted(); Thread::set_name(name); if (name.length() > 15) { name = name.substr(0, 15); @@ -808,8 +849,19 @@ void* PosixCondition::ThreadStartRoutine(void* parameter) { delete start_data; current_thread_ = thread; + { + std::unique_lock lock(thread->handle_.state_mutex_); + thread->handle_.state_ = State::kRunning; + thread->handle_.state_signal_.notify_all(); + } + start_routine(); + { + std::unique_lock lock(thread->handle_.state_mutex_); + thread->handle_.state_ = State::kFinished; + } + std::unique_lock lock(mutex_); thread->handle_.exit_code_ = 0; thread->handle_.signaled_ = true; From 00f223e73706fe1e3f408ccb8292062e2be6e433 Mon Sep 17 00:00:00 2001 From: Sandy Carter Date: Fri, 11 Jan 2019 14:47:59 -0500 Subject: [PATCH 15/24] [threading linux] Implement suspendable pthreads Use real-time event interrupt to communicate suspend in timely manner. Use conditional_variable to implement suspend wait and resume trigger. Ignore real-time event 36 in .gdbinit which is used to signal suspend. Test suspending threads. --- .gdbinit | 2 + src/xenia/base/testing/threading_test.cc | 25 ++++++++++- src/xenia/base/threading_posix.cc | 55 +++++++++++++++++++----- 3 files changed, 69 insertions(+), 13 deletions(-) diff --git a/.gdbinit b/.gdbinit index 3aaf134d2..68d6baa21 100644 --- a/.gdbinit +++ b/.gdbinit @@ -4,3 +4,5 @@ handle SIG34 nostop noprint handle SIG35 nostop noprint # Ignore PosixThread exit event handle SIG32 nostop noprint +# Ignore PosixThread suspend event +handle SIG36 nostop noprint diff --git a/src/xenia/base/testing/threading_test.cc b/src/xenia/base/testing/threading_test.cc index ee88788a0..50dd79baa 100644 --- a/src/xenia/base/testing/threading_test.cc +++ b/src/xenia/base/testing/threading_test.cc @@ -759,8 +759,29 @@ TEST_CASE("Create and Run Thread", "Thread") { } TEST_CASE("Test Suspending Thread", "Thread") { - // TODO(bwrsandman): Test suspension and resume - REQUIRE(true); + std::unique_ptr thread; + WaitResult result; + Thread::CreationParameters params = {}; + auto func = [] { Sleep(20ms); }; + + // Create initially suspended + params.create_suspended = true; + thread = threading::Thread::Create(params, func); + result = threading::Wait(thread.get(), false, 50ms); + REQUIRE(result == threading::WaitResult::kTimeout); + thread->Resume(); + result = threading::Wait(thread.get(), false, 50ms); + REQUIRE(result == threading::WaitResult::kSuccess); + params.create_suspended = false; + + // Create and then suspend + thread = threading::Thread::Create(params, func); + thread->Suspend(); + result = threading::Wait(thread.get(), false, 50ms); + REQUIRE(result == threading::WaitResult::kTimeout); + thread->Resume(); + result = threading::Wait(thread.get(), false, 50ms); + REQUIRE(result == threading::WaitResult::kSuccess); } TEST_CASE("Test Thread QueueUserCallback", "Thread") { diff --git a/src/xenia/base/threading_posix.cc b/src/xenia/base/threading_posix.cc index d34b32769..653ebde05 100644 --- a/src/xenia/base/threading_posix.cc +++ b/src/xenia/base/threading_posix.cc @@ -38,7 +38,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, kTimer, k_Count }; +enum class SignalType { kHighResolutionTimer, kTimer, kThreadSuspend, k_Count }; int GetSystemSignal(SignalType num) { auto result = SIGRTMIN + static_cast(num); @@ -427,6 +427,7 @@ class PosixCondition : public PosixConditionBase { struct ThreadStartData { std::function start_routine; + bool create_suspended; Thread* thread_obj; }; @@ -435,6 +436,7 @@ class PosixCondition : public PosixConditionBase { enum class State { kUninitialized, kRunning, + kSuspended, kFinished, }; @@ -446,7 +448,7 @@ class PosixCondition : public PosixConditionBase { state_(State::kUninitialized) {} bool Initialize(Thread::CreationParameters params, ThreadStartData* start_data) { - assert_false(params.create_suspended); + start_data->create_suspended = params.create_suspended; pthread_attr_t attr; if (pthread_attr_init(&attr) != 0) return false; if (pthread_attr_setstacksize(&attr, params.stack_size) != 0) { @@ -567,15 +569,23 @@ class PosixCondition : public PosixConditionBase { } bool Resume(uint32_t* out_new_suspend_count = nullptr) { - // TODO(bwrsandman) - assert_always(); - return false; + // TODO(bwrsandman): implement suspend_count + assert_null(out_new_suspend_count); + WaitStarted(); + std::unique_lock lock(state_mutex_); + if (state_ != State::kSuspended) return false; + state_ = State::kRunning; + state_signal_.notify_all(); + return true; } bool Suspend(uint32_t* out_previous_suspend_count = nullptr) { - // TODO(bwrsandman) - assert_always(); - return false; + // TODO(bwrsandman): implement suspend_count + assert_null(out_previous_suspend_count); + WaitStarted(); + int result = + pthread_kill(thread_, GetSystemSignal(SignalType::kThreadSuspend)); + return result == 0; } void Terminate(int exit_code) { @@ -603,6 +613,13 @@ class PosixCondition : public PosixConditionBase { [this] { return state_ != State::kUninitialized; }); } + /// Set state to suspended and wait until it reset by another thread + void WaitSuspended() { + std::unique_lock lock(state_mutex_); + state_ = State::kSuspended; + state_signal_.wait(lock, [this] { return state_ != State::kSuspended; }); + } + private: static void* ThreadStartRoutine(void* parameter); inline bool signaled() const override { return signaled_; } @@ -615,7 +632,7 @@ class PosixCondition : public PosixConditionBase { pthread_t thread_; bool signaled_; int exit_code_; - State state_; + volatile State state_; mutable std::mutex state_mutex_; mutable std::condition_variable state_signal_; }; @@ -783,7 +800,8 @@ class PosixThread : public PosixConditionHandle { bool Initialize(CreationParameters params, std::function start_routine) { - auto start_data = new ThreadStartData({std::move(start_routine), this}); + auto start_data = + new ThreadStartData({std::move(start_routine), false, this}); return handle_.Initialize(params, start_data); } @@ -830,6 +848,8 @@ class PosixThread : public PosixConditionHandle { } void Terminate(int exit_code) override { handle_.Terminate(exit_code); } + + void WaitSuspended() { handle_.WaitSuspended(); } }; thread_local PosixThread* current_thread_ = nullptr; @@ -846,12 +866,20 @@ void* PosixCondition::ThreadStartRoutine(void* parameter) { auto thread = dynamic_cast(start_data->thread_obj); auto start_routine = std::move(start_data->start_routine); + auto create_suspended = start_data->create_suspended; delete start_data; current_thread_ = thread; { std::unique_lock lock(thread->handle_.state_mutex_); - thread->handle_.state_ = State::kRunning; + if (create_suspended) { + thread->handle_.state_ = State::kSuspended; + thread->handle_.state_signal_.wait(lock, [thread] { + return thread->handle_.state_ != State::kSuspended; + }); + } else { + thread->handle_.state_ = State::kRunning; + } thread->handle_.state_signal_.notify_all(); } @@ -873,6 +901,7 @@ void* PosixCondition::ThreadStartRoutine(void* parameter) { std::unique_ptr Thread::Create(CreationParameters params, std::function start_routine) { + install_signal_handler(SignalType::kThreadSuspend); auto thread = std::make_unique(); if (!thread->Initialize(params, std::move(start_routine))) return nullptr; assert_not_null(thread); @@ -920,6 +949,10 @@ static void signal_handler(int signal, siginfo_t* info, void* /*context*/) { static_cast*>(info->si_value.sival_ptr); pTimer->CompletionRoutine(); } break; + case SignalType::kThreadSuspend: { + assert_not_null(current_thread_); + current_thread_->WaitSuspended(); + } break; default: assert_always(); } From 41ba1eb1c11f00d768ed032d7837e6ca81cf66d9 Mon Sep 17 00:00:00 2001 From: Sandy Carter Date: Sun, 11 Mar 2018 14:48:55 -0400 Subject: [PATCH 16/24] [threading linux] Implement Callback Queuing Add thread local bool for alertable state. Use real-time event interrupt to run callback. Fix sleep duration from miliseconds (microseconds / 1000) to seconds in sleep command. Add note for future implementation. Ignore real-time event 37 in .gdbinit which is used to signal callback. Test AlertableSleep Test Thread QueueUserCallback. TODO: Test alerted wait result when using IO functions. --- .gdbinit | 2 + src/xenia/base/testing/threading_test.cc | 82 ++++++++++++++++++++++-- src/xenia/base/threading_posix.cc | 58 ++++++++++++++--- 3 files changed, 128 insertions(+), 14 deletions(-) diff --git a/.gdbinit b/.gdbinit index 68d6baa21..09b4af30f 100644 --- a/.gdbinit +++ b/.gdbinit @@ -6,3 +6,5 @@ handle SIG35 nostop noprint handle SIG32 nostop noprint # Ignore PosixThread suspend event handle SIG36 nostop noprint +# Ignore PosixThread user callback event +handle SIG37 nostop noprint diff --git a/src/xenia/base/testing/threading_test.cc b/src/xenia/base/testing/threading_test.cc index 50dd79baa..714e9de8c 100644 --- a/src/xenia/base/testing/threading_test.cc +++ b/src/xenia/base/testing/threading_test.cc @@ -101,8 +101,15 @@ TEST_CASE("Sleep Current Thread", "Sleep") { } TEST_CASE("Sleep Current Thread in Alertable State", "Sleep") { - // TODO(bwrsandman): - REQUIRE(true); + auto wait_time = 50ms; + auto start = std::chrono::steady_clock::now(); + auto result = threading::AlertableSleep(wait_time); + auto duration = std::chrono::steady_clock::now() - start; + REQUIRE(duration >= wait_time); + REQUIRE(result == threading::SleepResult::kSuccess); + + // TODO(bwrsandman): Test a Thread to return kAlerted. + // Need callback to call extended I/O function (ReadFileEx or WriteFileEx) } TEST_CASE("TlsHandle") { @@ -785,10 +792,77 @@ TEST_CASE("Test Suspending Thread", "Thread") { } TEST_CASE("Test Thread QueueUserCallback", "Thread") { - // TODO(bwrsandman): Test Exit command with QueueUserCallback + std::unique_ptr thread; + WaitResult result; + Thread::CreationParameters params = {}; + std::atomic_int order; + int is_modified; + int has_finished; + auto callback = [&is_modified, &order] { + is_modified = std::atomic_fetch_add_explicit( + &order, 1, std::memory_order::memory_order_relaxed); + }; + + // Without alertable + order = 0; + is_modified = -1; + has_finished = -1; + thread = Thread::Create(params, [&has_finished, &order] { + // Not using Alertable so callback is not registered + Sleep(90ms); + has_finished = std::atomic_fetch_add_explicit( + &order, 1, std::memory_order::memory_order_relaxed); + }); + result = Wait(thread.get(), true, 50ms); + REQUIRE(result == WaitResult::kTimeout); + REQUIRE(is_modified == -1); + thread->QueueUserCallback(callback); + result = Wait(thread.get(), true, 100ms); + REQUIRE(result == WaitResult::kSuccess); + REQUIRE(is_modified == -1); + REQUIRE(has_finished == 0); + + // With alertable + order = 0; + is_modified = -1; + has_finished = -1; + thread = Thread::Create(params, [&has_finished, &order] { + // Using Alertable so callback is registered + AlertableSleep(90ms); + has_finished = std::atomic_fetch_add_explicit( + &order, 1, std::memory_order::memory_order_relaxed); + }); + result = Wait(thread.get(), true, 50ms); + REQUIRE(result == WaitResult::kTimeout); + REQUIRE(is_modified == -1); + thread->QueueUserCallback(callback); + result = Wait(thread.get(), true, 100ms); + REQUIRE(result == WaitResult::kSuccess); + REQUIRE(is_modified == 0); + REQUIRE(has_finished == 1); + + // Test Exit command with QueueUserCallback + order = 0; + is_modified = -1; + has_finished = -1; + thread = Thread::Create(params, [&is_modified, &has_finished, &order] { + is_modified = std::atomic_fetch_add_explicit( + &order, 1, std::memory_order::memory_order_relaxed); + // Using Alertable so callback is registered + AlertableSleep(200ms); + has_finished = std::atomic_fetch_add_explicit( + &order, 1, std::memory_order::memory_order_relaxed); + }); + result = Wait(thread.get(), true, 100ms); + REQUIRE(result == WaitResult::kTimeout); + thread->QueueUserCallback([] { Thread::Exit(0); }); + result = Wait(thread.get(), true, 500ms); + REQUIRE(result == WaitResult::kSuccess); + REQUIRE(is_modified == 0); + REQUIRE(has_finished == -1); + // TODO(bwrsandman): Test alertable wait returning kUserCallback by using IO // callbacks. - REQUIRE(true); } } // namespace test diff --git a/src/xenia/base/threading_posix.cc b/src/xenia/base/threading_posix.cc index 653ebde05..36d34bd82 100644 --- a/src/xenia/base/threading_posix.cc +++ b/src/xenia/base/threading_posix.cc @@ -38,7 +38,13 @@ 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, kTimer, kThreadSuspend, k_Count }; +enum class SignalType { + kHighResolutionTimer, + kTimer, + kThreadSuspend, + kThreadUserCallback, + k_Count +}; int GetSystemSignal(SignalType num) { auto result = SIGRTMIN + static_cast(num); @@ -104,9 +110,12 @@ void Sleep(std::chrono::microseconds duration) { } while (ret == -1 && errno == EINTR); } -// TODO(dougvj) Not sure how to implement the equivalent of this on POSIX. +// TODO(bwrsandman) Implement by allowing alert interrupts from IO operations +thread_local bool alertable_state_ = false; SleepResult AlertableSleep(std::chrono::microseconds duration) { - sleep(duration.count() / 1000); + alertable_state_ = true; + Sleep(duration); + alertable_state_ = false; return SleepResult::kSuccess; } @@ -564,8 +573,18 @@ class PosixCondition : public PosixConditionBase { } void QueueUserCallback(std::function callback) { - // TODO(bwrsandman) - assert_always(); + WaitStarted(); + std::unique_lock lock(callback_mutex_); + user_callback_ = std::move(callback); + sigval value{}; + value.sival_ptr = this; + pthread_sigqueue(thread_, GetSystemSignal(SignalType::kThreadUserCallback), + value); + } + + void CallUserCallback() { + std::unique_lock lock(callback_mutex_); + user_callback_(); } bool Resume(uint32_t* out_new_suspend_count = nullptr) { @@ -634,7 +653,9 @@ class PosixCondition : public PosixConditionBase { int exit_code_; volatile State state_; mutable std::mutex state_mutex_; + mutable std::mutex callback_mutex_; mutable std::condition_variable state_signal_; + std::function user_callback_; }; // This wraps a condition object as our handle because posix has no single @@ -684,7 +705,10 @@ WaitResult Wait(WaitHandle* wait_handle, bool is_alertable, std::chrono::milliseconds timeout) { auto handle = reinterpret_cast(wait_handle->native_handle()); - return handle->Wait(timeout); + if (is_alertable) alertable_state_ = true; + auto result = handle->Wait(timeout); + if (is_alertable) alertable_state_ = false; + return result; } // TODO(dougvj) @@ -692,10 +716,12 @@ WaitResult SignalAndWait(WaitHandle* wait_handle_to_signal, WaitHandle* wait_handle_to_wait_on, bool is_alertable, std::chrono::milliseconds timeout) { assert_always(); - return WaitResult::kFailed; + if (is_alertable) alertable_state_ = true; + auto result = WaitResult::kFailed; + if (is_alertable) alertable_state_ = false; + return result; } -// TODO(bwrsandman): Add support for is_alertable std::pair WaitMultiple(WaitHandle* wait_handles[], size_t wait_handle_count, bool wait_all, bool is_alertable, @@ -705,8 +731,11 @@ std::pair WaitMultiple(WaitHandle* wait_handles[], handles[i] = reinterpret_cast(wait_handles[i]->native_handle()); } - return PosixConditionBase::WaitMultiple(std::move(handles), wait_all, - timeout); + if (is_alertable) alertable_state_ = true; + auto result = + PosixConditionBase::WaitMultiple(std::move(handles), wait_all, timeout); + if (is_alertable) alertable_state_ = false; + return result; } class PosixEvent : public PosixConditionHandle { @@ -902,6 +931,7 @@ void* PosixCondition::ThreadStartRoutine(void* parameter) { std::unique_ptr Thread::Create(CreationParameters params, std::function start_routine) { install_signal_handler(SignalType::kThreadSuspend); + install_signal_handler(SignalType::kThreadUserCallback); auto thread = std::make_unique(); if (!thread->Initialize(params, std::move(start_routine))) return nullptr; assert_not_null(thread); @@ -953,6 +983,14 @@ static void signal_handler(int signal, siginfo_t* info, void* /*context*/) { assert_not_null(current_thread_); current_thread_->WaitSuspended(); } break; + case SignalType::kThreadUserCallback: { + assert_not_null(info->si_value.sival_ptr); + auto p_thread = + static_cast*>(info->si_value.sival_ptr); + if (alertable_state_) { + p_thread->CallUserCallback(); + } + } break; default: assert_always(); } From e2bf48ac1d741e616e43a2c1c6da404249c96727 Mon Sep 17 00:00:00 2001 From: Sandy Carter Date: Mon, 12 Mar 2018 00:03:52 -0400 Subject: [PATCH 17/24] [threading linux] Implement TLS Implement TLSHandle with pthread_key_t. Test Alloc, Free, Get and Set. --- src/xenia/base/testing/threading_test.cc | 31 ++++++++++++++++++++++-- src/xenia/base/threading_posix.cc | 21 +++++++++------- 2 files changed, 41 insertions(+), 11 deletions(-) diff --git a/src/xenia/base/testing/threading_test.cc b/src/xenia/base/testing/threading_test.cc index 714e9de8c..54f3a13b0 100644 --- a/src/xenia/base/testing/threading_test.cc +++ b/src/xenia/base/testing/threading_test.cc @@ -113,8 +113,35 @@ TEST_CASE("Sleep Current Thread in Alertable State", "Sleep") { } TEST_CASE("TlsHandle") { - // TODO(bwrsandman): - REQUIRE(true); + // Test Allocate + auto handle = threading::AllocateTlsHandle(); + + // Test Free + REQUIRE(threading::FreeTlsHandle(handle)); + REQUIRE(!threading::FreeTlsHandle(handle)); + REQUIRE(!threading::FreeTlsHandle(threading::kInvalidTlsHandle)); + + // Test setting values + handle = threading::AllocateTlsHandle(); + REQUIRE(threading::GetTlsValue(handle) == 0); + uint32_t value = 0xDEADBEEF; + threading::SetTlsValue(handle, reinterpret_cast(&value)); + auto p_received_value = threading::GetTlsValue(handle); + REQUIRE(threading::GetTlsValue(handle) != 0); + auto received_value = *reinterpret_cast(p_received_value); + REQUIRE(received_value == value); + + uintptr_t non_thread_local_value = 0; + auto thread = Thread::Create({}, [&non_thread_local_value, &handle] { + non_thread_local_value = threading::GetTlsValue(handle); + }); + + auto result = Wait(thread.get(), false, 50ms); + REQUIRE(result == WaitResult::kSuccess); + REQUIRE(non_thread_local_value == 0); + + // Cleanup + REQUIRE(threading::FreeTlsHandle(handle)); } TEST_CASE("HighResolutionTimer") { diff --git a/src/xenia/base/threading_posix.cc b/src/xenia/base/threading_posix.cc index 36d34bd82..4ae3f0554 100644 --- a/src/xenia/base/threading_posix.cc +++ b/src/xenia/base/threading_posix.cc @@ -119,23 +119,26 @@ SleepResult AlertableSleep(std::chrono::microseconds duration) { return SleepResult::kSuccess; } -// TODO(dougvj) We can probably wrap this with pthread_key_t but the type of -// TlsHandle probably needs to be refactored TlsHandle AllocateTlsHandle() { - assert_always(); - return 0; + auto key = static_cast(-1); + auto res = pthread_key_create(&key, nullptr); + assert_zero(res); + assert_true(key != static_cast(-1)); + return static_cast(key); } -bool FreeTlsHandle(TlsHandle handle) { return true; } +bool FreeTlsHandle(TlsHandle handle) { + return pthread_key_delete(static_cast(handle)) == 0; +} uintptr_t GetTlsValue(TlsHandle handle) { - assert_always(); - return 0; + return reinterpret_cast( + pthread_getspecific(static_cast(handle))); } bool SetTlsValue(TlsHandle handle, uintptr_t value) { - assert_always(); - return false; + return pthread_setspecific(static_cast(handle), + reinterpret_cast(value)) == 0; } class PosixHighResolutionTimer : public HighResolutionTimer { From 1ac9ac7cabf793edc2695ee06ceec5ac0ce93ec1 Mon Sep 17 00:00:00 2001 From: Sandy Carter Date: Wed, 16 Jan 2019 18:23:52 -0800 Subject: [PATCH 18/24] [threading] Add complex wait on multiple test --- src/xenia/base/testing/threading_test.cc | 26 ++++++++++++++++++++++-- 1 file changed, 24 insertions(+), 2 deletions(-) diff --git a/src/xenia/base/testing/threading_test.cc b/src/xenia/base/testing/threading_test.cc index 54f3a13b0..2d3e6b857 100644 --- a/src/xenia/base/testing/threading_test.cc +++ b/src/xenia/base/testing/threading_test.cc @@ -200,8 +200,30 @@ TEST_CASE("HighResolutionTimer") { } TEST_CASE("Wait on Multiple Handles", "Wait") { - // TODO(bwrsandman): - REQUIRE(true); + auto mutant = Mutant::Create(true); + auto semaphore = Semaphore::Create(10, 10); + auto event_ = Event::CreateManualResetEvent(false); + auto thread = Thread::Create({}, [&mutant, &semaphore, &event_] { + event_->Set(); + Wait(mutant.get(), false, 25ms); + semaphore->Release(1, nullptr); + Wait(mutant.get(), false, 25ms); + mutant->Release(); + }); + + std::vector handles = { + mutant.get(), + semaphore.get(), + event_.get(), + thread.get(), + }; + + auto any_result = WaitAny(handles, false, 100ms); + REQUIRE(any_result.first == WaitResult::kSuccess); + REQUIRE(any_result.second == 0); + + auto all_result = WaitAll(handles, false, 100ms); + REQUIRE(all_result == WaitResult::kSuccess); } TEST_CASE("Signal and Wait") { From d1b3c858e96d19b05e3c09523b5353092c36daa8 Mon Sep 17 00:00:00 2001 From: Sandy Carter Date: Wed, 16 Jan 2019 18:45:39 -0800 Subject: [PATCH 19/24] [threading linux] Implement Signal and Wait Add Signal abstract function to handles. Test SignalAndWait. --- src/xenia/base/testing/threading_test.cc | 15 ++++++++++++-- src/xenia/base/threading_posix.cc | 26 ++++++++++++++++++++---- 2 files changed, 35 insertions(+), 6 deletions(-) diff --git a/src/xenia/base/testing/threading_test.cc b/src/xenia/base/testing/threading_test.cc index 2d3e6b857..47735d353 100644 --- a/src/xenia/base/testing/threading_test.cc +++ b/src/xenia/base/testing/threading_test.cc @@ -227,8 +227,19 @@ TEST_CASE("Wait on Multiple Handles", "Wait") { } TEST_CASE("Signal and Wait") { - // TODO(bwrsandman): Test semaphore, mutex and event - REQUIRE(true); + WaitResult result; + auto mutant = Mutant::Create(true); + auto event_ = Event::CreateAutoResetEvent(false); + auto thread = Thread::Create({}, [&mutant, &event_] { + Wait(mutant.get(), false); + event_->Set(); + }); + result = Wait(event_.get(), false, 50ms); + REQUIRE(result == WaitResult::kTimeout); + result = SignalAndWait(mutant.get(), event_.get(), false, 50ms); + REQUIRE(result == WaitResult::kSuccess); + result = Wait(thread.get(), false, 50ms); + REQUIRE(result == WaitResult::kSuccess); } TEST_CASE("Wait on Event", "Event") { diff --git a/src/xenia/base/threading_posix.cc b/src/xenia/base/threading_posix.cc index 4ae3f0554..87c605f84 100644 --- a/src/xenia/base/threading_posix.cc +++ b/src/xenia/base/threading_posix.cc @@ -181,6 +181,8 @@ std::unique_ptr HighResolutionTimer::CreateRepeating( class PosixConditionBase { public: + virtual bool Signal() = 0; + WaitResult Wait(std::chrono::milliseconds timeout) { bool executed; auto predicate = [this] { return this->signaled(); }; @@ -274,7 +276,7 @@ class PosixCondition : public PosixConditionBase { : signal_(initial_state), manual_reset_(manual_reset) {} virtual ~PosixCondition() = default; - void Signal() { + bool Signal() override { auto lock = std::unique_lock(mutex_); signal_ = true; if (manual_reset_) { @@ -282,6 +284,7 @@ class PosixCondition : public PosixConditionBase { } else { cond_.notify_one(); } + return true; } void Reset() { @@ -306,6 +309,8 @@ class PosixCondition : public PosixConditionBase { PosixCondition(uint32_t initial_count, uint32_t maximum_count) : count_(initial_count), maximum_count_(maximum_count) {} + bool Signal() override { return Release(1, nullptr); } + bool Release(uint32_t release_count, int* out_previous_count) { if (maximum_count_ - count_ >= release_count) { auto lock = std::unique_lock(mutex_); @@ -336,6 +341,9 @@ class PosixCondition : public PosixConditionBase { owner_ = std::this_thread::get_id(); } } + + bool Signal() override { return Release(); } + bool Release() { if (owner_ == std::this_thread::get_id() && count_ > 0) { auto lock = std::unique_lock(mutex_); @@ -372,6 +380,11 @@ class PosixCondition : public PosixConditionBase { virtual ~PosixCondition() { Cancel(); } + bool Signal() override { + CompletionRoutine(); + return true; + } + // TODO(bwrsandman): due_times of under 1ms deadlock under travis bool Set(std::chrono::nanoseconds due_time, std::chrono::milliseconds period, std::function opt_callback = nullptr) { @@ -505,6 +518,8 @@ class PosixCondition : public PosixConditionBase { } } + bool Signal() override { return true; } + std::string name() const { WaitStarted(); auto result = std::array{'\0'}; @@ -714,13 +729,16 @@ WaitResult Wait(WaitHandle* wait_handle, bool is_alertable, return result; } -// TODO(dougvj) WaitResult SignalAndWait(WaitHandle* wait_handle_to_signal, WaitHandle* wait_handle_to_wait_on, bool is_alertable, std::chrono::milliseconds timeout) { - assert_always(); - if (is_alertable) alertable_state_ = true; auto result = WaitResult::kFailed; + auto handle_to_signal = reinterpret_cast( + wait_handle_to_signal->native_handle()); + auto handle_to_wait_on = reinterpret_cast( + wait_handle_to_wait_on->native_handle()); + if (is_alertable) alertable_state_ = true; + if (handle_to_signal->Signal()) result = handle_to_wait_on->Wait(timeout); if (is_alertable) alertable_state_ = false; return result; } From 5575bd9b6f55285c38a1c5dd1dcd0e230103175c Mon Sep 17 00:00:00 2001 From: Sandy Carter Date: Mon, 21 Jan 2019 14:26:16 -0500 Subject: [PATCH 20/24] [threads linux] Free and signal suspended threads Give other threads access to initially suspended threads by signalling conditional variable before waiting for state to be changed again. --- src/xenia/base/threading_posix.cc | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/src/xenia/base/threading_posix.cc b/src/xenia/base/threading_posix.cc index 87c605f84..cd040e22c 100644 --- a/src/xenia/base/threading_posix.cc +++ b/src/xenia/base/threading_posix.cc @@ -922,17 +922,17 @@ void* PosixCondition::ThreadStartRoutine(void* parameter) { current_thread_ = thread; { std::unique_lock lock(thread->handle_.state_mutex_); - if (create_suspended) { - thread->handle_.state_ = State::kSuspended; - thread->handle_.state_signal_.wait(lock, [thread] { - return thread->handle_.state_ != State::kSuspended; - }); - } else { - thread->handle_.state_ = State::kRunning; - } + thread->handle_.state_ = + create_suspended ? State::kSuspended : State::kRunning; thread->handle_.state_signal_.notify_all(); } + if (create_suspended) { + std::unique_lock lock(thread->handle_.state_mutex_); + thread->handle_.state_signal_.wait( + lock, [thread] { return thread->handle_.state_ != State::kSuspended; }); + } + start_routine(); { From 49f8a519afdad6026d89fcc812ec774824fe07c9 Mon Sep 17 00:00:00 2001 From: Sandy Carter Date: Sun, 27 Jan 2019 10:48:31 -0500 Subject: [PATCH 21/24] [threading] Change thread names to suit pthread Shorten names to 16. Rename Win32 to Windowing. Shorten GraphicsSystem thread names due to 16 length limit of pthread. Without this change, both show up as GraphicsSystem. Remove redundant "Worker" and "Thread" from names. Remove redundant thread handle from thread name. --- src/xenia/app/emulator_window.cc | 4 ++-- src/xenia/apu/xma_decoder.cc | 2 +- src/xenia/base/logging.cc | 2 +- src/xenia/gpu/command_processor.cc | 2 +- src/xenia/gpu/graphics_system.cc | 2 +- src/xenia/kernel/kernel_state.cc | 7 ++----- 6 files changed, 8 insertions(+), 11 deletions(-) diff --git a/src/xenia/app/emulator_window.cc b/src/xenia/app/emulator_window.cc index 6cb2cd452..4bb1cb096 100644 --- a/src/xenia/app/emulator_window.cc +++ b/src/xenia/app/emulator_window.cc @@ -65,8 +65,8 @@ std::unique_ptr EmulatorWindow::Create(Emulator* emulator) { std::unique_ptr emulator_window(new EmulatorWindow(emulator)); emulator_window->loop()->PostSynchronous([&emulator_window]() { - xe::threading::set_name("Win32 Loop"); - xe::Profiler::ThreadEnter("Win32 Loop"); + xe::threading::set_name("Windowing Loop"); + xe::Profiler::ThreadEnter("Windowing Loop"); if (!emulator_window->Initialize()) { xe::FatalError("Failed to initialize main window"); diff --git a/src/xenia/apu/xma_decoder.cc b/src/xenia/apu/xma_decoder.cc index 05777051a..0b408fac8 100644 --- a/src/xenia/apu/xma_decoder.cc +++ b/src/xenia/apu/xma_decoder.cc @@ -130,7 +130,7 @@ X_STATUS XmaDecoder::Setup(kernel::KernelState* kernel_state) { WorkerThreadMain(); return 0; })); - worker_thread_->set_name("XMA Decoder Worker"); + worker_thread_->set_name("XMA Decoder"); worker_thread_->set_can_debugger_suspend(true); worker_thread_->Create(); diff --git a/src/xenia/base/logging.cc b/src/xenia/base/logging.cc index 238969513..891e9d52c 100644 --- a/src/xenia/base/logging.cc +++ b/src/xenia/base/logging.cc @@ -72,7 +72,7 @@ class Logger { write_thread_ = xe::threading::Thread::Create({}, [this]() { WriteThread(); }); - write_thread_->set_name("xe::FileLogSink Writer"); + write_thread_->set_name("Logging Writer"); } ~Logger() { diff --git a/src/xenia/gpu/command_processor.cc b/src/xenia/gpu/command_processor.cc index ff7b00521..0017558c3 100644 --- a/src/xenia/gpu/command_processor.cc +++ b/src/xenia/gpu/command_processor.cc @@ -73,7 +73,7 @@ bool CommandProcessor::Initialize( WorkerThreadMain(); return 0; })); - worker_thread_->set_name("GraphicsSystem Command Processor"); + worker_thread_->set_name("GPU Commands"); worker_thread_->Create(); return true; diff --git a/src/xenia/gpu/graphics_system.cc b/src/xenia/gpu/graphics_system.cc index 0e1c008d8..e74d80d35 100644 --- a/src/xenia/gpu/graphics_system.cc +++ b/src/xenia/gpu/graphics_system.cc @@ -129,7 +129,7 @@ X_STATUS GraphicsSystem::Setup(cpu::Processor* processor, })); // As we run vblank interrupts the debugger must be able to suspend us. vsync_worker_thread_->set_can_debugger_suspend(true); - vsync_worker_thread_->set_name("GraphicsSystem Vsync"); + vsync_worker_thread_->set_name("GPU VSync"); vsync_worker_thread_->Create(); if (cvars::trace_gpu_stream) { diff --git a/src/xenia/kernel/kernel_state.cc b/src/xenia/kernel/kernel_state.cc index 57097148e..052c9df53 100644 --- a/src/xenia/kernel/kernel_state.cc +++ b/src/xenia/kernel/kernel_state.cc @@ -242,10 +242,7 @@ object_ref KernelState::LaunchModule(object_ref module) { module->entry_point(), 0, X_CREATE_SUSPENDED, true, true)); // We know this is the 'main thread'. - char thread_name[32]; - std::snprintf(thread_name, xe::countof(thread_name), "Main XThread%08X", - thread->handle()); - thread->set_name(thread_name); + thread->set_name("Main XThread"); X_STATUS result = thread->Create(); if (XFAILED(result)) { @@ -340,7 +337,7 @@ void KernelState::SetExecutableModule(object_ref module) { } return 0; })); - dispatch_thread_->set_name("Kernel Dispatch Thread"); + dispatch_thread_->set_name("Kernel Dispatch"); dispatch_thread_->Create(); } } From 24d210f07f4fb6acb7ee2a7db1884dd52a52621f Mon Sep 17 00:00:00 2001 From: Sandy Carter Date: Sat, 13 Jul 2019 16:18:49 -0400 Subject: [PATCH 22/24] [threading linux] Implement suspend count Add suspend count to thread implementation. Increment suspend count on suspend and decrement on resume. Wait on suspend count to be decremented to 0. Return suspend count on suspend and on resume before incr/decr. Fix naming of resume suspend count to make clear that suspend count is before incr/decr. Add test. --- src/xenia/base/testing/threading_test.cc | 35 +++++++++++++++++++++ src/xenia/base/threading.h | 2 +- src/xenia/base/threading_posix.cc | 39 ++++++++++++++++-------- src/xenia/base/threading_win.cc | 10 +++--- 4 files changed, 68 insertions(+), 18 deletions(-) diff --git a/src/xenia/base/testing/threading_test.cc b/src/xenia/base/testing/threading_test.cc index 47735d353..160459d3f 100644 --- a/src/xenia/base/testing/threading_test.cc +++ b/src/xenia/base/testing/threading_test.cc @@ -849,6 +849,41 @@ TEST_CASE("Test Suspending Thread", "Thread") { thread->Resume(); result = threading::Wait(thread.get(), false, 50ms); REQUIRE(result == threading::WaitResult::kSuccess); + + // Test recursive suspend + thread = threading::Thread::Create(params, func); + thread->Suspend(); + thread->Suspend(); + result = threading::Wait(thread.get(), false, 50ms); + REQUIRE(result == threading::WaitResult::kTimeout); + thread->Resume(); + result = threading::Wait(thread.get(), false, 50ms); + REQUIRE(result == threading::WaitResult::kTimeout); + thread->Resume(); + result = threading::Wait(thread.get(), false, 50ms); + REQUIRE(result == threading::WaitResult::kSuccess); + + // Test suspend count + uint32_t suspend_count = 0; + thread = threading::Thread::Create(params, func); + thread->Suspend(&suspend_count); + REQUIRE(suspend_count == 0); + thread->Suspend(&suspend_count); + REQUIRE(suspend_count == 1); + thread->Suspend(&suspend_count); + REQUIRE(suspend_count == 2); + thread->Resume(&suspend_count); + REQUIRE(suspend_count == 3); + thread->Resume(&suspend_count); + REQUIRE(suspend_count == 2); + thread->Resume(&suspend_count); + REQUIRE(suspend_count == 1); + thread->Suspend(&suspend_count); + REQUIRE(suspend_count == 0); + thread->Resume(&suspend_count); + REQUIRE(suspend_count == 1); + result = threading::Wait(thread.get(), false, 50ms); + REQUIRE(result == threading::WaitResult::kSuccess); } TEST_CASE("Test Thread QueueUserCallback", "Thread") { diff --git a/src/xenia/base/threading.h b/src/xenia/base/threading.h index 7621c297f..dab3b53c9 100644 --- a/src/xenia/base/threading.h +++ b/src/xenia/base/threading.h @@ -388,7 +388,7 @@ class Thread : public WaitHandle { // Decrements a thread's suspend count. When the suspend count is decremented // to zero, the execution of the thread is resumed. - virtual bool Resume(uint32_t* out_new_suspend_count = nullptr) = 0; + virtual bool Resume(uint32_t* out_previous_suspend_count = nullptr) = 0; // Suspends the specified thread. virtual bool Suspend(uint32_t* out_previous_suspend_count = nullptr) = 0; diff --git a/src/xenia/base/threading_posix.cc b/src/xenia/base/threading_posix.cc index cd040e22c..1ef062b9b 100644 --- a/src/xenia/base/threading_posix.cc +++ b/src/xenia/base/threading_posix.cc @@ -470,7 +470,8 @@ class PosixCondition : public PosixConditionBase { : thread_(0), signaled_(false), exit_code_(0), - state_(State::kUninitialized) {} + state_(State::kUninitialized), + suspend_count_(0) {} bool Initialize(Thread::CreationParameters params, ThreadStartData* start_data) { start_data->create_suspended = params.create_suspended; @@ -605,21 +606,33 @@ class PosixCondition : public PosixConditionBase { user_callback_(); } - bool Resume(uint32_t* out_new_suspend_count = nullptr) { - // TODO(bwrsandman): implement suspend_count - assert_null(out_new_suspend_count); + bool Resume(uint32_t* out_previous_suspend_count = nullptr) { + if (out_previous_suspend_count) { + *out_previous_suspend_count = 0; + } WaitStarted(); std::unique_lock lock(state_mutex_); if (state_ != State::kSuspended) return false; - state_ = State::kRunning; + if (out_previous_suspend_count) { + *out_previous_suspend_count = suspend_count_; + } + --suspend_count_; state_signal_.notify_all(); return true; } bool Suspend(uint32_t* out_previous_suspend_count = nullptr) { - // TODO(bwrsandman): implement suspend_count - assert_null(out_previous_suspend_count); + if (out_previous_suspend_count) { + *out_previous_suspend_count = 0; + } WaitStarted(); + { + if (out_previous_suspend_count) { + *out_previous_suspend_count = suspend_count_; + } + state_ = State::kSuspended; + ++suspend_count_; + } int result = pthread_kill(thread_, GetSystemSignal(SignalType::kThreadSuspend)); return result == 0; @@ -653,8 +666,8 @@ class PosixCondition : public PosixConditionBase { /// Set state to suspended and wait until it reset by another thread void WaitSuspended() { std::unique_lock lock(state_mutex_); - state_ = State::kSuspended; - state_signal_.wait(lock, [this] { return state_ != State::kSuspended; }); + state_signal_.wait(lock, [this] { return suspend_count_ == 0; }); + state_ = State::kRunning; } private: @@ -670,6 +683,7 @@ class PosixCondition : public PosixConditionBase { bool signaled_; int exit_code_; volatile State state_; + volatile uint32_t suspend_count_; mutable std::mutex state_mutex_; mutable std::mutex callback_mutex_; mutable std::condition_variable state_signal_; @@ -889,8 +903,8 @@ class PosixThread : public PosixConditionHandle { handle_.QueueUserCallback(std::move(callback)); } - bool Resume(uint32_t* out_new_suspend_count) override { - return handle_.Resume(out_new_suspend_count); + bool Resume(uint32_t* out_previous_suspend_count) override { + return handle_.Resume(out_previous_suspend_count); } bool Suspend(uint32_t* out_previous_suspend_count) override { @@ -929,8 +943,9 @@ void* PosixCondition::ThreadStartRoutine(void* parameter) { if (create_suspended) { std::unique_lock lock(thread->handle_.state_mutex_); + thread->handle_.suspend_count_ = 1; thread->handle_.state_signal_.wait( - lock, [thread] { return thread->handle_.state_ != State::kSuspended; }); + lock, [thread] { return thread->handle_.suspend_count_ == 0; }); } start_routine(); diff --git a/src/xenia/base/threading_win.cc b/src/xenia/base/threading_win.cc index bbdd03624..510ea7310 100644 --- a/src/xenia/base/threading_win.cc +++ b/src/xenia/base/threading_win.cc @@ -378,16 +378,16 @@ class Win32Thread : public Win32Handle { QueueUserAPC(DispatchApc, handle_, reinterpret_cast(apc_data)); } - bool Resume(uint32_t* out_new_suspend_count = nullptr) override { - if (out_new_suspend_count) { - *out_new_suspend_count = 0; + bool Resume(uint32_t* out_previous_suspend_count = nullptr) override { + if (out_previous_suspend_count) { + *out_previous_suspend_count = 0; } DWORD result = ResumeThread(handle_); if (result == UINT_MAX) { return false; } - if (out_new_suspend_count) { - *out_new_suspend_count = result; + if (out_previous_suspend_count) { + *out_previous_suspend_count = result; } return true; } From 02c17f086e3a987b71ca3af72aa076fd9e1e920e Mon Sep 17 00:00:00 2001 From: Sandy Carter Date: Fri, 19 Jul 2019 10:41:18 -0400 Subject: [PATCH 23/24] [threading linux] Implement native_handle Move wait implementation to not use native_handle. Implement native_handle for each primitive using posix natives. --- src/xenia/base/threading_posix.cc | 66 ++++++++++++++++++++++--------- 1 file changed, 47 insertions(+), 19 deletions(-) diff --git a/src/xenia/base/threading_posix.cc b/src/xenia/base/threading_posix.cc index 1ef062b9b..216d6714e 100644 --- a/src/xenia/base/threading_posix.cc +++ b/src/xenia/base/threading_posix.cc @@ -252,6 +252,8 @@ class PosixConditionBase { } } + virtual void* native_handle() const { return cond_.native_handle(); } + protected: inline virtual bool signaled() const = 0; inline virtual void post_execution() = 0; @@ -357,6 +359,8 @@ class PosixCondition : public PosixConditionBase { return false; } + void* native_handle() const override { return mutex_.native_handle(); } + private: inline bool signaled() const override { return count_ == 0 || owner_ == std::this_thread::get_id(); @@ -437,6 +441,10 @@ class PosixCondition : public PosixConditionBase { return result; } + void* native_handle() const override { + return reinterpret_cast(timer_); + } + private: inline bool signaled() const override { return signal_; } inline void post_execution() override { @@ -670,6 +678,10 @@ class PosixCondition : public PosixConditionBase { state_ = State::kRunning; } + void* native_handle() const override { + return reinterpret_cast(thread_); + } + private: static void* ThreadStartRoutine(void* parameter); inline bool signaled() const override { return signaled_; } @@ -690,10 +702,15 @@ class PosixCondition : public PosixConditionBase { std::function user_callback_; }; +class PosixWaitHandle { + public: + virtual PosixConditionBase& condition() = 0; +}; + // This wraps a condition object as our handle because posix has no single // native handle for higher level concurrency constructs such as semaphores template -class PosixConditionHandle : public T { +class PosixConditionHandle : public T, public PosixWaitHandle { public: PosixConditionHandle() = default; explicit PosixConditionHandle(bool); @@ -702,11 +719,10 @@ class PosixConditionHandle : public T { PosixConditionHandle(uint32_t initial_count, uint32_t maximum_count); ~PosixConditionHandle() override = default; - protected: - void* native_handle() const override { - return reinterpret_cast(const_cast*>(&handle_)); - } + PosixConditionBase& condition() override { return handle_; } + void* native_handle() const override { return handle_.native_handle(); } + protected: PosixCondition handle_; friend PosixCondition; }; @@ -735,10 +751,12 @@ PosixConditionHandle::PosixConditionHandle(pthread_t thread) WaitResult Wait(WaitHandle* wait_handle, bool is_alertable, std::chrono::milliseconds timeout) { - auto handle = - reinterpret_cast(wait_handle->native_handle()); + auto posix_wait_handle = dynamic_cast(wait_handle); + if (posix_wait_handle == nullptr) { + return WaitResult::kFailed; + } if (is_alertable) alertable_state_ = true; - auto result = handle->Wait(timeout); + auto result = posix_wait_handle->condition().Wait(timeout); if (is_alertable) alertable_state_ = false; return result; } @@ -747,12 +765,18 @@ WaitResult SignalAndWait(WaitHandle* wait_handle_to_signal, WaitHandle* wait_handle_to_wait_on, bool is_alertable, std::chrono::milliseconds timeout) { auto result = WaitResult::kFailed; - auto handle_to_signal = reinterpret_cast( - wait_handle_to_signal->native_handle()); - auto handle_to_wait_on = reinterpret_cast( - wait_handle_to_wait_on->native_handle()); + auto posix_wait_handle_to_signal = + dynamic_cast(wait_handle_to_signal); + auto posix_wait_handle_to_wait_on = + dynamic_cast(wait_handle_to_wait_on); + if (posix_wait_handle_to_signal == nullptr || + posix_wait_handle_to_wait_on == nullptr) { + return WaitResult::kFailed; + } if (is_alertable) alertable_state_ = true; - if (handle_to_signal->Signal()) result = handle_to_wait_on->Wait(timeout); + if (posix_wait_handle_to_signal->condition().Signal()) { + result = posix_wait_handle_to_wait_on->condition().Wait(timeout); + } if (is_alertable) alertable_state_ = false; return result; } @@ -761,14 +785,18 @@ std::pair WaitMultiple(WaitHandle* wait_handles[], size_t wait_handle_count, bool wait_all, bool is_alertable, std::chrono::milliseconds timeout) { - std::vector handles(wait_handle_count); - for (int i = 0u; i < wait_handle_count; ++i) { - handles[i] = - reinterpret_cast(wait_handles[i]->native_handle()); + std::vector conditions; + conditions.reserve(wait_handle_count); + for (size_t i = 0u; i < wait_handle_count; ++i) { + auto handle = dynamic_cast(wait_handles[i]); + if (handle == nullptr) { + return std::make_pair(WaitResult::kFailed, 0); + } + conditions.push_back(&handle->condition()); } if (is_alertable) alertable_state_ = true; - auto result = - PosixConditionBase::WaitMultiple(std::move(handles), wait_all, timeout); + auto result = PosixConditionBase::WaitMultiple(std::move(conditions), + wait_all, timeout); if (is_alertable) alertable_state_ = false; return result; } From c01f87890484eb1d980ab08644c18e0eeac50e55 Mon Sep 17 00:00:00 2001 From: Sandy Carter Date: Sat, 31 Aug 2019 09:53:15 +0200 Subject: [PATCH 24/24] [threading test]: Reduce sleep and wait times --- src/xenia/base/testing/threading_test.cc | 206 +++++++++++------------ 1 file changed, 103 insertions(+), 103 deletions(-) diff --git a/src/xenia/base/testing/threading_test.cc b/src/xenia/base/testing/threading_test.cc index 160459d3f..bfcabb9e4 100644 --- a/src/xenia/base/testing/threading_test.cc +++ b/src/xenia/base/testing/threading_test.cc @@ -56,13 +56,13 @@ TEST_CASE("Fence") { std::thread(func), }); - Sleep(100ms); + Sleep(10ms); REQUIRE(finished.load() == 0); // TODO(bwrsandman): Check if this is correct behaviour: looping with Sleep // is the only way to get fence to signal all threads on windows for (int i = 0; i < threads.size(); ++i) { - Sleep(10ms); + Sleep(1ms); pFence->Signal(); } REQUIRE(started.load() == threads.size()); @@ -93,7 +93,7 @@ TEST_CASE("Sync with Memory Barrier", "SyncMemory") { } TEST_CASE("Sleep Current Thread", "Sleep") { - auto wait_time = 50ms; + auto wait_time = 5ms; auto start = std::chrono::steady_clock::now(); Sleep(wait_time); auto duration = std::chrono::steady_clock::now() - start; @@ -101,7 +101,7 @@ TEST_CASE("Sleep Current Thread", "Sleep") { } TEST_CASE("Sleep Current Thread in Alertable State", "Sleep") { - auto wait_time = 50ms; + auto wait_time = 5ms; auto start = std::chrono::steady_clock::now(); auto result = threading::AlertableSleep(wait_time); auto duration = std::chrono::steady_clock::now() - start; @@ -136,7 +136,7 @@ TEST_CASE("TlsHandle") { non_thread_local_value = threading::GetTlsValue(handle); }); - auto result = Wait(thread.get(), false, 50ms); + auto result = Wait(thread.get(), false, 5ms); REQUIRE(result == WaitResult::kSuccess); REQUIRE(non_thread_local_value == 0); @@ -147,11 +147,11 @@ TEST_CASE("TlsHandle") { TEST_CASE("HighResolutionTimer") { // 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; + const auto wait_time = 50ms; // Time the actual sleep duration { - const auto interval = 50ms; + const auto interval = 5ms; uint64_t counter = 0; auto start = std::chrono::steady_clock::now(); auto cb = [&counter] { ++counter; }; @@ -170,8 +170,8 @@ TEST_CASE("HighResolutionTimer") { // Test concurrent timers { - const auto interval1 = 100ms; - const auto interval2 = 200ms; + const auto interval1 = 10ms; + const auto interval2 = 20ms; uint64_t counter1 = 0; uint64_t counter2 = 0; auto start = std::chrono::steady_clock::now(); @@ -205,9 +205,9 @@ TEST_CASE("Wait on Multiple Handles", "Wait") { auto event_ = Event::CreateManualResetEvent(false); auto thread = Thread::Create({}, [&mutant, &semaphore, &event_] { event_->Set(); - Wait(mutant.get(), false, 25ms); + Wait(mutant.get(), false, 2ms); semaphore->Release(1, nullptr); - Wait(mutant.get(), false, 25ms); + Wait(mutant.get(), false, 2ms); mutant->Release(); }); @@ -218,11 +218,11 @@ TEST_CASE("Wait on Multiple Handles", "Wait") { thread.get(), }; - auto any_result = WaitAny(handles, false, 100ms); + auto any_result = WaitAny(handles, false, 10ms); REQUIRE(any_result.first == WaitResult::kSuccess); REQUIRE(any_result.second == 0); - auto all_result = WaitAll(handles, false, 100ms); + auto all_result = WaitAll(handles, false, 10ms); REQUIRE(all_result == WaitResult::kSuccess); } @@ -234,11 +234,11 @@ TEST_CASE("Signal and Wait") { Wait(mutant.get(), false); event_->Set(); }); - result = Wait(event_.get(), false, 50ms); + result = Wait(event_.get(), false, 5ms); REQUIRE(result == WaitResult::kTimeout); - result = SignalAndWait(mutant.get(), event_.get(), false, 50ms); + result = SignalAndWait(mutant.get(), event_.get(), false, 5ms); REQUIRE(result == WaitResult::kSuccess); - result = Wait(thread.get(), false, 50ms); + result = Wait(thread.get(), false, 5ms); REQUIRE(result == WaitResult::kSuccess); } @@ -247,16 +247,16 @@ TEST_CASE("Wait on Event", "Event") { WaitResult result; // Call wait on unset Event - result = Wait(evt.get(), false, 50ms); + result = Wait(evt.get(), false, 5ms); REQUIRE(result == WaitResult::kTimeout); // Call wait on set Event evt->Set(); - result = Wait(evt.get(), false, 50ms); + result = Wait(evt.get(), false, 5ms); REQUIRE(result == WaitResult::kSuccess); // Call wait on now consumed Event - result = Wait(evt.get(), false, 50ms); + result = Wait(evt.get(), false, 5ms); REQUIRE(result == WaitResult::kTimeout); } @@ -267,17 +267,17 @@ TEST_CASE("Reset Event", "Event") { // Call wait on reset Event evt->Set(); evt->Reset(); - result = Wait(evt.get(), false, 50ms); + result = Wait(evt.get(), false, 5ms); REQUIRE(result == WaitResult::kTimeout); // Test resetting the unset event evt->Reset(); - result = Wait(evt.get(), false, 50ms); + result = Wait(evt.get(), false, 5ms); REQUIRE(result == WaitResult::kTimeout); // Test setting the reset event evt->Set(); - result = Wait(evt.get(), false, 50ms); + result = Wait(evt.get(), false, 5ms); REQUIRE(result == WaitResult::kSuccess); } @@ -298,39 +298,39 @@ TEST_CASE("Wait on Multiple Events", "Event") { auto threads = std::array{ std::thread([&events, &sign_in] { - auto res = WaitAll({events[1].get(), events[3].get()}, false, 100ms); + auto res = WaitAll({events[1].get(), events[3].get()}, false, 10ms); if (res == WaitResult::kSuccess) { sign_in(1); } }), std::thread([&events, &sign_in] { - auto res = WaitAny({events[0].get(), events[2].get()}, false, 100ms); + auto res = WaitAny({events[0].get(), events[2].get()}, false, 10ms); if (res.first == WaitResult::kSuccess) { sign_in(2); } }), std::thread([&events, &sign_in] { auto res = WaitAll({events[0].get(), events[2].get(), events[3].get()}, - false, 100ms); + false, 10ms); if (res == WaitResult::kSuccess) { sign_in(3); } }), std::thread([&events, &sign_in] { - auto res = WaitAny({events[1].get(), events[3].get()}, false, 100ms); + auto res = WaitAny({events[1].get(), events[3].get()}, false, 10ms); if (res.first == WaitResult::kSuccess) { sign_in(4); } }), }; - Sleep(10ms); + Sleep(1ms); events[3]->Set(); // Signals thread id=4 and stays on for 1 and 3 - Sleep(10ms); + Sleep(1ms); events[1]->Set(); // Signals thread id=1 - Sleep(10ms); + Sleep(1ms); events[0]->Set(); // Signals thread id=2 - Sleep(10ms); + Sleep(1ms); events[2]->Set(); // Partial signals thread id=3 events[0]->Set(); // Signals thread id=3 @@ -353,7 +353,7 @@ TEST_CASE("Wait on Semaphore", "Semaphore") { // Wait on semaphore with no room sem = Semaphore::Create(0, 5); - result = Wait(sem.get(), false, 10ms); + result = Wait(sem.get(), false, 1ms); REQUIRE(result == WaitResult::kTimeout); // Add room in semaphore @@ -361,7 +361,7 @@ TEST_CASE("Wait on Semaphore", "Semaphore") { REQUIRE(previous_count == 0); REQUIRE(sem->Release(1, &previous_count)); REQUIRE(previous_count == 2); - result = Wait(sem.get(), false, 10ms); + result = Wait(sem.get(), false, 1ms); REQUIRE(result == WaitResult::kSuccess); REQUIRE(sem->Release(1, &previous_count)); REQUIRE(previous_count == 2); @@ -387,26 +387,26 @@ TEST_CASE("Wait on Semaphore", "Semaphore") { // Wait on fully available semaphore sem = Semaphore::Create(5, 5); - result = Wait(sem.get(), false, 10ms); + result = Wait(sem.get(), false, 1ms); REQUIRE(result == WaitResult::kSuccess); - result = Wait(sem.get(), false, 10ms); + result = Wait(sem.get(), false, 1ms); REQUIRE(result == WaitResult::kSuccess); - result = Wait(sem.get(), false, 10ms); + result = Wait(sem.get(), false, 1ms); REQUIRE(result == WaitResult::kSuccess); - result = Wait(sem.get(), false, 10ms); + result = Wait(sem.get(), false, 1ms); REQUIRE(result == WaitResult::kSuccess); - result = Wait(sem.get(), false, 10ms); + result = Wait(sem.get(), false, 1ms); REQUIRE(result == WaitResult::kSuccess); - result = Wait(sem.get(), false, 10ms); + result = Wait(sem.get(), false, 1ms); REQUIRE(result == WaitResult::kTimeout); // Semaphore between threads sem = Semaphore::Create(5, 5); - Sleep(10ms); + Sleep(1ms); // Occupy the semaphore with 5 threads auto func = [&sem] { - auto res = Wait(sem.get(), false, 100ms); - Sleep(500ms); + auto res = Wait(sem.get(), false, 10ms); + Sleep(50ms); if (res == WaitResult::kSuccess) { sem->Release(1, nullptr); } @@ -416,15 +416,15 @@ TEST_CASE("Wait on Semaphore", "Semaphore") { std::thread(func), std::thread(func), }; // Give threads time to acquire semaphore - Sleep(10ms); + Sleep(1ms); // Attempt to acquire full semaphore with current (6th) thread - result = Wait(sem.get(), false, 20ms); + result = Wait(sem.get(), false, 2ms); REQUIRE(result == WaitResult::kTimeout); // Give threads time to release semaphore for (auto& t : threads) { t.join(); } - result = Wait(sem.get(), false, 10ms); + result = Wait(sem.get(), false, 1ms); REQUIRE(result == WaitResult::kSuccess); sem->Release(1, &previous_count); REQUIRE(previous_count == 4); @@ -451,7 +451,7 @@ TEST_CASE("Wait on Multiple Semaphores", "Semaphore") { // Test Wait all which should fail sem0 = Semaphore::Create(0, 5); sem1 = Semaphore::Create(5, 5); - all_result = WaitAll({sem0.get(), sem1.get()}, false, 10ms); + all_result = WaitAll({sem0.get(), sem1.get()}, false, 1ms); REQUIRE(all_result == WaitResult::kTimeout); previous_count = -1; REQUIRE(sem0->Release(1, &previous_count)); @@ -463,7 +463,7 @@ TEST_CASE("Wait on Multiple Semaphores", "Semaphore") { // Test Wait all again which should succeed sem0 = Semaphore::Create(1, 5); sem1 = Semaphore::Create(5, 5); - all_result = WaitAll({sem0.get(), sem1.get()}, false, 10ms); + all_result = WaitAll({sem0.get(), sem1.get()}, false, 1ms); REQUIRE(all_result == WaitResult::kSuccess); previous_count = -1; REQUIRE(sem0->Release(1, &previous_count)); @@ -475,7 +475,7 @@ TEST_CASE("Wait on Multiple Semaphores", "Semaphore") { // Test Wait Any which should fail sem0 = Semaphore::Create(0, 5); sem1 = Semaphore::Create(0, 5); - any_result = WaitAny({sem0.get(), sem1.get()}, false, 10ms); + any_result = WaitAny({sem0.get(), sem1.get()}, false, 1ms); REQUIRE(any_result.first == WaitResult::kTimeout); REQUIRE(any_result.second == 0); previous_count = -1; @@ -488,7 +488,7 @@ TEST_CASE("Wait on Multiple Semaphores", "Semaphore") { // Test Wait Any which should succeed sem0 = Semaphore::Create(0, 5); sem1 = Semaphore::Create(5, 5); - any_result = WaitAny({sem0.get(), sem1.get()}, false, 10ms); + any_result = WaitAny({sem0.get(), sem1.get()}, false, 1ms); REQUIRE(any_result.first == WaitResult::kSuccess); REQUIRE(any_result.second == 1); previous_count = -1; @@ -565,16 +565,16 @@ TEST_CASE("Wait on Multiple Mutants", "Mutant") { auto thread0 = std::thread([&mut0, &mut1] { mut0 = Mutant::Create(true); mut1 = Mutant::Create(true); - Sleep(50ms); + Sleep(5ms); mut0->Release(); mut1->Release(); }); - Sleep(10ms); - all_result = WaitAll({mut0.get(), mut1.get()}, false, 10ms); + Sleep(1ms); + all_result = WaitAll({mut0.get(), mut1.get()}, false, 1ms); REQUIRE(all_result == WaitResult::kTimeout); REQUIRE_FALSE(mut0->Release()); REQUIRE_FALSE(mut1->Release()); - any_result = WaitAny({mut0.get(), mut1.get()}, false, 10ms); + any_result = WaitAny({mut0.get(), mut1.get()}, false, 1ms); REQUIRE(any_result.first == WaitResult::kTimeout); REQUIRE(any_result.second == 0); REQUIRE_FALSE(mut0->Release()); @@ -585,15 +585,15 @@ TEST_CASE("Wait on Multiple Mutants", "Mutant") { auto thread1 = std::thread([&mut0, &mut1] { mut0 = Mutant::Create(true); mut1 = Mutant::Create(false); - Sleep(50ms); + Sleep(5ms); mut0->Release(); }); - Sleep(10ms); - all_result = WaitAll({mut0.get(), mut1.get()}, false, 10ms); + Sleep(1ms); + all_result = WaitAll({mut0.get(), mut1.get()}, false, 1ms); REQUIRE(all_result == WaitResult::kTimeout); REQUIRE_FALSE(mut0->Release()); REQUIRE_FALSE(mut1->Release()); - any_result = WaitAny({mut0.get(), mut1.get()}, false, 10ms); + any_result = WaitAny({mut0.get(), mut1.get()}, false, 1ms); REQUIRE(any_result.first == WaitResult::kSuccess); REQUIRE(any_result.second == 1); REQUIRE_FALSE(mut0->Release()); @@ -604,14 +604,14 @@ TEST_CASE("Wait on Multiple Mutants", "Mutant") { auto thread2 = std::thread([&mut0, &mut1] { mut0 = Mutant::Create(false); mut1 = Mutant::Create(false); - Sleep(50ms); + Sleep(5ms); }); - Sleep(10ms); - all_result = WaitAll({mut0.get(), mut1.get()}, false, 10ms); + Sleep(1ms); + all_result = WaitAll({mut0.get(), mut1.get()}, false, 1ms); REQUIRE(all_result == WaitResult::kSuccess); REQUIRE(mut0->Release()); REQUIRE(mut1->Release()); - any_result = WaitAny({mut0.get(), mut1.get()}, false, 10ms); + any_result = WaitAny({mut0.get(), mut1.get()}, false, 1ms); REQUIRE(any_result.first == WaitResult::kSuccess); REQUIRE(any_result.second == 0); REQUIRE(mut0->Release()); @@ -651,36 +651,36 @@ TEST_CASE("Wait on Timer", "Timer") { // REQUIRE(result == WaitResult::kTimeout); // Test Repeating - REQUIRE(timer->SetRepeating(1ms, 10ms)); + REQUIRE(timer->SetRepeating(100us, 1ms)); for (int i = 0; i < 10; ++i) { - result = Wait(timer.get(), false, 20ms); + result = Wait(timer.get(), false, 2ms); INFO(i); REQUIRE(result == WaitResult::kSuccess); } MaybeYield(); - Sleep(10ms); // Skip a few events + Sleep(1ms); // Skip a few events for (int i = 0; i < 10; ++i) { - result = Wait(timer.get(), false, 20ms); + result = Wait(timer.get(), false, 2ms); REQUIRE(result == WaitResult::kSuccess); } // Cancel it timer->Cancel(); - result = Wait(timer.get(), false, 20ms); + result = Wait(timer.get(), false, 2ms); REQUIRE(result == WaitResult::kTimeout); MaybeYield(); - Sleep(10ms); // Skip a few events - result = Wait(timer.get(), false, 20ms); + Sleep(1ms); // Skip a few events + result = Wait(timer.get(), false, 2ms); REQUIRE(result == WaitResult::kTimeout); // Cancel with SetOnce - REQUIRE(timer->SetRepeating(1ms, 10ms)); + REQUIRE(timer->SetRepeating(1ms, 1ms)); for (int i = 0; i < 10; ++i) { - result = Wait(timer.get(), false, 20ms); + result = Wait(timer.get(), false, 2ms); REQUIRE(result == WaitResult::kSuccess); } - REQUIRE(timer->SetOnce(1ms)); - result = Wait(timer.get(), false, 20ms); + REQUIRE(timer->SetOnce(100us)); + result = Wait(timer.get(), false, 2ms); REQUIRE(result == WaitResult::kSuccess); // Signal from Set Once - result = Wait(timer.get(), false, 20ms); + result = Wait(timer.get(), false, 2ms); REQUIRE(result == WaitResult::kTimeout); // No more signals from repeating } @@ -700,24 +700,24 @@ TEST_CASE("Wait on Multiple Timers", "Timer") { // Some signaled REQUIRE(timer1->SetOnce(1ms)); - all_result = WaitAll({timer0.get(), timer1.get()}, false, 100ms); + all_result = WaitAll({timer0.get(), timer1.get()}, false, 10ms); REQUIRE(all_result == WaitResult::kTimeout); - any_result = WaitAny({timer0.get(), timer1.get()}, false, 100ms); + any_result = WaitAny({timer0.get(), timer1.get()}, false, 10ms); 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); + all_result = WaitAll({timer0.get(), timer1.get()}, false, 10ms); REQUIRE(all_result == WaitResult::kSuccess); REQUIRE(timer0->SetOnce(1ms)); Sleep(1ms); - any_result = WaitAny({timer0.get(), timer1.get()}, false, 100ms); + any_result = WaitAny({timer0.get(), timer1.get()}, false, 10ms); REQUIRE(any_result.first == WaitResult::kSuccess); REQUIRE(any_result.second == 0); // Check that timer0 reset - any_result = WaitAny({timer0.get(), timer1.get()}, false, 100ms); + any_result = WaitAny({timer0.get(), timer1.get()}, false, 10ms); REQUIRE(any_result.first == WaitResult::kSuccess); REQUIRE(any_result.second == 1); } @@ -765,14 +765,14 @@ TEST_CASE("Create and Run Thread", "Thread") { std::unique_ptr thread; WaitResult result; Thread::CreationParameters params = {}; - auto func = [] { Sleep(20ms); }; + auto func = [] { Sleep(20ns); }; // Create most basic case of thread thread = Thread::Create(params, func); REQUIRE(thread->native_handle() != nullptr); REQUIRE_NOTHROW(thread->affinity_mask()); REQUIRE(thread->name().empty()); - result = Wait(thread.get(), false, 50ms); + result = Wait(thread.get(), false, 5ms); REQUIRE(result == WaitResult::kSuccess); // Add thread name @@ -783,7 +783,7 @@ TEST_CASE("Create and Run Thread", "Thread") { REQUIRE(name.empty()); thread->set_name(new_name); REQUIRE(thread->name() == new_name); - result = Wait(thread.get(), false, 50ms); + result = Wait(thread.get(), false, 5ms); REQUIRE(result == WaitResult::kSuccess); // Use Terminate to end an infinitely looping thread @@ -792,10 +792,10 @@ TEST_CASE("Create and Run Thread", "Thread") { Sleep(1ms); } }); - result = Wait(thread.get(), false, 50ms); + result = Wait(thread.get(), false, 5ms); REQUIRE(result == WaitResult::kTimeout); thread->Terminate(-1); - result = Wait(thread.get(), false, 50ms); + result = Wait(thread.get(), false, 5ms); REQUIRE(result == WaitResult::kSuccess); // Call Exit from inside an infinitely looping thread @@ -804,11 +804,11 @@ TEST_CASE("Create and Run Thread", "Thread") { Thread::Exit(-1); } }); - result = Wait(thread.get(), false, 50ms); + result = Wait(thread.get(), false, 5ms); REQUIRE(result == WaitResult::kSuccess); // Call timeout wait on self - result = Wait(Thread::GetCurrentThread(), false, 50ms); + result = Wait(Thread::GetCurrentThread(), false, 5ms); REQUIRE(result == WaitResult::kTimeout); params.stack_size = 16 * 1024; @@ -818,7 +818,7 @@ TEST_CASE("Create and Run Thread", "Thread") { } }); REQUIRE(thread != nullptr); - result = Wait(thread.get(), false, 50ms); + result = Wait(thread.get(), false, 5ms); REQUIRE(result == WaitResult::kSuccess); // TODO(bwrsandman): Test with different priorities @@ -829,38 +829,38 @@ TEST_CASE("Test Suspending Thread", "Thread") { std::unique_ptr thread; WaitResult result; Thread::CreationParameters params = {}; - auto func = [] { Sleep(20ms); }; + auto func = [] { Sleep(20us); }; // Create initially suspended params.create_suspended = true; thread = threading::Thread::Create(params, func); - result = threading::Wait(thread.get(), false, 50ms); + result = threading::Wait(thread.get(), false, 5ms); REQUIRE(result == threading::WaitResult::kTimeout); thread->Resume(); - result = threading::Wait(thread.get(), false, 50ms); + result = threading::Wait(thread.get(), false, 5ms); REQUIRE(result == threading::WaitResult::kSuccess); params.create_suspended = false; // Create and then suspend thread = threading::Thread::Create(params, func); thread->Suspend(); - result = threading::Wait(thread.get(), false, 50ms); + result = threading::Wait(thread.get(), false, 5ms); REQUIRE(result == threading::WaitResult::kTimeout); thread->Resume(); - result = threading::Wait(thread.get(), false, 50ms); + result = threading::Wait(thread.get(), false, 5ms); REQUIRE(result == threading::WaitResult::kSuccess); // Test recursive suspend thread = threading::Thread::Create(params, func); thread->Suspend(); thread->Suspend(); - result = threading::Wait(thread.get(), false, 50ms); + result = threading::Wait(thread.get(), false, 5ms); REQUIRE(result == threading::WaitResult::kTimeout); thread->Resume(); - result = threading::Wait(thread.get(), false, 50ms); + result = threading::Wait(thread.get(), false, 5ms); REQUIRE(result == threading::WaitResult::kTimeout); thread->Resume(); - result = threading::Wait(thread.get(), false, 50ms); + result = threading::Wait(thread.get(), false, 5ms); REQUIRE(result == threading::WaitResult::kSuccess); // Test suspend count @@ -882,7 +882,7 @@ TEST_CASE("Test Suspending Thread", "Thread") { REQUIRE(suspend_count == 0); thread->Resume(&suspend_count); REQUIRE(suspend_count == 1); - result = threading::Wait(thread.get(), false, 50ms); + result = threading::Wait(thread.get(), false, 5ms); REQUIRE(result == threading::WaitResult::kSuccess); } @@ -904,15 +904,15 @@ TEST_CASE("Test Thread QueueUserCallback", "Thread") { has_finished = -1; thread = Thread::Create(params, [&has_finished, &order] { // Not using Alertable so callback is not registered - Sleep(90ms); + Sleep(9ms); has_finished = std::atomic_fetch_add_explicit( &order, 1, std::memory_order::memory_order_relaxed); }); - result = Wait(thread.get(), true, 50ms); + result = Wait(thread.get(), true, 5ms); REQUIRE(result == WaitResult::kTimeout); REQUIRE(is_modified == -1); thread->QueueUserCallback(callback); - result = Wait(thread.get(), true, 100ms); + result = Wait(thread.get(), true, 10ms); REQUIRE(result == WaitResult::kSuccess); REQUIRE(is_modified == -1); REQUIRE(has_finished == 0); @@ -923,15 +923,15 @@ TEST_CASE("Test Thread QueueUserCallback", "Thread") { has_finished = -1; thread = Thread::Create(params, [&has_finished, &order] { // Using Alertable so callback is registered - AlertableSleep(90ms); + AlertableSleep(9ms); has_finished = std::atomic_fetch_add_explicit( &order, 1, std::memory_order::memory_order_relaxed); }); - result = Wait(thread.get(), true, 50ms); + result = Wait(thread.get(), true, 5ms); REQUIRE(result == WaitResult::kTimeout); REQUIRE(is_modified == -1); thread->QueueUserCallback(callback); - result = Wait(thread.get(), true, 100ms); + result = Wait(thread.get(), true, 10ms); REQUIRE(result == WaitResult::kSuccess); REQUIRE(is_modified == 0); REQUIRE(has_finished == 1); @@ -944,14 +944,14 @@ TEST_CASE("Test Thread QueueUserCallback", "Thread") { is_modified = std::atomic_fetch_add_explicit( &order, 1, std::memory_order::memory_order_relaxed); // Using Alertable so callback is registered - AlertableSleep(200ms); + AlertableSleep(20ms); has_finished = std::atomic_fetch_add_explicit( &order, 1, std::memory_order::memory_order_relaxed); }); - result = Wait(thread.get(), true, 100ms); + result = Wait(thread.get(), true, 10ms); REQUIRE(result == WaitResult::kTimeout); thread->QueueUserCallback([] { Thread::Exit(0); }); - result = Wait(thread.get(), true, 500ms); + result = Wait(thread.get(), true, 50ms); REQUIRE(result == WaitResult::kSuccess); REQUIRE(is_modified == 0); REQUIRE(has_finished == -1);