blob: f60d980cf63f4980c0b157b982907d499663a00a [file] [log] [blame]
// Copyright 2016 The Fuchsia Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include <atomic>
#include <fbl/auto_call.h>
#include <fbl/futex.h>
#include <inttypes.h>
#include <lib/zx/suspend_token.h>
#include <lib/zx/thread.h>
#include <limits.h>
#include <sched.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <threads.h>
#include <time.h>
#include <unistd.h>
#include <unittest/unittest.h>
#include <zircon/syscalls.h>
#include <zircon/threads.h>
#include <zircon/time.h>
#include <zircon/types.h>
static constexpr zx::duration DEFAULT_TIMEOUT = zx::msec(500);
static constexpr zx::duration DEFAULT_POLL_INTERVAL = zx::usec(100);
// Poll until the kernel says that the given thread is blocked on a futex.
static bool WaitUntilBlockedOnSomeFutex(const zx::thread& thread) {
BEGIN_HELPER;
zx::time deadline = zx::deadline_after(DEFAULT_TIMEOUT);
zx::time now;
while ((now = zx::clock::get_monotonic()) < deadline) {
zx_info_thread_t info;
ASSERT_EQ(thread.get_info(ZX_INFO_THREAD, &info,
sizeof(info), nullptr, nullptr), ZX_OK);
if (info.state != ZX_THREAD_STATE_BLOCKED_FUTEX) {
zx::nanosleep(zx::deadline_after(DEFAULT_POLL_INTERVAL));
continue;
}
return true;
}
ASSERT_LT(now.get(), deadline.get(), "timeout waiting for thread to block on futex");
END_HELPER;
}
// This starts a thread which waits on a futex. We can do futex_wake()
// operations and then test whether or not this thread has been woken up.
class TestThread {
public:
TestThread() = default;
~TestThread() { Shutdown(); }
TestThread(const TestThread &) = delete;
TestThread& operator=(const TestThread &) = delete;
static bool AssertWokeThreadCount(const TestThread threads[],
uint32_t total_thread_count,
uint32_t target_woke_count) {
BEGIN_HELPER;
ASSERT_LE(target_woke_count, total_thread_count);
auto CountWoken = [&threads, total_thread_count]() -> uint32_t {
uint32_t ret = 0;
for (uint32_t i = 0; i < total_thread_count; ++i) {
if (threads[i].state() == State::WAIT_RETURNED) {
++ret;
}
}
return ret;
};
// Wait forever until we achieve the target count. If threads are not
// waking up as they should, the test framework should eventually kill
// us.
uint32_t woken;
do {
woken = CountWoken();
} while (woken < target_woke_count);
ASSERT_EQ(CountWoken(), target_woke_count);
// Wait an arbitrary amount of time to be sure that no one else wakes
// up.
//
// TODO(johngro) : It would be really nice if we didn't have to have an
// arbitrary wait here. Unfortunately, I'm not sure that there is any
// amount of time that we can wait and prove that a thread might not
// spuriously wake up in the future.
zx::nanosleep(zx::deadline_after(zx::msec(300)));
ASSERT_EQ(CountWoken(), target_woke_count);
END_HELPER;
}
bool Start(zx_futex_t* futex_addr, zx::duration timeout = zx::duration::infinite()) {
BEGIN_HELPER;
ASSERT_FALSE(thread_handle_.is_valid());
futex_addr_.store(futex_addr, std::memory_order_relaxed);
timeout_ = timeout;
wait_result_.store(ZX_ERR_INTERNAL, std::memory_order_relaxed);
auto ret = thrd_create_with_name(&thread_,
[](void* ctx) -> int { return reinterpret_cast<TestThread*>(ctx)->ThreadFunc(); },
this, "wakeup_test_thread");
ASSERT_EQ(ret, thrd_success, "Error during thread creation");
// Make a copy of our thread's handle so that we have something to query
// re: the thread's status, even if the thread exits out from under us
// (which will invalidate the handled returned by thrd_get_zx_handle
zx::unowned_thread(thrd_get_zx_handle(thread_))->duplicate(ZX_RIGHT_SAME_RIGHTS,
&thread_handle_);
while (state() == State::STARTED) {
sched_yield();
}
// Note that this could fail if futex_wait() gets a spurious wakeup.
EXPECT_EQ(state(), State::ABOUT_TO_WAIT, "wrong state");
// We should only do this after state_ is State::ABOUT_TO_WAIT,
// otherwise it could return when the thread has temporarily
// blocked on a libc-internal futex.
EXPECT_TRUE(WaitUntilBlockedOnSomeFutex(get_thread_handle()));
// This could also fail if futex_wait() gets a spurious wakeup.
EXPECT_EQ(state(), State::ABOUT_TO_WAIT, "wrong state");
END_HELPER;
}
bool Shutdown() {
BEGIN_HELPER;
if (thread_handle_.is_valid()) {
zx_status_t res = thread_handle_.wait_one(ZX_THREAD_TERMINATED,
zx::deadline_after(zx::msec(500)),
NULL);
EXPECT_EQ(res, ZX_OK, "Thread did not terminate in a timely fashion!\n");
if (res == ZX_OK) {
// If we have already explicitly killed this thread, do not
// attempt to join it.
//
// The ZXR runtime currently depends on the thread thread_t
// exiting via it's trampoline in order to properly clean up and
// signal it's join waiters. Attempting to join a thread which
// was explicitly killed using a task syscall will result in a
// hang.
//
// Note: This means that if we kill a thread created using the
// C11 thrd_t routines, then we are fundamentally leaking
// resources (such as the thread's internal handle, the stack,
// the thread's copy of the root VMAR handle, and so on).
// Generally speaking, this is Bad and we should not be doing
// it. Unfortunately, ZXR does not provide a way to kill a
// thread directly, so it is difficult to construct our kill
// test in a way which says in sync with ZXR.
//
// At some point, either we need to figure out a way to keep ZXR
// in sync with direct kill methods (seems difficult), provide a
// way to kill and clean up from ZXR, or come back to this code
// and create threads without using the ZXR routines at all in
// order to avoid this situation. Right now, we just deal with
// the fact that we are leaking resources and that they will be
// cleaned up when the test exits.
if (!explicitly_killed_) {
EXPECT_EQ(thrd_join(thread_, NULL), thrd_success, "thrd_join failed");
}
} else {
EXPECT_EQ(thread_handle_.kill(), ZX_OK, "Failed to kill unresponsive thread!\n");
}
thread_handle_.reset();
}
END_HELPER;
}
bool GetThreadState(zx_thread_state_t* out_state) const {
BEGIN_HELPER;
zx_info_thread_t info;
ASSERT_NONNULL(out_state);
ASSERT_TRUE(thread_handle_.is_valid());
ASSERT_EQ(thread_handle_.get_info(ZX_INFO_THREAD, &info,
sizeof(info), nullptr, nullptr), ZX_OK);
*out_state = info.state;
END_HELPER;
}
bool WaitThreadWoken() const {
BEGIN_HELPER;
zx::time deadline = zx::deadline_after(zx::msec(500));
zx::time now;
while (((now = zx::clock::get_monotonic()) < deadline) &&
(state() != State::WAIT_RETURNED)) {
zx::nanosleep(zx::deadline_after(zx::usec(100)));
}
EXPECT_LT(now.get(), deadline.get(), "timeout waiting for thread wake");
EXPECT_EQ(state(), State::WAIT_RETURNED, "wrong state");
END_HELPER;
}
bool WaitThreadInvoluntarilyTerminated() const {
BEGIN_HELPER;
ASSERT_TRUE(thread_handle_.is_valid());
ASSERT_TRUE(explicitly_killed_);
zx_status_t res = thread_handle_.wait_one(ZX_THREAD_TERMINATED,
zx::deadline_after(zx::msec(500)),
NULL);
EXPECT_EQ(res, ZX_OK, "Thread did not terminate in a timely fashion!\n");
EXPECT_EQ(state(), State::ABOUT_TO_WAIT);
EXPECT_EQ(wait_result(), ZX_ERR_INTERNAL);
END_HELPER;
}
bool AssertThreadBlockedOnFutex() const {
BEGIN_HELPER;
zx_thread_state_t state;
ASSERT_TRUE(GetThreadState(&state));
ASSERT_EQ(state, ZX_THREAD_STATE_BLOCKED_FUTEX);
END_HELPER;
}
bool Kill() {
BEGIN_HELPER;
ASSERT_TRUE(thread_handle_.is_valid());
EXPECT_EQ(thread_handle_.kill(), ZX_OK, "zx_task_kill() failed");
explicitly_killed_ = true;
END_HELPER;
}
const zx::thread& get_thread_handle() const { return thread_handle_; }
zx_status_t wait_result() const { return wait_result_.load(std::memory_order_relaxed); }
private:
enum class State {
STARTED = 100,
ABOUT_TO_WAIT = 200,
WAIT_RETURNED = 300,
};
int ThreadFunc() {
state_.store(State::ABOUT_TO_WAIT, std::memory_order_relaxed);
zx::time deadline = timeout_ == zx::duration::infinite() ?
zx::time::infinite() :
zx::deadline_after(timeout_);
wait_result_.store(zx_futex_wait(futex_addr(), *futex_addr(),
ZX_HANDLE_INVALID, deadline.get()),
std::memory_order_relaxed);
state_.store(State::WAIT_RETURNED, std::memory_order_relaxed);
return 0;
}
State state() const { return state_.load(std::memory_order_relaxed); }
zx_futex_t* futex_addr() const { return futex_addr_.load(std::memory_order_relaxed); }
thrd_t thread_;
std::atomic<zx_status_t> wait_result_{ ZX_ERR_INTERNAL };
std::atomic<zx_futex_t*> futex_addr_{ nullptr };
zx::duration timeout_ = zx::duration::infinite();
zx::thread thread_handle_;
bool explicitly_killed_ = false;
std::atomic<State> state_{ State::STARTED };
};
static bool TestFutexWaitValueMismatch() {
BEGIN_TEST;
int32_t futex_value = 123;
zx_status_t rc = zx_futex_wait(&futex_value, futex_value + 1,
ZX_HANDLE_INVALID, ZX_TIME_INFINITE);
ASSERT_EQ(rc, ZX_ERR_BAD_STATE, "Futex wait should have reurned bad state");
END_TEST;
}
static bool TestFutexWaitTimeout() {
BEGIN_TEST;
int32_t futex_value = 123;
zx_status_t rc = zx_futex_wait(&futex_value, futex_value, ZX_HANDLE_INVALID, 0);
ASSERT_EQ(rc, ZX_ERR_TIMED_OUT, "Futex wait should have reurned timeout");
END_TEST;
}
// This test checks that the timeout in futex_wait() is respected
static bool TestFutexWaitTimeoutElapsed() {
BEGIN_TEST;
int32_t futex_value = 0;
constexpr zx::duration kRelativeDeadline = zx::msec(100);
for (int i = 0; i < 5; ++i) {
zx::time deadline = zx::deadline_after(kRelativeDeadline);
zx_status_t rc = zx_futex_wait(&futex_value, 0, ZX_HANDLE_INVALID, deadline.get());
ASSERT_EQ(rc, ZX_ERR_TIMED_OUT, "wait should time out");
EXPECT_GE(zx::clock::get_monotonic().get(), deadline.get(), "wait returned early");
}
END_TEST;
}
static bool TestFutexWaitBadAddress() {
BEGIN_TEST;
// Check that the wait address is checked for validity.
zx_status_t rc = zx_futex_wait(nullptr, 123, ZX_HANDLE_INVALID, ZX_TIME_INFINITE);
ASSERT_EQ(rc, ZX_ERR_INVALID_ARGS, "Futex wait should have reurned invalid_arg");
END_TEST;
}
// Test that we can wake up a single thread.
bool TestFutexWakeup() {
BEGIN_TEST;
fbl::futex_t futex_value(1);
TestThread thread;
ASSERT_TRUE(thread.Start(&futex_value));
// If something goes wrong and we bail out early, do our best to shut down as cleanly as we can.
auto cleanup = fbl::MakeAutoCall([&]() {
zx_futex_wake(&futex_value, INT_MAX);
thread.Shutdown();
});
ASSERT_EQ(zx_futex_wake(&futex_value, INT_MAX), ZX_OK);
ASSERT_TRUE(thread.WaitThreadWoken());
ASSERT_EQ(thread.wait_result(), ZX_OK);
ASSERT_TRUE(thread.Shutdown());
END_TEST;
}
// Test that we can wake up multiple threads, and that futex_wake() heeds
// the wakeup limit.
bool TestFutexWakeupLimit() {
BEGIN_TEST;
fbl::futex_t futex_value(1);
TestThread threads[4];
// If something goes wrong and we bail out early, do our best to shut down as cleanly as we can.
auto cleanup = fbl::MakeAutoCall([&]() {
zx_futex_wake(&futex_value, INT_MAX);
for (auto& t : threads) {
t.Shutdown();
}
});
for (auto& t : threads) {
ASSERT_TRUE(t.Start(&futex_value));
}
ASSERT_EQ(zx_futex_wake(&futex_value, 2), ZX_OK);
// Test that exactly two threads wake up from the queue. We do not know
// which threads are going to wake up, just that two threads are going to
// wake up.
ASSERT_TRUE(TestThread::AssertWokeThreadCount(threads, countof(threads), 2));
// Clean up: Wake the remaining threads so that they can exit.
ASSERT_EQ(zx_futex_wake(&futex_value, INT_MAX), ZX_OK);
ASSERT_TRUE(TestThread::AssertWokeThreadCount(threads, countof(threads), countof(threads)));
for (auto& t : threads) {
ASSERT_EQ(t.wait_result(), ZX_OK);
ASSERT_TRUE(t.Shutdown());
}
cleanup.cancel();
END_TEST;
}
// Check that futex_wait() and futex_wake() heed their address arguments
// properly. A futex_wait() call on one address should not be woken by a
// futex_wake() call on another address.
bool TestFutexWakeupAddress() {
BEGIN_TEST;
fbl::futex_t futex_value1(1);
fbl::futex_t futex_value2(1);
fbl::futex_t dummy_value(1);
TestThread threads[2];
// If something goes wrong and we bail out early, do our best to shut down as cleanly as we can.
auto cleanup = fbl::MakeAutoCall([&]() {
zx_futex_wake(&futex_value1, INT_MAX);
zx_futex_wake(&futex_value2, INT_MAX);
for (auto& t : threads) {
t.Shutdown();
}
});
ASSERT_TRUE(threads[0].Start(&futex_value1));
ASSERT_TRUE(threads[1].Start(&futex_value2));
ASSERT_EQ(zx_futex_wake(&dummy_value, INT_MAX), ZX_OK);
ASSERT_TRUE(threads[0].AssertThreadBlockedOnFutex());
ASSERT_TRUE(threads[1].AssertThreadBlockedOnFutex());
ASSERT_EQ(zx_futex_wake(&futex_value1, INT_MAX), ZX_OK);
ASSERT_TRUE(threads[0].WaitThreadWoken());
ASSERT_TRUE(threads[1].AssertThreadBlockedOnFutex());
// Clean up: Wake the remaining thread so that it can exit.
ASSERT_EQ(zx_futex_wake(&futex_value2, INT_MAX), ZX_OK);
ASSERT_TRUE(threads[1].WaitThreadWoken());
for (auto& t : threads) {
ASSERT_EQ(t.wait_result(), ZX_OK);
ASSERT_TRUE(t.Shutdown());
}
cleanup.cancel();
END_TEST;
}
bool TestFutexRequeueValueMismatch() {
BEGIN_TEST;
zx_futex_t futex_value1 = 100;
zx_futex_t futex_value2 = 200;
zx_status_t rc = zx_futex_requeue(&futex_value1, 1, futex_value1 + 1,
&futex_value2, 1, ZX_HANDLE_INVALID);
ASSERT_EQ(rc, ZX_ERR_BAD_STATE, "requeue should have returned bad state");
END_TEST;
}
bool TestFutexRequeueSameAddr() {
BEGIN_TEST;
zx_futex_t futex_value = 100;
zx_status_t rc = zx_futex_requeue(&futex_value, 1, futex_value,
&futex_value, 1, ZX_HANDLE_INVALID);
ASSERT_EQ(rc, ZX_ERR_INVALID_ARGS, "requeue should have returned invalid args");
END_TEST;
}
// Test that futex_requeue() can wake up some threads and requeue others.
bool TestFutexRequeue() {
BEGIN_TEST;
fbl::futex_t futex_value1(100);
fbl::futex_t futex_value2(200);
TestThread threads[6];
// If something goes wrong and we bail out early, do our best to shut down as cleanly as we can.
auto cleanup = fbl::MakeAutoCall([&]() {
zx_futex_wake(&futex_value1, INT_MAX);
zx_futex_wake(&futex_value2, INT_MAX);
for (auto& t : threads) {
t.Shutdown();
}
});
for (auto& t : threads) {
ASSERT_TRUE(t.Start(&futex_value1));
}
zx_status_t rc = zx_futex_requeue(&futex_value1, 3, 100,
&futex_value2, 2, ZX_HANDLE_INVALID);
ASSERT_EQ(rc, ZX_OK, "Error in requeue");
// 3 of the threads should have been woken.
ASSERT_TRUE(TestThread::AssertWokeThreadCount(threads, countof(threads), 3));
// Since 2 of the threads should have been requeued, waking all the
// threads on futex_value2 should wake 2 more threads.
ASSERT_EQ(zx_futex_wake(&futex_value2, INT_MAX), ZX_OK);
ASSERT_TRUE(TestThread::AssertWokeThreadCount(threads, countof(threads), 5));
// Clean up: Wake the remaining thread so that it can exit.
ASSERT_EQ(zx_futex_wake(&futex_value1, 1), ZX_OK);
ASSERT_TRUE(TestThread::AssertWokeThreadCount(threads, countof(threads), countof(threads)));
for (auto& t : threads) {
ASSERT_TRUE(t.Shutdown());
}
cleanup.cancel();
END_TEST;
}
// Test the case where futex_wait() times out after having been moved to a
// different queue by futex_requeue(). Check that futex_wait() removes
// itself from the correct queue in that case.
bool TestFutexRequeueUnqueuedOnTimeout() {
BEGIN_TEST;
fbl::futex_t futex_value1(100);
fbl::futex_t futex_value2(200);
TestThread threads[2];
// If something goes wrong and we bail out early, do our best to shut down as cleanly as we can.
auto cleanup = fbl::MakeAutoCall([&]() {
zx_futex_wake(&futex_value1, INT_MAX);
zx_futex_wake(&futex_value2, INT_MAX);
for (auto& t : threads) {
t.Shutdown();
}
});
ASSERT_TRUE(threads[0].Start(&futex_value1, zx::msec(300)));
zx_status_t rc = zx_futex_requeue(&futex_value1, 0, 100,
&futex_value2, INT_MAX, ZX_HANDLE_INVALID);
ASSERT_EQ(rc, ZX_OK, "Error in requeue");
ASSERT_TRUE(threads[1].Start(&futex_value2));
// thread 0 and 1 should now both be waiting on futex_value2. Thread 0
// should timeout in a short while, but thread 1 should still be waiting.
ASSERT_TRUE(threads[0].WaitThreadWoken());
ASSERT_EQ(threads[0].wait_result(), ZX_ERR_TIMED_OUT);
ASSERT_TRUE(threads[1].AssertThreadBlockedOnFutex());
// thread 0 should have removed itself from futex_value2's wait queue,
// so only thread 1 should be waiting on futex_value2. We can test that
// by doing futex_wake() with count=1.
ASSERT_EQ(zx_futex_wake(&futex_value2, 1), ZX_OK);
ASSERT_TRUE(threads[1].WaitThreadWoken());
for (auto& t : threads) {
ASSERT_TRUE(t.Shutdown());
}
cleanup.cancel();
END_TEST;
}
// Test that we can successfully kill a thread that is waiting on a futex,
// and that we can join the thread afterwards. This checks that waiting on
// a futex does not leave the thread in an unkillable state.
bool TestFutexThreadKilled() {
BEGIN_TEST;
fbl::futex_t futex_value1(1);
// TODO(johngro): Is this statement true? It does not seem like it should
// be. The MUSL thread handle should still be valid and keeping the thread
// object alive, even though the thread was killed. In order to avoid
// leaking the handle, it seems like a user would _have_ to join the dead
// thread if we killed it directly using the zircon syscall.
//
// Note: TestThread will ensure the kernel thread died, though
// it's not possible to thrd_join after killing the thread.
TestThread thread;
// If something goes wrong and we bail out early, do our best to shut down as cleanly as we can.
auto cleanup = fbl::MakeAutoCall([&]() {
zx_futex_wake(&futex_value1, INT_MAX);
thread.Shutdown();
});
ASSERT_TRUE(thread.Start(&futex_value1));
ASSERT_TRUE(thread.AssertThreadBlockedOnFutex());
ASSERT_TRUE(thread.Kill());
// Wait for the thread to make it to the DEAD state, and verify that it has
// not managed to update either its wait_result_ or state_ members.
ASSERT_TRUE(thread.WaitThreadInvoluntarilyTerminated());
// TODO: update the way shutdown and kill work so that this is correct.
ASSERT_TRUE(thread.Shutdown());
cleanup.cancel();
END_TEST;
}
// Test that the futex_wait() syscall is restarted properly if the thread
// calling it gets suspended and resumed. (This tests for a bug where the
// futex_wait() syscall would return ZX_ERR_TIMED_OUT and not get restarted by
// the syscall wrapper in the VDSO.)
static bool TestFutexThreadSuspended() {
BEGIN_TEST;
fbl::futex_t futex_value1(1);
TestThread thread;
// If something goes wrong and we bail out early, do our best to shut down as cleanly as we can.
auto cleanup = fbl::MakeAutoCall([&]() {
zx_futex_wake(&futex_value1, INT_MAX);
thread.Shutdown();
});
ASSERT_TRUE(thread.Start(&futex_value1));
zx::suspend_token suspend_token;
ASSERT_EQ(thread.get_thread_handle().suspend(&suspend_token), ZX_OK);
// Wait some time for the thread suspension to take effect.
zx::nanosleep(zx::deadline_after(zx::msec(10)));
ASSERT_EQ(zx_handle_close(suspend_token.release()), ZX_OK);
// Wait some time for the thread to resume and execute.
zx::nanosleep(zx::deadline_after(zx::msec(10)));
ASSERT_TRUE(thread.AssertThreadBlockedOnFutex());
ASSERT_EQ(zx_futex_wake(&futex_value1, 1), ZX_OK);
ASSERT_TRUE(TestThread::AssertWokeThreadCount(&thread, 1, 1));
ASSERT_TRUE(thread.Shutdown());
cleanup.cancel();
END_TEST;
}
// Test that misaligned pointers cause futex syscalls to return a failure.
static bool TestFutexMisaligned() {
BEGIN_TEST;
// Make sure the whole thing is aligned, so the 'futex' member will
// definitely be misaligned.
alignas(zx_futex_t) struct {
uint8_t misalign;
zx_futex_t futex[2];
} __attribute__((packed)) buffer;
zx_futex_t* const futex = &buffer.futex[0];
zx_futex_t* const futex_2 = &buffer.futex[1];
ASSERT_GT(alignof(zx_futex_t), 1);
ASSERT_NE((uintptr_t)futex % alignof(zx_futex_t), 0);
ASSERT_NE((uintptr_t)futex_2 % alignof(zx_futex_t), 0);
// zx_futex_requeue might check the waited-for value before it
// checks the second futex's alignment, so make sure the call is
// valid other than the alignment. (Also don't ask anybody to
// look at uninitialized stack space!)
memset(&buffer, 0, sizeof(buffer));
ASSERT_EQ(zx_futex_wait(futex, 0, ZX_HANDLE_INVALID, ZX_TIME_INFINITE), ZX_ERR_INVALID_ARGS);
ASSERT_EQ(zx_futex_wake(futex, 1), ZX_ERR_INVALID_ARGS);
ASSERT_EQ(zx_futex_requeue(futex, 1, 0, futex_2, 1, ZX_HANDLE_INVALID), ZX_ERR_INVALID_ARGS);
END_TEST;
}
static void log(const char* str) {
zx::time now = zx::clock::get_monotonic();
unittest_printf("[%08" PRIu64 ".%08" PRIu64 "]: %s",
now.get() / 1000000000, now.get() % 1000000000, str);
}
class Event {
public:
Event()
: signaled_(0) {}
void wait() {
if (signaled_ == 0) {
zx_futex_wait(&signaled_, signaled_, ZX_HANDLE_INVALID, ZX_TIME_INFINITE);
}
}
void signal() {
if (signaled_ == 0) {
signaled_ = 1;
zx_futex_wake(&signaled_, UINT32_MAX);
}
}
private:
int32_t signaled_;
};
static Event event;
static int signal_thread1(void* arg) {
log("thread 1 waiting on event\n");
event.wait();
log("thread 1 done\n");
return 0;
}
static int signal_thread2(void* arg) {
log("thread 2 waiting on event\n");
event.wait();
log("thread 2 done\n");
return 0;
}
static int signal_thread3(void* arg) {
log("thread 3 waiting on event\n");
event.wait();
log("thread 3 done\n");
return 0;
}
static bool TestEventSignaling() {
BEGIN_TEST;
thrd_t thread1, thread2, thread3;
log("starting signal threads\n");
thrd_create_with_name(&thread1, signal_thread1, NULL, "thread 1");
thrd_create_with_name(&thread2, signal_thread2, NULL, "thread 2");
thrd_create_with_name(&thread3, signal_thread3, NULL, "thread 3");
zx::nanosleep(zx::deadline_after(zx::msec(300)));
log("signaling event\n");
event.signal();
log("joining signal threads\n");
thrd_join(thread1, NULL);
log("signal_thread 1 joined\n");
thrd_join(thread2, NULL);
log("signal_thread 2 joined\n");
thrd_join(thread3, NULL);
log("signal_thread 3 joined\n");
END_TEST;
}
BEGIN_TEST_CASE(futex_tests)
RUN_TEST(TestFutexWaitValueMismatch);
RUN_TEST(TestFutexWaitTimeout);
RUN_TEST(TestFutexWaitTimeoutElapsed);
RUN_TEST(TestFutexWaitBadAddress);
RUN_TEST(TestFutexWakeup);
RUN_TEST(TestFutexWakeupLimit);
RUN_TEST(TestFutexWakeupAddress);
RUN_TEST(TestFutexRequeueValueMismatch);
RUN_TEST(TestFutexRequeueSameAddr);
RUN_TEST(TestFutexRequeue);
RUN_TEST(TestFutexRequeueUnqueuedOnTimeout);
RUN_TEST(TestFutexThreadKilled);
RUN_TEST(TestFutexThreadSuspended);
RUN_TEST(TestFutexMisaligned);
RUN_TEST(TestEventSignaling);
END_TEST_CASE(futex_tests)