blob: dfe5239f0e25eaf7fb7741686defcb2dab5fce19 [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 <inttypes.h>
#include <limits.h>
#include <zircon/syscalls.h>
#include <zircon/threads.h>
#include <unittest/unittest.h>
#include <sched.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <threads.h>
#include <time.h>
#include <unistd.h>
static bool test_futex_wait_value_mismatch() {
BEGIN_TEST;
int futex_value = 123;
zx_status_t rc = zx_futex_wait(&futex_value, futex_value + 1,
ZX_TIME_INFINITE);
ASSERT_EQ(rc, ZX_ERR_BAD_STATE, "Futex wait should have reurned bad state");
END_TEST;
}
static bool test_futex_wait_timeout() {
BEGIN_TEST;
int futex_value = 123;
zx_status_t rc = zx_futex_wait(&futex_value, futex_value, 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 test_futex_wait_timeout_elapsed() {
BEGIN_TEST;
int futex_value = 0;
constexpr zx_duration_t kRelativeDeadline = ZX_MSEC(500);
for (int i = 0; i < 5; ++i) {
zx_time_t now = zx_time_get(ZX_CLOCK_MONOTONIC);
zx_status_t rc = zx_futex_wait(&futex_value, 0, zx_deadline_after(kRelativeDeadline));
ASSERT_EQ(rc, ZX_ERR_TIMED_OUT, "wait should time out");
zx_time_t elapsed = zx_time_get(ZX_CLOCK_MONOTONIC) - now;
if (elapsed < kRelativeDeadline) {
unittest_printf("\nelapsed %" PRIu64
" < kRelativeDeadline: %" PRIu64 "\n",
elapsed, kRelativeDeadline);
EXPECT_TRUE(false, "wait returned early");
}
}
END_TEST;
}
static bool test_futex_wait_bad_address() {
BEGIN_TEST;
// Check that the wait address is checked for validity.
zx_status_t rc = zx_futex_wait(nullptr, 123, ZX_TIME_INFINITE);
ASSERT_EQ(rc, ZX_ERR_INVALID_ARGS, "Futex wait should have reurned invalid_arg");
END_TEST;
}
// 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(volatile int* futex_addr,
zx_duration_t timeout_in_us = ZX_TIME_INFINITE)
: futex_addr_(futex_addr),
timeout_in_ns_(timeout_in_us) {
auto ret = thrd_create_with_name(&thread_, wakeup_test_thread, this, "wakeup_test_thread");
EXPECT_EQ(ret, thrd_success, "Error during thread creation");
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");
// This should be long enough for wakeup_test_thread() to enter
// futex_wait() and add the thread to the wait queue.
struct timespec wait_time = {0, 100 * 1000000 /* nanoseconds */};
EXPECT_EQ(nanosleep(&wait_time, NULL), 0, "Error in nanosleep");
// This could also fail if futex_wait() gets a spurious wakeup.
EXPECT_EQ(state_, STATE_ABOUT_TO_WAIT, "wrong state");
}
TestThread(const TestThread &) = delete;
TestThread& operator=(const TestThread &) = delete;
~TestThread() {
if (handle_ != ZX_HANDLE_INVALID) {
// kill_thread() was used, so the thrd_t is in undefined state.
// Use the kernel handle to ensure the thread has died.
EXPECT_EQ(zx_object_wait_one(handle_, ZX_THREAD_TERMINATED,
ZX_TIME_INFINITE, NULL), ZX_OK,
"zx_object_wait_one failed on killed thread");
EXPECT_EQ(zx_handle_close(handle_), ZX_OK,
"zx_handle_close failed on killed thread's handle");
// The thrd_t and state associated with it is leaked at this point.
} else {
EXPECT_EQ(thrd_join(thread_, NULL), thrd_success,
"thrd_join failed");
}
}
void assert_thread_woken() {
while (state_ == STATE_ABOUT_TO_WAIT) {
sched_yield();
}
EXPECT_EQ(state_, STATE_WAIT_RETURNED, "wrong state");
}
void assert_thread_not_woken() {
EXPECT_EQ(state_, STATE_ABOUT_TO_WAIT, "wrong state");
}
bool wait_for_timeout() {
ASSERT_EQ(state_, STATE_ABOUT_TO_WAIT, "wrong state");
while (state_ == STATE_ABOUT_TO_WAIT) {
struct timespec wait_time = {0, 50 * 1000000 /* nanoseconds */};
ASSERT_EQ(nanosleep(&wait_time, NULL), 0, "Error during sleep");
}
EXPECT_EQ(state_, STATE_WAIT_RETURNED, "wrong state");
return true;
}
void kill_thread() {
EXPECT_EQ(handle_, ZX_HANDLE_INVALID, "kill_thread called twice??");
EXPECT_EQ(zx_handle_duplicate(thrd_get_zx_handle(thread_),
ZX_RIGHT_SAME_RIGHTS, &handle_),
ZX_OK, "zx_handle_duplicate failed on thread handle");
EXPECT_EQ(zx_task_kill(handle_), ZX_OK, "zx_task_kill() failed");
}
zx_handle_t get_thread_handle() {
return thrd_get_zx_handle(thread_);
}
private:
static int wakeup_test_thread(void* thread_arg) {
TestThread* thread = reinterpret_cast<TestThread*>(thread_arg);
thread->state_ = STATE_ABOUT_TO_WAIT;
zx_time_t deadline = thread->timeout_in_ns_ == ZX_TIME_INFINITE ? ZX_TIME_INFINITE :
zx_deadline_after(thread->timeout_in_ns_);
zx_status_t rc = zx_futex_wait(const_cast<int*>(thread->futex_addr_),
*thread->futex_addr_, deadline);
if (thread->timeout_in_ns_ == ZX_TIME_INFINITE) {
EXPECT_EQ(rc, ZX_OK, "Error while wait");
} else {
EXPECT_EQ(rc, ZX_ERR_TIMED_OUT, "wait should have timedout");
}
thread->state_ = STATE_WAIT_RETURNED;
return 0;
}
thrd_t thread_;
volatile int* futex_addr_;
zx_duration_t timeout_in_ns_;
zx_handle_t handle_ = ZX_HANDLE_INVALID;
volatile enum {
STATE_STARTED = 100,
STATE_ABOUT_TO_WAIT = 200,
STATE_WAIT_RETURNED = 300,
} state_ = STATE_STARTED;
};
void check_futex_wake(volatile int* futex_addr, int nwake) {
// Change *futex_addr just in case our nanosleep() call did not wait
// long enough for futex_wait() to enter the wait queue, although that
// is unlikely. This prevents the test from hanging if that happens,
// though the test will fail because futex_wait() will not return a
// success result.
(*futex_addr)++;
zx_status_t rc = zx_futex_wake(const_cast<int*>(futex_addr), nwake);
EXPECT_EQ(rc, ZX_OK, "error during futex wait");
}
// Test that we can wake up a single thread.
bool test_futex_wakeup() {
BEGIN_TEST;
volatile int futex_value = 1;
TestThread thread(&futex_value);
check_futex_wake(&futex_value, INT_MAX);
thread.assert_thread_woken();
END_TEST;
}
// Test that we can wake up multiple threads, and that futex_wake() heeds
// the wakeup limit.
bool test_futex_wakeup_limit() {
BEGIN_TEST;
volatile int futex_value = 1;
TestThread thread1(&futex_value);
TestThread thread2(&futex_value);
TestThread thread3(&futex_value);
TestThread thread4(&futex_value);
check_futex_wake(&futex_value, 2);
// Test that threads are woken up in the order that they were added to
// the wait queue. This is not necessarily true for the Linux
// implementation of futexes, but it is true for Zircon's
// implementation.
thread1.assert_thread_woken();
thread2.assert_thread_woken();
thread3.assert_thread_not_woken();
thread4.assert_thread_not_woken();
// Clean up: Wake the remaining threads so that they can exit.
check_futex_wake(&futex_value, INT_MAX);
thread3.assert_thread_woken();
thread4.assert_thread_woken();
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 test_futex_wakeup_address() {
BEGIN_TEST;
volatile int futex_value1 = 1;
volatile int futex_value2 = 1;
volatile int dummy_addr = 1;
TestThread thread1(&futex_value1);
TestThread thread2(&futex_value2);
check_futex_wake(&dummy_addr, INT_MAX);
thread1.assert_thread_not_woken();
thread2.assert_thread_not_woken();
check_futex_wake(&futex_value1, INT_MAX);
thread1.assert_thread_woken();
thread2.assert_thread_not_woken();
// Clean up: Wake the remaining thread so that it can exit.
check_futex_wake(&futex_value2, INT_MAX);
thread2.assert_thread_woken();
END_TEST;
}
// Check that when futex_wait() times out, it removes the thread from
// the futex wait queue.
bool test_futex_unqueued_on_timeout() {
BEGIN_TEST;
volatile int futex_value = 1;
zx_status_t rc = zx_futex_wait(const_cast<int*>(&futex_value),
futex_value, zx_deadline_after(1));
ASSERT_EQ(rc, ZX_ERR_TIMED_OUT, "wait should have timedout");
TestThread thread(&futex_value);
// If the earlier futex_wait() did not remove itself from the wait
// queue properly, the following futex_wake() call will attempt to wake
// a thread that is no longer waiting, rather than waking the child
// thread.
check_futex_wake(&futex_value, 1);
thread.assert_thread_woken();
END_TEST;
}
// This tests for a specific bug in list handling.
bool test_futex_unqueued_on_timeout_2() {
BEGIN_TEST;
volatile int futex_value = 10;
TestThread thread1(&futex_value);
TestThread thread2(&futex_value, ZX_MSEC(200));
ASSERT_TRUE(thread2.wait_for_timeout());
// With the bug present, thread2 was removed but the futex wait queue's
// tail pointer still points to thread2. When another thread is
// enqueued, it gets added to the thread2 node and lost.
TestThread thread3(&futex_value);
check_futex_wake(&futex_value, 2);
thread1.assert_thread_woken();
thread3.assert_thread_woken();
END_TEST;
}
// This tests for a specific bug in list handling.
bool test_futex_unqueued_on_timeout_3() {
BEGIN_TEST;
volatile int futex_value = 10;
TestThread thread1(&futex_value, ZX_MSEC(400));
TestThread thread2(&futex_value);
TestThread thread3(&futex_value);
ASSERT_TRUE(thread1.wait_for_timeout());
// With the bug present, thread1 was removed but the futex wait queue
// is set to the thread2 node, which has an invalid (null) tail
// pointer. When another thread is enqueued, we get a null pointer
// dereference or an assertion failure.
TestThread thread4(&futex_value);
check_futex_wake(&futex_value, 3);
thread2.assert_thread_woken();
thread3.assert_thread_woken();
thread4.assert_thread_woken();
END_TEST;
}
bool test_futex_requeue_value_mismatch() {
BEGIN_TEST;
int futex_value1 = 100;
int futex_value2 = 200;
zx_status_t rc = zx_futex_requeue(&futex_value1, 1, futex_value1 + 1,
&futex_value2, 1);
ASSERT_EQ(rc, ZX_ERR_BAD_STATE, "requeue should have returned bad state");
END_TEST;
}
bool test_futex_requeue_same_addr() {
BEGIN_TEST;
int futex_value = 100;
zx_status_t rc = zx_futex_requeue(&futex_value, 1, futex_value,
&futex_value, 1);
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 test_futex_requeue() {
BEGIN_TEST;
volatile int futex_value1 = 100;
volatile int futex_value2 = 200;
TestThread thread1(&futex_value1);
TestThread thread2(&futex_value1);
TestThread thread3(&futex_value1);
TestThread thread4(&futex_value1);
TestThread thread5(&futex_value1);
TestThread thread6(&futex_value1);
zx_status_t rc = zx_futex_requeue(
const_cast<int*>(&futex_value1), 3, futex_value1,
const_cast<int*>(&futex_value2), 2);
ASSERT_EQ(rc, ZX_OK, "Error in requeue");
// 3 of the threads should have been woken.
thread1.assert_thread_woken();
thread2.assert_thread_woken();
thread3.assert_thread_woken();
thread4.assert_thread_not_woken();
thread5.assert_thread_not_woken();
thread6.assert_thread_not_woken();
// Since 2 of the threads should have been requeued, waking all the
// threads on futex_value2 should wake 2 threads.
check_futex_wake(&futex_value2, INT_MAX);
thread4.assert_thread_woken();
thread5.assert_thread_woken();
thread6.assert_thread_not_woken();
// Clean up: Wake the remaining thread so that it can exit.
check_futex_wake(&futex_value1, 1);
thread6.assert_thread_woken();
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 test_futex_requeue_unqueued_on_timeout() {
BEGIN_TEST;
zx_duration_t timeout_in_ns = ZX_MSEC(300);
volatile int futex_value1 = 100;
volatile int futex_value2 = 200;
TestThread thread1(&futex_value1, timeout_in_ns);
zx_status_t rc = zx_futex_requeue(
const_cast<int*>(&futex_value1), 0, futex_value1,
const_cast<int*>(&futex_value2), INT_MAX);
ASSERT_EQ(rc, ZX_OK, "Error in requeue");
TestThread thread2(&futex_value2);
// thread1 and thread2 should now both be waiting on futex_value2.
ASSERT_TRUE(thread1.wait_for_timeout());
thread2.assert_thread_not_woken();
// thread1 should have removed itself from futex_value2's wait queue,
// so only thread2 should be waiting on futex_value2. We can test that
// by doing futex_wake() with count=1.
check_futex_wake(&futex_value2, 1);
thread2.assert_thread_woken();
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 test_futex_thread_killed() {
BEGIN_TEST;
volatile int futex_value = 1;
// Note: TestThread will ensure the kernel thread died, though
// it's not possible to thrd_join after killing the thread.
TestThread thread(&futex_value);
thread.kill_thread();
// Check that the futex_wait() syscall does not return control to
// userland before the thread gets killed.
struct timespec wait_time = {0, 10 * 1000000 /* nanoseconds */};
ASSERT_EQ(nanosleep(&wait_time, NULL), 0, "Error during sleep");
thread.assert_thread_not_woken();
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 test_futex_thread_suspended() {
BEGIN_TEST;
volatile int futex_value = 1;
TestThread thread(&futex_value);
ASSERT_EQ(zx_task_suspend(thread.get_thread_handle()), ZX_OK);
// Wait some time for the thread suspension to take effect.
struct timespec wait_time = {0, 10 * 1000000 /* nanoseconds */};
ASSERT_EQ(nanosleep(&wait_time, NULL), 0, "Error during sleep");
ASSERT_EQ(zx_task_resume(thread.get_thread_handle(), 0), ZX_OK);
// Wait some time for the thread to resume and execute.
ASSERT_EQ(nanosleep(&wait_time, NULL), 0, "Error during sleep");
thread.assert_thread_not_woken();
check_futex_wake(&futex_value, 1);
thread.assert_thread_woken();
END_TEST;
}
// Test that misaligned pointers cause futex syscalls to return a failure.
static bool test_futex_misaligned() {
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_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_ERR_INVALID_ARGS);
END_TEST;
}
static void log(const char* str) {
uint64_t now = zx_time_get(ZX_CLOCK_MONOTONIC);
unittest_printf("[%08" PRIu64 ".%08" PRIu64 "]: %s",
now / 1000000000, now % 1000000000, str);
}
class Event {
public:
Event()
: signaled_(0) {}
void wait() {
if (signaled_ == 0) {
zx_futex_wait(&signaled_, signaled_, ZX_TIME_INFINITE);
}
}
void signal() {
if (signaled_ == 0) {
signaled_ = 1;
zx_futex_wake(&signaled_, UINT32_MAX);
}
}
private:
int signaled_;
};
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 test_event_signaling() {
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(test_futex_wait_value_mismatch);
RUN_TEST(test_futex_wait_timeout);
RUN_TEST(test_futex_wait_timeout_elapsed);
RUN_TEST(test_futex_wait_bad_address);
RUN_TEST(test_futex_wakeup);
RUN_TEST(test_futex_wakeup_limit);
RUN_TEST(test_futex_wakeup_address);
RUN_TEST(test_futex_unqueued_on_timeout);
RUN_TEST(test_futex_unqueued_on_timeout_2);
RUN_TEST(test_futex_unqueued_on_timeout_3);
RUN_TEST(test_futex_requeue_value_mismatch);
RUN_TEST(test_futex_requeue_same_addr);
RUN_TEST(test_futex_requeue);
RUN_TEST(test_futex_requeue_unqueued_on_timeout);
RUN_TEST(test_futex_thread_killed);
RUN_TEST(test_futex_thread_suspended);
RUN_TEST(test_futex_misaligned);
RUN_TEST(test_event_signaling);
END_TEST_CASE(futex_tests)
#ifndef BUILD_COMBINED_TESTS
int main(int argc, char** argv) {
bool success = unittest_run_all_tests(argc, argv);
return success ? 0 : -1;
}
#endif