Merge branch 'master' into vulkan
This commit is contained in:
@@ -36,10 +36,8 @@
|
||||
|
||||
#include "third_party/fmt/include/fmt/format.h"
|
||||
|
||||
DEFINE_path(
|
||||
log_file, "",
|
||||
"Logs are written to the given file (specify stdout for command line)",
|
||||
"Logging");
|
||||
DEFINE_path(log_file, "", "Logs are written to the given file", "Logging");
|
||||
DEFINE_bool(log_to_stdout, true, "Write log output to stdout", "Logging");
|
||||
DEFINE_bool(log_to_debugprint, false, "Dump the log to DebugPrint.", "Logging");
|
||||
DEFINE_bool(flush_log, true, "Flush log file after each log line batch.",
|
||||
"Logging");
|
||||
@@ -66,41 +64,39 @@ struct LogLine {
|
||||
|
||||
thread_local char thread_log_buffer_[64 * 1024];
|
||||
|
||||
void FileLogSink::Write(const char* buf, size_t size) {
|
||||
if (file_) {
|
||||
fwrite(buf, 1, size, file_);
|
||||
}
|
||||
}
|
||||
|
||||
void FileLogSink::Flush() {
|
||||
if (file_) {
|
||||
fflush(file_);
|
||||
}
|
||||
}
|
||||
|
||||
class Logger {
|
||||
public:
|
||||
explicit Logger(const std::string_view app_name)
|
||||
: file_(nullptr),
|
||||
running_(true),
|
||||
wait_strategy_(),
|
||||
: wait_strategy_(),
|
||||
claim_strategy_(kBlockCount, wait_strategy_),
|
||||
consumed_(wait_strategy_) {
|
||||
consumed_(wait_strategy_),
|
||||
running_(true) {
|
||||
claim_strategy_.add_claim_barrier(consumed_);
|
||||
|
||||
if (cvars::log_file.empty()) {
|
||||
// Default to app name.
|
||||
auto file_name = fmt::format("{}.log", app_name);
|
||||
auto file_path = std::filesystem::path(file_name);
|
||||
xe::filesystem::CreateParentFolder(file_path);
|
||||
file_ = xe::filesystem::OpenFile(file_path, "wt");
|
||||
} else {
|
||||
if (cvars::log_file == "stdout") {
|
||||
file_ = stdout;
|
||||
} else {
|
||||
xe::filesystem::CreateParentFolder(cvars::log_file);
|
||||
file_ = xe::filesystem::OpenFile(cvars::log_file, "wt");
|
||||
}
|
||||
}
|
||||
|
||||
write_thread_ =
|
||||
xe::threading::Thread::Create({}, [this]() { WriteThread(); });
|
||||
write_thread_->set_name("xe::FileLogSink Writer");
|
||||
write_thread_->set_name("Logging Writer");
|
||||
}
|
||||
|
||||
~Logger() {
|
||||
running_ = false;
|
||||
xe::threading::Wait(write_thread_.get(), true);
|
||||
fflush(file_);
|
||||
fclose(file_);
|
||||
}
|
||||
|
||||
void AddLogSink(std::unique_ptr<LogSink>&& sink) {
|
||||
sinks_.push_back(std::move(sink));
|
||||
}
|
||||
|
||||
private:
|
||||
@@ -126,14 +122,14 @@ class Logger {
|
||||
dp::multi_threaded_claim_strategy<dp::spin_wait_strategy> claim_strategy_;
|
||||
dp::sequence_barrier<dp::spin_wait_strategy> consumed_;
|
||||
|
||||
FILE* file_;
|
||||
std::vector<std::unique_ptr<LogSink>> sinks_;
|
||||
|
||||
std::atomic<bool> running_;
|
||||
std::unique_ptr<xe::threading::Thread> write_thread_;
|
||||
|
||||
void Write(const char* buf, size_t size) {
|
||||
if (file_) {
|
||||
fwrite(buf, 1, size, file_);
|
||||
for (const auto& sink : sinks_) {
|
||||
sink->Write(buf, size);
|
||||
}
|
||||
if (cvars::log_to_debugprint) {
|
||||
debugging::DebugPrint("{}", std::string_view(buf, size));
|
||||
@@ -246,7 +242,9 @@ class Logger {
|
||||
desired_count = 1;
|
||||
|
||||
if (cvars::flush_log) {
|
||||
fflush(file_);
|
||||
for (const auto& sink : sinks_) {
|
||||
sink->Flush();
|
||||
}
|
||||
}
|
||||
|
||||
idle_loops = 0;
|
||||
@@ -291,6 +289,27 @@ class Logger {
|
||||
void InitializeLogging(const std::string_view app_name) {
|
||||
auto mem = memory::AlignedAlloc<Logger>(0x10);
|
||||
logger_ = new (mem) Logger(app_name);
|
||||
|
||||
FILE* log_file = nullptr;
|
||||
|
||||
if (cvars::log_file.empty()) {
|
||||
// Default to app name.
|
||||
auto file_name = fmt::format("{}.log", app_name);
|
||||
auto file_path = std::filesystem::path(file_name);
|
||||
xe::filesystem::CreateParentFolder(file_path);
|
||||
|
||||
log_file = xe::filesystem::OpenFile(file_path, "wt");
|
||||
} else {
|
||||
xe::filesystem::CreateParentFolder(cvars::log_file);
|
||||
log_file = xe::filesystem::OpenFile(cvars::log_file, "wt");
|
||||
}
|
||||
auto sink = std::make_unique<FileLogSink>(log_file);
|
||||
logger_->AddLogSink(std::move(sink));
|
||||
|
||||
if (cvars::log_to_stdout) {
|
||||
auto stdout_sink = std::make_unique<FileLogSink>(stdout);
|
||||
logger_->AddLogSink(std::move(stdout_sink));
|
||||
}
|
||||
}
|
||||
|
||||
void ShutdownLogging() {
|
||||
|
||||
@@ -34,6 +34,31 @@ enum class LogLevel {
|
||||
Trace,
|
||||
};
|
||||
|
||||
class LogSink {
|
||||
public:
|
||||
virtual ~LogSink() = default;
|
||||
|
||||
virtual void Write(const char* buf, size_t size) = 0;
|
||||
virtual void Flush() = 0;
|
||||
};
|
||||
|
||||
class FileLogSink final : public LogSink {
|
||||
public:
|
||||
explicit FileLogSink(FILE* file) : file_(file) {}
|
||||
virtual ~FileLogSink() {
|
||||
if (file_) {
|
||||
fflush(file_);
|
||||
fclose(file_);
|
||||
}
|
||||
}
|
||||
|
||||
void Write(const char* buf, size_t size) override;
|
||||
void Flush() override;
|
||||
|
||||
private:
|
||||
FILE* file_;
|
||||
};
|
||||
|
||||
// Initializes the logging system and any outputs requested.
|
||||
// Must be called on startup.
|
||||
void InitializeLogging(const std::string_view app_name);
|
||||
|
||||
@@ -29,6 +29,8 @@
|
||||
|
||||
DEFINE_bool(win32_high_freq, true,
|
||||
"Requests high performance from the NT kernel", "Kernel");
|
||||
DEFINE_bool(enable_console, false, "Open a console window with the main window",
|
||||
"General");
|
||||
|
||||
namespace xe {
|
||||
|
||||
@@ -37,27 +39,23 @@ bool has_console_attached_ = true;
|
||||
bool has_console_attached() { return has_console_attached_; }
|
||||
|
||||
void AttachConsole() {
|
||||
bool has_console = ::AttachConsole(ATTACH_PARENT_PROCESS) == TRUE;
|
||||
if (!has_console) {
|
||||
// We weren't launched from a console, so just return.
|
||||
// We could alloc our own console, but meh:
|
||||
// has_console = AllocConsole() == TRUE;
|
||||
has_console_attached_ = false;
|
||||
if (!cvars::enable_console) {
|
||||
return;
|
||||
}
|
||||
|
||||
AllocConsole();
|
||||
|
||||
has_console_attached_ = true;
|
||||
|
||||
auto std_handle = (intptr_t)GetStdHandle(STD_OUTPUT_HANDLE);
|
||||
auto con_handle = _open_osfhandle(std_handle, _O_TEXT);
|
||||
auto fp = _fdopen(con_handle, "w");
|
||||
*stdout = *fp;
|
||||
setvbuf(stdout, nullptr, _IONBF, 0);
|
||||
freopen_s(&fp, "CONOUT$", "w", stdout);
|
||||
|
||||
std_handle = (intptr_t)GetStdHandle(STD_ERROR_HANDLE);
|
||||
con_handle = _open_osfhandle(std_handle, _O_TEXT);
|
||||
fp = _fdopen(con_handle, "w");
|
||||
*stderr = *fp;
|
||||
setvbuf(stderr, nullptr, _IONBF, 0);
|
||||
freopen_s(&fp, "CONOUT$", "w", stderr);
|
||||
}
|
||||
|
||||
static void RequestHighPerformance() {
|
||||
@@ -125,6 +123,10 @@ int Main() {
|
||||
return 1;
|
||||
}
|
||||
|
||||
// Attach a console so we can write output to stdout. If the user hasn't
|
||||
// redirected output themselves it'll pop up a window.
|
||||
xe::AttachConsole();
|
||||
|
||||
// Setup COM on the main thread.
|
||||
// NOTE: this may fail if COM has already been initialized - that's OK.
|
||||
#pragma warning(suppress : 6031)
|
||||
@@ -163,10 +165,6 @@ int main(int argc_ignored, char** argv_ignored) { return xe::Main(); }
|
||||
|
||||
// Used in windowed apps; automatically picked based on subsystem.
|
||||
int WINAPI wWinMain(HINSTANCE, HINSTANCE, LPWSTR command_line, int) {
|
||||
// Attach a console so we can write output to stdout. If the user hasn't
|
||||
// redirected output themselves it'll pop up a window.
|
||||
xe::AttachConsole();
|
||||
|
||||
// Run normal entry point.
|
||||
return xe::Main();
|
||||
}
|
||||
|
||||
@@ -10,11 +10,15 @@
|
||||
#ifndef XENIA_BASE_STRING_UTIL_H_
|
||||
#define XENIA_BASE_STRING_UTIL_H_
|
||||
|
||||
#include <algorithm>
|
||||
#include <charconv>
|
||||
#include <cstddef>
|
||||
#include <cstring>
|
||||
#include <string>
|
||||
|
||||
#include "third_party/fmt/include/fmt/format.h"
|
||||
#include "xenia/base/assert.h"
|
||||
#include "xenia/base/memory.h"
|
||||
#include "xenia/base/platform.h"
|
||||
#include "xenia/base/string.h"
|
||||
#include "xenia/base/vec128.h"
|
||||
@@ -30,6 +34,40 @@
|
||||
namespace xe {
|
||||
namespace string_util {
|
||||
|
||||
inline size_t copy_truncating(char* dest, const std::string_view source,
|
||||
size_t dest_buffer_count) {
|
||||
if (!dest_buffer_count) {
|
||||
return 0;
|
||||
}
|
||||
size_t chars_copied = std::min(source.size(), dest_buffer_count - size_t(1));
|
||||
std::memcpy(dest, source.data(), chars_copied);
|
||||
dest[chars_copied] = '\0';
|
||||
return chars_copied;
|
||||
}
|
||||
|
||||
inline size_t copy_truncating(char16_t* dest, const std::u16string_view source,
|
||||
size_t dest_buffer_count) {
|
||||
if (!dest_buffer_count) {
|
||||
return 0;
|
||||
}
|
||||
size_t chars_copied = std::min(source.size(), dest_buffer_count - size_t(1));
|
||||
std::memcpy(dest, source.data(), chars_copied * sizeof(char16_t));
|
||||
dest[chars_copied] = u'\0';
|
||||
return chars_copied;
|
||||
}
|
||||
|
||||
inline size_t copy_and_swap_truncating(char16_t* dest,
|
||||
const std::u16string_view source,
|
||||
size_t dest_buffer_count) {
|
||||
if (!dest_buffer_count) {
|
||||
return 0;
|
||||
}
|
||||
size_t chars_copied = std::min(source.size(), dest_buffer_count - size_t(1));
|
||||
xe::copy_and_swap(dest, source.data(), chars_copied);
|
||||
dest[chars_copied] = u'\0';
|
||||
return chars_copied;
|
||||
}
|
||||
|
||||
inline std::string to_hex_string(uint32_t value) {
|
||||
return fmt::format("{:08X}", value);
|
||||
}
|
||||
|
||||
@@ -15,7 +15,7 @@ namespace xe {
|
||||
|
||||
void LaunchWebBrowser(const std::string& url) {
|
||||
auto temp = xe::to_utf16(url);
|
||||
ShellExecuteW(nullptr, L"open", reinterpret_cast<LPCWSTR>(url.c_str()),
|
||||
ShellExecuteW(nullptr, L"open", reinterpret_cast<LPCWSTR>(temp.c_str()),
|
||||
nullptr, nullptr, SW_SHOWNORMAL);
|
||||
}
|
||||
|
||||
|
||||
967
src/xenia/base/testing/threading_test.cc
Normal file
967
src/xenia/base/testing/threading_test.cc
Normal file
@@ -0,0 +1,967 @@
|
||||
/**
|
||||
******************************************************************************
|
||||
* 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 <array>
|
||||
|
||||
#include "xenia/base/threading.h"
|
||||
|
||||
#include "third_party/catch/include/catch.hpp"
|
||||
|
||||
namespace xe {
|
||||
namespace base {
|
||||
namespace test {
|
||||
using namespace threading;
|
||||
using namespace std::chrono_literals;
|
||||
|
||||
TEST_CASE("Fence") {
|
||||
std::unique_ptr<threading::Fence> pFence;
|
||||
std::unique_ptr<threading::HighResolutionTimer> pTimer;
|
||||
|
||||
// Signal without wait
|
||||
pFence = std::make_unique<threading::Fence>();
|
||||
pFence->Signal();
|
||||
|
||||
// Signal once and wait
|
||||
pFence = std::make_unique<threading::Fence>();
|
||||
pFence->Signal();
|
||||
pFence->Wait();
|
||||
|
||||
// Signal twice and wait
|
||||
pFence = std::make_unique<threading::Fence>();
|
||||
pFence->Signal();
|
||||
pFence->Signal();
|
||||
pFence->Wait();
|
||||
|
||||
// Signal and wait two times
|
||||
pFence = std::make_unique<threading::Fence>();
|
||||
pFence->Signal();
|
||||
pFence->Wait();
|
||||
pFence->Signal();
|
||||
pFence->Wait();
|
||||
|
||||
// Test to synchronize multiple threads
|
||||
std::atomic<int> started(0);
|
||||
std::atomic<int> finished(0);
|
||||
pFence = std::make_unique<threading::Fence>();
|
||||
auto func = [&pFence, &started, &finished] {
|
||||
started.fetch_add(1);
|
||||
pFence->Wait();
|
||||
finished.fetch_add(1);
|
||||
};
|
||||
|
||||
auto threads = std::array<std::thread, 5>({
|
||||
std::thread(func),
|
||||
std::thread(func),
|
||||
std::thread(func),
|
||||
std::thread(func),
|
||||
std::thread(func),
|
||||
});
|
||||
|
||||
Sleep(100ms);
|
||||
REQUIRE(started.load() == threads.size());
|
||||
REQUIRE(finished.load() == 0);
|
||||
|
||||
pFence->Signal();
|
||||
|
||||
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();
|
||||
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") {
|
||||
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") {
|
||||
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") {
|
||||
// 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<uintptr_t>(&value));
|
||||
auto p_received_value = threading::GetTlsValue(handle);
|
||||
REQUIRE(threading::GetTlsValue(handle) != 0);
|
||||
auto received_value = *reinterpret_cast<uint32_t*>(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") {
|
||||
// The wait time is 500ms with an interval of 50ms
|
||||
// Smaller values are not as precise and fail the test
|
||||
const auto wait_time = 500ms;
|
||||
|
||||
// Time the actual sleep duration
|
||||
{
|
||||
const auto interval = 50ms;
|
||||
std::atomic<uint64_t> counter;
|
||||
auto start = std::chrono::steady_clock::now();
|
||||
auto cb = [&counter] { ++counter; };
|
||||
auto pTimer = HighResolutionTimer::CreateRepeating(interval, cb);
|
||||
Sleep(wait_time);
|
||||
pTimer.reset();
|
||||
auto duration = std::chrono::steady_clock::now() - start;
|
||||
|
||||
// Should have run as many times as wait_time / timer_interval plus or
|
||||
// minus 1 due to imprecision of Sleep
|
||||
REQUIRE(duration.count() >= wait_time.count());
|
||||
auto ratio = static_cast<uint64_t>(duration / interval);
|
||||
REQUIRE(counter >= ratio - 1);
|
||||
REQUIRE(counter <= ratio + 1);
|
||||
}
|
||||
|
||||
// Test concurrent timers
|
||||
{
|
||||
const auto interval1 = 100ms;
|
||||
const auto interval2 = 200ms;
|
||||
std::atomic<uint64_t> counter1(0);
|
||||
std::atomic<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<uint64_t>(duration / interval1);
|
||||
auto ratio2 = static_cast<uint64_t>(duration / interval2);
|
||||
REQUIRE(counter1 >= ratio1 - 1);
|
||||
REQUIRE(counter1 <= ratio1 + 1);
|
||||
REQUIRE(counter2 >= ratio2 - 1);
|
||||
REQUIRE(counter2 <= ratio2 + 1);
|
||||
}
|
||||
|
||||
// TODO(bwrsandman): Check on which thread callbacks are executed when
|
||||
// spawned from differing threads
|
||||
}
|
||||
|
||||
TEST_CASE("Wait on Multiple Handles", "Wait") {
|
||||
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<WaitHandle*> 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") {
|
||||
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") {
|
||||
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);
|
||||
}
|
||||
|
||||
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 Multiple Events", "Event") {
|
||||
auto events = std::array<std::unique_ptr<Event>, 4>{
|
||||
Event::CreateAutoResetEvent(false),
|
||||
Event::CreateAutoResetEvent(false),
|
||||
Event::CreateAutoResetEvent(false),
|
||||
Event::CreateManualResetEvent(false),
|
||||
};
|
||||
|
||||
std::array<char, 8> 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] = static_cast<char>('0' + id);
|
||||
};
|
||||
|
||||
auto threads = std::array<std::thread, 4>{
|
||||
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();
|
||||
}
|
||||
|
||||
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") {
|
||||
WaitResult result;
|
||||
std::unique_ptr<Semaphore> 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, 5>{
|
||||
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<WaitResult, size_t> any_result;
|
||||
int previous_count;
|
||||
std::unique_ptr<Semaphore> 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") {
|
||||
WaitResult result;
|
||||
std::unique_ptr<Mutant> 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<WaitResult, size_t> any_result;
|
||||
std::unique_ptr<Mutant> 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("Wait on Timer", "Timer") {
|
||||
WaitResult result;
|
||||
std::unique_ptr<Timer> timer;
|
||||
|
||||
// Test Manual Reset
|
||||
timer = Timer::CreateManualResetTimer();
|
||||
result = Wait(timer.get(), false, 1ms);
|
||||
REQUIRE(result == WaitResult::kTimeout);
|
||||
REQUIRE(timer->SetOnce(1ms)); // Signals it
|
||||
result = Wait(timer.get(), false, 2ms);
|
||||
REQUIRE(result == WaitResult::kSuccess);
|
||||
result = Wait(timer.get(), false, 1ms);
|
||||
REQUIRE(result == WaitResult::kSuccess); // Did not reset
|
||||
|
||||
// Test Synchronization
|
||||
timer = Timer::CreateSynchronizationTimer();
|
||||
result = Wait(timer.get(), false, 1ms);
|
||||
REQUIRE(result == WaitResult::kTimeout);
|
||||
REQUIRE(timer->SetOnce(1ms)); // Signals it
|
||||
result = Wait(timer.get(), false, 2ms);
|
||||
REQUIRE(result == WaitResult::kSuccess);
|
||||
result = Wait(timer.get(), false, 1ms);
|
||||
REQUIRE(result == WaitResult::kTimeout); // Did reset
|
||||
|
||||
// TODO(bwrsandman): This test unexpectedly fails under windows
|
||||
// Test long due time
|
||||
// timer = Timer::CreateSynchronizationTimer();
|
||||
// REQUIRE(timer->SetOnce(10s));
|
||||
// result = Wait(timer.get(), false, 10ms); // Still signals under windows
|
||||
// REQUIRE(result == WaitResult::kTimeout);
|
||||
|
||||
// Test Repeating
|
||||
REQUIRE(timer->SetRepeating(1ms, 10ms));
|
||||
for (int i = 0; i < 10; ++i) {
|
||||
result = Wait(timer.get(), false, 20ms);
|
||||
INFO(i);
|
||||
REQUIRE(result == WaitResult::kSuccess);
|
||||
}
|
||||
MaybeYield();
|
||||
Sleep(10ms); // Skip a few events
|
||||
for (int i = 0; i < 10; ++i) {
|
||||
result = Wait(timer.get(), false, 20ms);
|
||||
REQUIRE(result == WaitResult::kSuccess);
|
||||
}
|
||||
// Cancel it
|
||||
timer->Cancel();
|
||||
result = Wait(timer.get(), false, 20ms);
|
||||
REQUIRE(result == WaitResult::kTimeout);
|
||||
MaybeYield();
|
||||
Sleep(10ms); // Skip a few events
|
||||
result = Wait(timer.get(), false, 20ms);
|
||||
REQUIRE(result == WaitResult::kTimeout);
|
||||
// Cancel with SetOnce
|
||||
REQUIRE(timer->SetRepeating(1ms, 10ms));
|
||||
for (int i = 0; i < 10; ++i) {
|
||||
result = Wait(timer.get(), false, 20ms);
|
||||
REQUIRE(result == WaitResult::kSuccess);
|
||||
}
|
||||
REQUIRE(timer->SetOnce(1ms));
|
||||
result = Wait(timer.get(), false, 20ms);
|
||||
REQUIRE(result == WaitResult::kSuccess); // Signal from Set Once
|
||||
result = Wait(timer.get(), false, 20ms);
|
||||
REQUIRE(result == WaitResult::kTimeout); // No more signals from repeating
|
||||
}
|
||||
|
||||
TEST_CASE("Wait on Multiple Timers", "Timer") {
|
||||
WaitResult all_result;
|
||||
std::pair<WaitResult, size_t> any_result;
|
||||
|
||||
auto timer0 = Timer::CreateSynchronizationTimer();
|
||||
auto timer1 = Timer::CreateManualResetTimer();
|
||||
|
||||
// None signaled
|
||||
all_result = WaitAll({timer0.get(), timer1.get()}, false, 1ms);
|
||||
REQUIRE(all_result == WaitResult::kTimeout);
|
||||
any_result = WaitAny({timer0.get(), timer1.get()}, false, 1ms);
|
||||
REQUIRE(any_result.first == WaitResult::kTimeout);
|
||||
REQUIRE(any_result.second == 0);
|
||||
|
||||
// Some signaled
|
||||
REQUIRE(timer1->SetOnce(1ms));
|
||||
all_result = WaitAll({timer0.get(), timer1.get()}, false, 100ms);
|
||||
REQUIRE(all_result == WaitResult::kTimeout);
|
||||
any_result = WaitAny({timer0.get(), timer1.get()}, false, 100ms);
|
||||
REQUIRE(any_result.first == WaitResult::kSuccess);
|
||||
REQUIRE(any_result.second == 1);
|
||||
|
||||
// All signaled
|
||||
REQUIRE(timer0->SetOnce(1ms));
|
||||
all_result = WaitAll({timer0.get(), timer1.get()}, false, 100ms);
|
||||
REQUIRE(all_result == WaitResult::kSuccess);
|
||||
REQUIRE(timer0->SetOnce(1ms));
|
||||
Sleep(1ms);
|
||||
any_result = WaitAny({timer0.get(), timer1.get()}, false, 100ms);
|
||||
REQUIRE(any_result.first == WaitResult::kSuccess);
|
||||
REQUIRE(any_result.second == 0);
|
||||
|
||||
// Check that timer0 reset
|
||||
any_result = WaitAny({timer0.get(), timer1.get()}, false, 100ms);
|
||||
REQUIRE(any_result.first == WaitResult::kSuccess);
|
||||
REQUIRE(any_result.second == 1);
|
||||
}
|
||||
|
||||
TEST_CASE("Create and Trigger Timer Callbacks", "Timer") {
|
||||
// TODO(bwrsandman): Check which thread performs callback and timing of
|
||||
// callback
|
||||
REQUIRE(true);
|
||||
}
|
||||
|
||||
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<uint32_t>::max());
|
||||
REQUIRE(current_thread_id() == system_id);
|
||||
|
||||
// TODO(bwrsandman): Test on Thread object
|
||||
}
|
||||
|
||||
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";
|
||||
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") {
|
||||
std::unique_ptr<Thread> 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") {
|
||||
std::unique_ptr<Thread> 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 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") {
|
||||
std::unique_ptr<Thread> 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.
|
||||
}
|
||||
|
||||
} // namespace test
|
||||
} // namespace base
|
||||
} // namespace xe
|
||||
@@ -24,29 +24,56 @@
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
#include "xenia/base/assert.h"
|
||||
|
||||
namespace xe {
|
||||
namespace threading {
|
||||
|
||||
// This is more like an Event with self-reset when returning from Wait()
|
||||
class Fence {
|
||||
public:
|
||||
Fence() : signaled_(false) {}
|
||||
Fence() : signal_state_(0) {}
|
||||
|
||||
void Signal() {
|
||||
std::unique_lock<std::mutex> lock(mutex_);
|
||||
signaled_.store(true);
|
||||
signal_state_ |= SIGMASK_;
|
||||
cond_.notify_all();
|
||||
}
|
||||
|
||||
// Wait for the Fence to be signaled. Clears the signal on return.
|
||||
void Wait() {
|
||||
std::unique_lock<std::mutex> lock(mutex_);
|
||||
while (!signaled_.load()) {
|
||||
assert_true((signal_state_ & ~SIGMASK_) < (SIGMASK_ - 1) &&
|
||||
"Too many threads?");
|
||||
|
||||
// keep local copy to minimize loads
|
||||
auto signal_state = ++signal_state_;
|
||||
for (; !(signal_state & SIGMASK_); signal_state = signal_state_) {
|
||||
cond_.wait(lock);
|
||||
}
|
||||
signaled_.store(false);
|
||||
|
||||
// We can't just clear the signal as other threads may not have read it yet
|
||||
assert_true((signal_state & ~SIGMASK_) > 0); // wait_count > 0
|
||||
if (signal_state == (1 | SIGMASK_)) { // wait_count == 1
|
||||
// Last one out turn off the lights
|
||||
signal_state_ = 0;
|
||||
} else {
|
||||
// Oops, another thread is still waiting, set the new count and keep the
|
||||
// signal.
|
||||
signal_state_ = --signal_state;
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
using state_t_ = uint_fast32_t;
|
||||
static constexpr state_t_ SIGMASK_ = state_t_(1)
|
||||
<< (sizeof(state_t_) * 8 - 1);
|
||||
|
||||
std::mutex mutex_;
|
||||
std::condition_variable cond_;
|
||||
std::atomic<bool> signaled_;
|
||||
// Use the highest bit (sign bit) as the signal flag and the rest to count
|
||||
// waiting threads.
|
||||
volatile state_t_ signal_state_;
|
||||
};
|
||||
|
||||
// Returns the total number of logical processors in the host system.
|
||||
@@ -308,12 +335,12 @@ class Timer : public WaitHandle {
|
||||
std::chrono::milliseconds period,
|
||||
std::function<void()> opt_callback = nullptr) = 0;
|
||||
template <typename Rep, typename Period>
|
||||
void SetRepeating(std::chrono::nanoseconds due_time,
|
||||
bool SetRepeating(std::chrono::nanoseconds due_time,
|
||||
std::chrono::duration<Rep, Period> period,
|
||||
std::function<void()> opt_callback = nullptr) {
|
||||
SetRepeating(due_time,
|
||||
std::chrono::duration_cast<std::chrono::milliseconds>(period),
|
||||
std::move(opt_callback));
|
||||
return SetRepeating(
|
||||
due_time, std::chrono::duration_cast<std::chrono::milliseconds>(period),
|
||||
std::move(opt_callback));
|
||||
}
|
||||
|
||||
// Stops the timer before it can be set to the signaled state and cancels
|
||||
@@ -391,7 +418,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;
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -388,16 +388,16 @@ class Win32Thread : public Win32Handle<Thread> {
|
||||
QueueUserAPC(DispatchApc, handle_, reinterpret_cast<ULONG_PTR>(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;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user