- Because setting the timer is scheduled by us but the wait on POSIX is currently scheduled by pthreads, this solves issues on overprovisioned CIs
1146 lines
36 KiB
C++
1146 lines
36 KiB
C++
/**
|
|
******************************************************************************
|
|
* Xenia : Xbox 360 Emulator Research Project *
|
|
******************************************************************************
|
|
* Copyright 2022 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"
|
|
|
|
#define CATCH_CONFIG_ENABLE_CHRONO_STRINGMAKER
|
|
#include "third_party/catch/include/catch.hpp"
|
|
|
|
namespace xe {
|
|
namespace base {
|
|
namespace test {
|
|
using namespace threading;
|
|
using namespace std::chrono_literals;
|
|
|
|
// Helpers to wait on a predicate which do not depend on complex sync primitives
|
|
template <class Clock, class Duration, class Predicate>
|
|
bool spin_wait_until(
|
|
const std::chrono::time_point<Clock, Duration>& timeout_time,
|
|
Predicate stop_waiting) {
|
|
while (!stop_waiting()) {
|
|
if (std::chrono::steady_clock::now() >= timeout_time) {
|
|
return false;
|
|
}
|
|
// Needed for valgrind because it basically runs one thread:
|
|
MaybeYield();
|
|
}
|
|
return true;
|
|
}
|
|
template <class Period, class Rep, class Predicate>
|
|
bool spin_wait_for(const std::chrono::duration<Rep, Period>& rel_time,
|
|
Predicate stop_waiting) {
|
|
return spin_wait_until(std::chrono::steady_clock::now() + rel_time,
|
|
stop_waiting);
|
|
}
|
|
|
|
template <class Predicate>
|
|
void spin_wait(Predicate stop_waiting) {
|
|
while (!stop_waiting()) {
|
|
// Needed for valgrind because it basically runs one thread:
|
|
MaybeYield();
|
|
}
|
|
}
|
|
|
|
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),
|
|
});
|
|
|
|
REQUIRE(spin_wait_for(1s, [&] { return started == 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", "[maybe_yield]") {
|
|
// Run to see if there are any errors
|
|
MaybeYield();
|
|
}
|
|
|
|
TEST_CASE("Sync with Memory Barrier", "[sync_memory]") {
|
|
// 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);
|
|
});
|
|
REQUIRE(thread);
|
|
|
|
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;
|
|
|
|
const Thread* timer_thread = nullptr;
|
|
|
|
// Time the actual sleep duration
|
|
{
|
|
const auto interval = 50ms;
|
|
std::atomic<uint64_t> counter(0);
|
|
auto start = std::chrono::steady_clock::now();
|
|
auto cb = [&counter, &timer_thread] {
|
|
if (counter == 0) {
|
|
timer_thread = Thread::GetCurrentThread();
|
|
} else {
|
|
REQUIRE(Thread::GetCurrentThread() == timer_thread);
|
|
}
|
|
++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, timer_thread] {
|
|
++counter1;
|
|
REQUIRE(Thread::GetCurrentThread() == timer_thread);
|
|
};
|
|
auto cb2 = [&counter2, timer_thread] {
|
|
++counter2;
|
|
REQUIRE(Thread::GetCurrentThread() == timer_thread);
|
|
};
|
|
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);
|
|
}
|
|
|
|
// Test many timers
|
|
{
|
|
const auto interval = 50ms;
|
|
const size_t timer_count = 128;
|
|
std::atomic<uint64_t> counter(0);
|
|
auto cb = [&counter, &timer_thread] {
|
|
++counter;
|
|
REQUIRE(Thread::GetCurrentThread() == timer_thread);
|
|
};
|
|
std::vector<std::unique_ptr<HighResolutionTimer>> timers;
|
|
auto start = std::chrono::steady_clock::now();
|
|
for (size_t i = 0; i < timer_count; i++) {
|
|
timers.emplace_back(HighResolutionTimer::CreateRepeating(interval, cb));
|
|
}
|
|
Sleep(wait_time);
|
|
timers.clear();
|
|
auto duration = std::chrono::steady_clock::now() - start;
|
|
|
|
REQUIRE(duration.count() >= wait_time.count());
|
|
auto ratio = static_cast<uint64_t>(timer_count * duration / interval);
|
|
REQUIRE(counter >= ratio - timer_count);
|
|
REQUIRE(counter <= ratio + timer_count);
|
|
}
|
|
|
|
// Check timer order
|
|
{
|
|
constexpr size_t timer_count = 16;
|
|
using pair_t = std::pair<std::atomic<uint64_t>,
|
|
std::chrono::high_resolution_clock::time_point>;
|
|
std::array<pair_t, timer_count> time_points{};
|
|
auto start = std::chrono::steady_clock::now();
|
|
auto gen_callback = [&timer_thread, &time_points](size_t i) {
|
|
return [&timer_thread, &time_points, i]() {
|
|
auto& pair = time_points[i];
|
|
if (pair.first.fetch_add(1) == 1) {
|
|
pair.second = std::chrono::high_resolution_clock::now();
|
|
pair.first++;
|
|
}
|
|
REQUIRE(Thread::GetCurrentThread() == timer_thread);
|
|
};
|
|
};
|
|
std::vector<std::unique_ptr<HighResolutionTimer>> timers;
|
|
for (size_t i = 0; i < timer_count; i++) {
|
|
timers.emplace_back(HighResolutionTimer::CreateRepeating(
|
|
10ms * (timer_count - i), gen_callback(timer_count - i - 1)));
|
|
}
|
|
REQUIRE(spin_wait_for(2s, [&] {
|
|
return std::all_of(time_points.cbegin(), time_points.cend(),
|
|
[](auto& pair) { return pair.first >= 3; });
|
|
}));
|
|
|
|
timers.clear();
|
|
|
|
REQUIRE(std::is_sorted(
|
|
time_points.cbegin(), time_points.cend(),
|
|
[](auto& left, auto& right) { return left.second < right.second; }));
|
|
}
|
|
}
|
|
|
|
TEST_CASE("Wait on Multiple Handles", "[wait]") {
|
|
auto mutant = Mutant::Create(true);
|
|
REQUIRE(mutant);
|
|
auto semaphore = Semaphore::Create(10, 10);
|
|
REQUIRE(semaphore);
|
|
auto event_ = Event::CreateManualResetEvent(false);
|
|
REQUIRE(event_);
|
|
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);
|
|
REQUIRE(mutant);
|
|
auto event_ = Event::CreateAutoResetEvent(false);
|
|
REQUIRE(event_);
|
|
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);
|
|
REQUIRE(evt);
|
|
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);
|
|
REQUIRE(evt);
|
|
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),
|
|
};
|
|
for (auto& event : events) {
|
|
REQUIRE(event.get() != nullptr);
|
|
}
|
|
|
|
std::atomic_uint threads_started(0);
|
|
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, &threads_started] {
|
|
set_name("1");
|
|
threads_started++;
|
|
auto res = WaitAll({events[1].get(), events[3].get()}, false);
|
|
REQUIRE(res == WaitResult::kSuccess);
|
|
if (res == WaitResult::kSuccess) {
|
|
sign_in(1);
|
|
}
|
|
}),
|
|
std::thread([&events, &sign_in, &threads_started] {
|
|
set_name("2");
|
|
threads_started++;
|
|
auto res = WaitAny({events[0].get(), events[2].get()}, false);
|
|
REQUIRE(res.first == WaitResult::kSuccess);
|
|
if (res.first == WaitResult::kSuccess) {
|
|
sign_in(2);
|
|
}
|
|
}),
|
|
std::thread([&events, &sign_in, &threads_started] {
|
|
set_name("3");
|
|
threads_started++;
|
|
auto res =
|
|
WaitAll({events[0].get(), events[2].get(), events[3].get()}, false);
|
|
REQUIRE(res == WaitResult::kSuccess);
|
|
if (res == WaitResult::kSuccess) {
|
|
sign_in(3);
|
|
}
|
|
}),
|
|
std::thread([&events, &sign_in, &threads_started] {
|
|
set_name("4");
|
|
threads_started++;
|
|
auto res = WaitAny({events[1].get(), events[3].get()}, false);
|
|
REQUIRE(res.first == WaitResult::kSuccess);
|
|
if (res.first == WaitResult::kSuccess) {
|
|
sign_in(4);
|
|
}
|
|
}),
|
|
};
|
|
|
|
// wait for all threads starting up
|
|
REQUIRE(spin_wait_for(1s, [&] { return threads_started == 4; }));
|
|
events[3]->Set(); // Signals thread id=4 and stays on for 1 and 3
|
|
REQUIRE(spin_wait_for(1s, [&] { return index == 1; }));
|
|
events[1]->Set(); // Signals thread id=1
|
|
REQUIRE(spin_wait_for(1s, [&] { return index == 2; }));
|
|
events[0]->Set(); // Signals thread id=2
|
|
REQUIRE(spin_wait_for(1s, [&] { return index == 3; }));
|
|
events[2]->Set(); // Partial signals thread id=3
|
|
events[0]->Set(); // Signals thread id=3
|
|
|
|
for (auto& t : threads) {
|
|
t.join();
|
|
}
|
|
|
|
REQUIRE(index == 4);
|
|
|
|
INFO(order.data());
|
|
REQUIRE(order[0] == '4');
|
|
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);
|
|
REQUIRE(sem);
|
|
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);
|
|
REQUIRE(sem);
|
|
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(sem);
|
|
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);
|
|
REQUIRE(sem);
|
|
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);
|
|
REQUIRE(sem);
|
|
// Occupy the semaphore with 5 threads
|
|
std::atomic<int> wait_count(0);
|
|
std::atomic<bool> threads_terminate(false);
|
|
auto func = [&sem, &wait_count, &threads_terminate] {
|
|
auto res = Wait(sem.get(), false, 100ms);
|
|
wait_count++;
|
|
|
|
REQUIRE(spin_wait_for(2s, [&] { return threads_terminate.load(); }));
|
|
|
|
REQUIRE(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),
|
|
};
|
|
// Wait for threads to finish semaphore calls
|
|
REQUIRE(spin_wait_for(1s, [&] { return wait_count == 5; }));
|
|
// 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
|
|
threads_terminate = true;
|
|
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_CASE("Invalid semaphore parameters", "[semaphore]") {
|
|
std::unique_ptr<Semaphore> sem;
|
|
|
|
// Test invalid construction parameters
|
|
// These are invalid according to documentation
|
|
sem = Semaphore::Create(-1, 5);
|
|
REQUIRE(sem == nullptr);
|
|
sem = Semaphore::Create(10, 5);
|
|
REQUIRE(sem == nullptr);
|
|
sem = Semaphore::Create(0, 0);
|
|
REQUIRE(sem == nullptr);
|
|
sem = Semaphore::Create(0, -1);
|
|
REQUIRE(sem == nullptr);
|
|
sem = Semaphore::Create(-1, 0);
|
|
REQUIRE(sem == 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);
|
|
REQUIRE(sem0);
|
|
REQUIRE(sem1);
|
|
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);
|
|
REQUIRE(sem0);
|
|
REQUIRE(sem1);
|
|
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);
|
|
REQUIRE(sem0);
|
|
REQUIRE(sem1);
|
|
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);
|
|
REQUIRE(sem0);
|
|
REQUIRE(sem1);
|
|
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
|
|
std::atomic<unsigned int> step(0);
|
|
auto thread1 = std::thread([&mut, &step] {
|
|
mut = Mutant::Create(true);
|
|
step++; // 1
|
|
REQUIRE(spin_wait_for(2s, [&] { return step == 2; }));
|
|
mut->Release();
|
|
});
|
|
REQUIRE(spin_wait_for(1s, [&] { return step == 1; }));
|
|
REQUIRE_FALSE(mut->Release());
|
|
result = Wait(mut.get(), false, 50ms);
|
|
REQUIRE(result == WaitResult::kTimeout);
|
|
step++; // 2
|
|
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;
|
|
std::atomic<unsigned int> step(0);
|
|
|
|
// Test which should fail for WaitAll and WaitAny
|
|
auto thread0 = std::thread([&mut0, &mut1, &step] {
|
|
mut0 = Mutant::Create(true);
|
|
mut1 = Mutant::Create(true);
|
|
step++; // 1
|
|
REQUIRE(spin_wait_for(2s, [&] { return step == 2; }));
|
|
mut0->Release();
|
|
mut1->Release();
|
|
});
|
|
REQUIRE(spin_wait_for(1s, [&] { return step == 1; }));
|
|
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());
|
|
step++; // 2
|
|
thread0.join();
|
|
|
|
// Test which should fail for WaitAll but not WaitAny
|
|
step = 0;
|
|
auto thread1 = std::thread([&mut0, &mut1, &step] {
|
|
mut0 = Mutant::Create(true);
|
|
mut1 = Mutant::Create(false);
|
|
step++; // 1
|
|
REQUIRE(spin_wait_for(2s, [&] { return step == 2; }));
|
|
mut0->Release();
|
|
});
|
|
REQUIRE(spin_wait_for(1s, [&] { return step == 1; }));
|
|
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());
|
|
step++; // 2
|
|
thread1.join();
|
|
|
|
// Test which should pass for WaitAll and WaitAny
|
|
step = 0;
|
|
auto thread2 = std::thread([&mut0, &mut1, &step] {
|
|
mut0 = Mutant::Create(false);
|
|
mut1 = Mutant::Create(false);
|
|
step++; // 1
|
|
REQUIRE(spin_wait_for(2s, [&] { return step == 2; }));
|
|
});
|
|
REQUIRE(spin_wait_for(1s, [&] { return step == 1; }));
|
|
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());
|
|
step++; // 2
|
|
thread2.join();
|
|
}
|
|
|
|
TEST_CASE("Wait on Timer", "[timer]") {
|
|
WaitResult result;
|
|
std::unique_ptr<Timer> timer;
|
|
|
|
// Test Manual Reset
|
|
timer = Timer::CreateManualResetTimer();
|
|
REQUIRE(timer);
|
|
result = Wait(timer.get(), false, 1ms);
|
|
REQUIRE(result == WaitResult::kTimeout);
|
|
REQUIRE(timer->SetOnceAfter(1ms)); // Signals it
|
|
result = Wait(timer.get(), false, 20ms);
|
|
REQUIRE(result == WaitResult::kSuccess);
|
|
result = Wait(timer.get(), false, 1ms);
|
|
REQUIRE(result == WaitResult::kSuccess); // Did not reset
|
|
|
|
// Test Synchronization
|
|
timer = Timer::CreateSynchronizationTimer();
|
|
REQUIRE(timer);
|
|
result = Wait(timer.get(), false, 1ms);
|
|
REQUIRE(result == WaitResult::kTimeout);
|
|
REQUIRE(timer->SetOnceAfter(1ms)); // Signals it
|
|
result = Wait(timer.get(), false, 20ms);
|
|
REQUIRE(result == WaitResult::kSuccess);
|
|
result = Wait(timer.get(), false, 1ms);
|
|
REQUIRE(result == WaitResult::kTimeout); // Did reset
|
|
|
|
// Test long due time
|
|
timer = Timer::CreateSynchronizationTimer();
|
|
REQUIRE(timer->SetOnceAfter(10s));
|
|
result = Wait(timer.get(), false, 10ms);
|
|
REQUIRE(result == WaitResult::kTimeout);
|
|
|
|
// Test Repeating
|
|
REQUIRE(timer->SetRepeatingAfter(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->SetRepeatingAfter(1ms, 10ms));
|
|
for (int i = 0; i < 10; ++i) {
|
|
result = Wait(timer.get(), false, 20ms);
|
|
REQUIRE(result == WaitResult::kSuccess);
|
|
}
|
|
REQUIRE(timer->SetOnceAfter(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->SetOnceAfter(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->SetOnceAfter(1ms));
|
|
all_result = WaitAll({timer0.get(), timer1.get()}, false, 100ms);
|
|
REQUIRE(all_result == WaitResult::kSuccess);
|
|
REQUIRE(timer0->SetOnceAfter(1ms));
|
|
Sleep(2ms);
|
|
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 = {};
|
|
std::atomic<unsigned int> fence(0);
|
|
auto func = [&fence] {
|
|
REQUIRE(spin_wait_for(1s, [&] { return fence == 1; }));
|
|
fence++;
|
|
};
|
|
|
|
SECTION("Create most basic case of thread") {
|
|
fence = 0;
|
|
thread = Thread::Create(params, func);
|
|
REQUIRE(thread->native_handle() != nullptr);
|
|
REQUIRE_NOTHROW(thread->affinity_mask());
|
|
REQUIRE(thread->name().empty());
|
|
fence++;
|
|
result = Wait(thread.get(), false, 1s);
|
|
REQUIRE(result == WaitResult::kSuccess);
|
|
}
|
|
|
|
SECTION("Add thread name") {
|
|
fence = 0;
|
|
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);
|
|
fence++;
|
|
result = Wait(thread.get(), false, 1s);
|
|
REQUIRE(result == WaitResult::kSuccess);
|
|
}
|
|
|
|
SECTION("Use Terminate to end an infinitely looping thread") {
|
|
thread = Thread::Create(params, [] {
|
|
while (true) {
|
|
Sleep(1ms);
|
|
}
|
|
});
|
|
result = Wait(thread.get(), false, 1s);
|
|
REQUIRE(result == WaitResult::kTimeout);
|
|
thread->Terminate(-1);
|
|
result = Wait(thread.get(), false, 1s);
|
|
REQUIRE(result == WaitResult::kSuccess);
|
|
}
|
|
|
|
SECTION("Call Exit from inside an infinitely looping thread") {
|
|
thread = Thread::Create(params, [] {
|
|
Thread::Exit(-1);
|
|
FAIL("Function must not return");
|
|
while (true)
|
|
;
|
|
});
|
|
result = Wait(thread.get(), false, 1s);
|
|
REQUIRE(result == WaitResult::kSuccess);
|
|
}
|
|
|
|
SECTION("Call timeout wait on self") {
|
|
result = Wait(Thread::GetCurrentThread(), false, 50ms);
|
|
REQUIRE(result == WaitResult::kTimeout);
|
|
}
|
|
|
|
SECTION("16Mb stack size") {
|
|
params.stack_size = 16_MiB;
|
|
thread = Thread::Create(params, [] {
|
|
Thread::Exit(-1);
|
|
FAIL("Function must not return");
|
|
while (true)
|
|
;
|
|
});
|
|
REQUIRE(thread != nullptr);
|
|
result = Wait(thread.get(), false, 1s);
|
|
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
|
|
order++; // 1
|
|
Sleep(90ms);
|
|
order++; // 2
|
|
has_finished = std::atomic_fetch_add_explicit(
|
|
&order, 1, std::memory_order::memory_order_relaxed);
|
|
});
|
|
REQUIRE(!spin_wait_for(50ms, [&] { return order == 2; }));
|
|
REQUIRE(is_modified == -1);
|
|
thread->QueueUserCallback(callback);
|
|
result = Wait(thread.get(), true, 200ms);
|
|
REQUIRE(result == WaitResult::kSuccess);
|
|
REQUIRE(is_modified == -1);
|
|
REQUIRE(has_finished == 2);
|
|
|
|
// With alertable
|
|
order = 0;
|
|
is_modified = -1;
|
|
has_finished = -1;
|
|
thread = Thread::Create(params, [&has_finished, &order] {
|
|
// Using Alertable so callback is registered
|
|
order++; // 1
|
|
AlertableSleep(90ms);
|
|
order++; // 3
|
|
has_finished = std::atomic_fetch_add_explicit(
|
|
&order, 1, std::memory_order::memory_order_relaxed);
|
|
});
|
|
REQUIRE(!spin_wait_for(50ms, [&] { return order == 2; }));
|
|
REQUIRE(is_modified == -1);
|
|
thread->QueueUserCallback(callback);
|
|
result = Wait(thread.get(), true, 200ms);
|
|
REQUIRE(result == WaitResult::kSuccess);
|
|
REQUIRE(is_modified == 1);
|
|
REQUIRE(has_finished == 3);
|
|
|
|
// 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
|
|
order++; // 2
|
|
AlertableSleep(1s);
|
|
FAIL("Thread should have been terminated during alertable sleep");
|
|
while (true)
|
|
;
|
|
});
|
|
REQUIRE(!spin_wait_for(100ms, [&] { return order == 3; })); // timeout
|
|
thread->QueueUserCallback([] { Thread::Exit(0); });
|
|
result = Wait(thread.get(), true, 500ms);
|
|
REQUIRE(result == WaitResult::kSuccess);
|
|
REQUIRE(is_modified == 0);
|
|
REQUIRE(order == 2);
|
|
|
|
// TODO(bwrsandman): Test alertable wait returning kUserCallback by using IO
|
|
// callbacks.
|
|
}
|
|
|
|
} // namespace test
|
|
} // namespace base
|
|
} // namespace xe
|