blob: 63f5974389b6f6b8db92b95327341b7335e0d528 [file] [log] [blame]
// Copyright 2018 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 <fbl/auto_call.h>
#include <fbl/futex.h>
#include <lib/zx/event.h>
#include <limits>
#include <zircon/process.h>
#include <zircon/syscalls.h>
#include <zircon/types.h>
#include <zxtest/zxtest.h>
#include "utils.h"
namespace {
// A constant which is guaranteed to be an invalid handle, but not equal to the
// special value ZX_HANDLE_INVALID. We use the INVALID sentinel to mean other
// things is certain contexts (like passing nullptr to a function), and for some
// of these tests, we just want a handle which is guaranteed to be simply bad.
//
// The FIXED_BITS_MASK specifies a pair of bits which are guaranteed to be 1 in
// any valid user-mode representation of a handle. We can generate a
// guaranteed-to-be-bad handle by simply inverting this mask.
constexpr zx_handle_t ZX_HANDLE_BAD_BUT_NOT_INVALID =
static_cast<zx_handle_t>(~ZX_HANDLE_FIXED_BITS_MASK);
static_assert(ZX_HANDLE_BAD_BUT_NOT_INVALID != ZX_HANDLE_INVALID,
"ZX_HANDLE_BAD_BUT_NOT_INVALID must not match ZX_HANDLE_INVALID");
// Templated operation adapters which allow us to test the wake operation using
// the same code for zx_wake and zx_requeue
enum class OpType {
kStandard,
kRequeue,
};
template <OpType OPERATION>
struct WakeOperation;
template <>
struct WakeOperation<OpType::kStandard> {
static zx_status_t wake(const fbl::futex_t& wake_futex, uint32_t count) {
return zx_futex_wake(&wake_futex, count);
}
static zx_status_t wake_single_owner(const fbl::futex_t& wake_futex) {
return zx_futex_wake_single_owner(&wake_futex);
}
};
template <>
struct WakeOperation<OpType::kRequeue> {
static zx_status_t wake(const fbl::futex_t& wake_futex, uint32_t count) {
const fbl::futex_t& requeue_futex(0);
return zx_futex_requeue(&wake_futex, count, 0, &requeue_futex, 0u, ZX_HANDLE_INVALID);
}
static zx_status_t wake_single_owner(const fbl::futex_t& wake_futex) {
const fbl::futex_t& requeue_futex(0);
return zx_futex_requeue_single_owner(&wake_futex, 0, &requeue_futex, 0u, ZX_HANDLE_INVALID);
}
};
} // namespace
TEST(FutexOwnershipTestCase, GetOwner) {
fbl::futex_t the_futex(0);
// No one should own our brand new futex right now.
zx_status_t res;
zx_koid_t koid = ~ZX_KOID_INVALID;
res = zx_futex_get_owner(&the_futex, &koid);
ASSERT_OK(res);
ASSERT_EQ(koid, ZX_KOID_INVALID);
// Passing a bad pointer for koid is an error.
res = zx_futex_get_owner(&the_futex, nullptr);
ASSERT_EQ(res, ZX_ERR_INVALID_ARGS);
// Passing a misaligned pointer for the futex is an error.
res = zx_futex_get_owner(
reinterpret_cast<zx_futex_t*>(reinterpret_cast<uintptr_t>(&the_futex) + 1), &koid);
ASSERT_EQ(res, ZX_ERR_INVALID_ARGS);
// Passing a null pointer for the futex is an error.
res = zx_futex_get_owner(nullptr, &koid);
ASSERT_EQ(res, ZX_ERR_INVALID_ARGS);
}
TEST(FutexOwnershipTestCase, Wait) {
fbl::futex_t the_futex(0);
ExternalThread external;
Thread thread1, thread2, thread3;
zx_status_t res;
std::atomic<zx_status_t> t1_res, t2_res;
zx_handle_t test_thread_handle = zx_thread_self();
zx_koid_t test_thread_koid = CurrentThreadKoid();
zx_koid_t koid;
// If things go wrong, and we bail out early, do out best to shut down all
// of the threads we may have started before unwinding our stack state out
// from under them.
auto cleanup = fbl::MakeAutoCall([&]() {
zx_futex_wake(&the_futex, std::numeric_limits<uint32_t>::max());
external.Stop();
thread1.Stop();
thread2.Stop();
thread3.Stop();
});
// Attempt to fetch the owner of the futex. It should be no-one right now.
res = zx_futex_get_owner(&the_futex, &koid);
ASSERT_OK(res);
ASSERT_EQ(koid, ZX_KOID_INVALID);
// Start a thread and have it declare us to be the owner of the futex.
koid = ~ZX_KOID_INVALID;
t1_res.store(ZX_ERR_INTERNAL);
ASSERT_NO_FATAL_FAILURES(thread1.Start("thread_1", [&]() -> int {
t1_res.store(zx_futex_wait(&the_futex, 0, test_thread_handle, ZX_TIME_INFINITE));
return 0;
}));
ASSERT_TRUE(WaitFor(ZX_MSEC(1000), [&]() -> bool {
res = zx_futex_get_owner(&the_futex, &koid);
// Stop waiting if we fail to fetch the owner, or if the koid matches what we expect.
return ((res != ZX_OK) || (koid == test_thread_koid));
}));
ASSERT_OK(res);
ASSERT_EQ(koid, test_thread_koid);
ASSERT_EQ(t1_res.load(), ZX_ERR_INTERNAL); // thread1 is still waiting.
// Start another thread and have it fail to set the futex owner to no one because of
// an expected futex value mismatch.
t2_res.store(ZX_ERR_INTERNAL);
ASSERT_NO_FATAL_FAILURES(thread2.Start("thread_2.0", [&]() -> int {
t2_res.store(zx_futex_wait(&the_futex, 1, ZX_HANDLE_INVALID, ZX_TIME_INFINITE));
return 0;
}));
ASSERT_OK(thread2.Stop());
// The futex owner should not have changed.
res = zx_futex_get_owner(&the_futex, &koid);
ASSERT_OK(res);
ASSERT_EQ(koid, test_thread_koid);
ASSERT_EQ(t2_res.load(), ZX_ERR_BAD_STATE);
// Start a thread and attempt to set the futex owner to the thread doing the
// wait (thread2). This should fail.
t2_res.store(ZX_ERR_INTERNAL);
ASSERT_NO_FATAL_FAILURES(thread2.Start("thread_2.1", [&]() -> int {
t2_res.store(zx_futex_wait(&the_futex, 0, thread2.handle().get(), ZX_TIME_INFINITE));
return 0;
}));
ASSERT_OK(thread2.Stop());
// The futex owner should not have changed.
res = zx_futex_get_owner(&the_futex, &koid);
ASSERT_OK(res);
ASSERT_EQ(koid, test_thread_koid);
ASSERT_EQ(t2_res.load(), ZX_ERR_INVALID_ARGS);
// Start a thread and attempt to set the futex owner to the thread which is
// already waiting (thread1). This should fail.
t2_res.store(ZX_ERR_INTERNAL);
ASSERT_NO_FATAL_FAILURES(thread2.Start("thread_2.2", [&]() -> int {
t2_res.store(zx_futex_wait(&the_futex, 0, thread1.handle().get(), ZX_TIME_INFINITE));
return 0;
}));
ASSERT_OK(thread2.Stop());
// The futex owner should not have changed.
res = zx_futex_get_owner(&the_futex, &koid);
ASSERT_OK(res);
ASSERT_EQ(koid, test_thread_koid);
ASSERT_EQ(t2_res.load(), ZX_ERR_INVALID_ARGS);
// Start a thread and attempt to set the futex owner to a handle which is valid, but is not
// actually a thread.
zx::event not_a_thread;
res = zx::event::create(0, &not_a_thread);
ASSERT_OK(res);
t2_res.store(ZX_ERR_INTERNAL);
ASSERT_NO_FATAL_FAILURES(thread2.Start("thread_2.3", [&]() -> int {
t2_res.store(zx_futex_wait(&the_futex, 0, not_a_thread.get(), ZX_TIME_INFINITE));
return 0;
}));
ASSERT_OK(thread2.Stop());
// The futex owner should not have changed.
res = zx_futex_get_owner(&the_futex, &koid);
ASSERT_OK(res);
ASSERT_EQ(koid, test_thread_koid);
ASSERT_EQ(t2_res.load(), ZX_ERR_WRONG_TYPE);
// Start a thread and attempt to set the futex owner to the handle to a thread in another
// process.
ASSERT_NO_FATAL_FAILURES(external.Start());
t2_res.store(ZX_ERR_INTERNAL);
ASSERT_NO_FATAL_FAILURES(thread2.Start("thread_2.4", [&]() -> int {
t2_res.store(zx_futex_wait(&the_futex, 0, external.thread().get(), ZX_TIME_INFINITE));
return 0;
}));
ASSERT_OK(thread2.Stop());
external.Stop();
res = zx_futex_get_owner(&the_futex, &koid);
ASSERT_OK(res);
ASSERT_EQ(koid, test_thread_koid);
ASSERT_EQ(t2_res.load(), ZX_ERR_INVALID_ARGS);
// Start thread3, just so we have a different owner to assign. Then start
// up thread2 and have it declare thread3 to be the new owner of the futex,
// and finally timeout. Verify that the ownership changes properly, and
// that it does not change when thread2 times out.
ASSERT_NO_FATAL_FAILURES(thread3.Start("thread_3", [&]() -> int { return 0; }));
t2_res.store(ZX_ERR_INTERNAL);
ASSERT_NO_FATAL_FAILURES(thread2.Start("thread_2.5", [&]() -> int {
t2_res.store(
zx_futex_wait(&the_futex, 0, thread3.handle().get(), zx_deadline_after(ZX_MSEC(50))));
return 0;
}));
ASSERT_OK(thread2.Stop());
ASSERT_EQ(t2_res.load(), ZX_ERR_TIMED_OUT);
ASSERT_TRUE(WaitFor(ZX_SEC(15), [&]() -> bool {
res = zx_futex_get_owner(&the_futex, &koid);
// Stop waiting if we fail to fetch the owner, or if the koid matches what we expect.
return ((res != ZX_OK) || (koid == thread3.koid()));
}));
ASSERT_OK(res);
ASSERT_EQ(koid, thread3.koid());
res = zx_futex_get_owner(&the_futex, &koid);
ASSERT_OK(res);
ASSERT_EQ(koid, thread3.koid());
// Start thread2 again and have it reset ownership back to the main test
// thread. This time, do so with a timeout which has already expired.
// Ownership should be changed even if we wait with a timeout which has
// already expired.
t2_res.store(ZX_ERR_INTERNAL);
ASSERT_NO_FATAL_FAILURES(thread2.Start("thread_2.6", [&]() -> int {
t2_res.store(zx_futex_wait(&the_futex, 0, test_thread_handle, 0));
return 0;
}));
ASSERT_OK(thread2.Stop());
ASSERT_EQ(t2_res.load(), ZX_ERR_TIMED_OUT);
ASSERT_TRUE(WaitFor(ZX_SEC(15), [&]() -> bool {
res = zx_futex_get_owner(&the_futex, &koid);
// Stop waiting if we fail to fetch the owner, or if the koid matches what we expect.
return ((res != ZX_OK) || (koid == test_thread_koid));
}));
res = zx_futex_get_owner(&the_futex, &koid);
ASSERT_OK(res);
ASSERT_EQ(koid, test_thread_koid);
// Start a thread and have it attempt to set the futex owner to a value
// which is just a bad handle (but not ZX_HANDLE_INVALID). Attempting to
// wait like this should result in an error of ZX_ERR_BAD_HANDLE
t2_res.store(ZX_ERR_INTERNAL);
ASSERT_NO_FATAL_FAILURES(thread2.Start("thread_2.7", [&]() -> int {
t2_res.store(zx_futex_wait(&the_futex, 0, ZX_HANDLE_BAD_BUT_NOT_INVALID, ZX_TIME_INFINITE));
return 0;
}));
ASSERT_OK(thread2.Stop());
// The futex owner should not have changed, the error should be bad handle.
res = zx_futex_get_owner(&the_futex, &koid);
ASSERT_OK(res);
ASSERT_EQ(koid, test_thread_koid);
ASSERT_EQ(t2_res.load(), ZX_ERR_BAD_HANDLE);
// Do the same test, but this time, pass a bad state value. The state needs
// to be checked and return BAD_STATE before the proposed owner handle is
// validated. Failure to do this in the proper order can lead to a race
// which can cause a job policy exception to fire in mutex code which
// implements priority inheritance; see ZX-4607.
t2_res.store(ZX_ERR_INTERNAL);
ASSERT_NO_FATAL_FAILURES(thread2.Start("thread_2.8", [&]() -> int {
zx_handle_t bad_handle = test_thread_handle & ~ZX_HANDLE_FIXED_BITS_MASK;
t2_res.store(zx_futex_wait(&the_futex, 1, bad_handle, ZX_TIME_INFINITE));
return 0;
}));
ASSERT_OK(thread2.Stop());
// The futex owner should not have changed, the error should be bad state.
res = zx_futex_get_owner(&the_futex, &koid);
ASSERT_OK(res);
ASSERT_EQ(koid, test_thread_koid);
ASSERT_EQ(t2_res.load(), ZX_ERR_BAD_STATE);
// Finally, start second thread and have it succeed in waiting, setting
// the owner of the futex to nothing in the process.
t2_res.store(ZX_ERR_INTERNAL);
ASSERT_NO_FATAL_FAILURES(thread2.Start("thread_2.9", [&]() -> int {
t2_res.store(zx_futex_wait(&the_futex, 0, ZX_HANDLE_INVALID, ZX_TIME_INFINITE));
return 0;
}));
ASSERT_TRUE(WaitFor(ZX_SEC(15), [&]() -> bool {
res = zx_futex_get_owner(&the_futex, &koid);
// Stop waiting if we fail to fetch the owner, or if the koid matches what we expect.
return ((res != ZX_OK) || (koid == ZX_KOID_INVALID));
}));
ASSERT_OK(res);
ASSERT_EQ(koid, ZX_KOID_INVALID);
// Wakeup all of the threads and join
res = zx_futex_wake(&the_futex, std::numeric_limits<uint32_t>::max());
ASSERT_OK(res);
ASSERT_OK(thread1.Stop());
ASSERT_OK(thread2.Stop());
ASSERT_OK(thread3.Stop());
ASSERT_OK(t1_res.load());
ASSERT_OK(t2_res.load());
cleanup.cancel();
}
template <OpType OPERATION>
static void WakeOwnershipTest() {
using do_op = WakeOperation<OPERATION>;
fbl::futex_t the_futex(0);
zx_handle_t test_thread_handle = zx_thread_self();
zx_koid_t test_thread_koid = CurrentThreadKoid();
zx_koid_t koid;
zx_status_t res;
struct WaiterState {
Thread thread;
std::atomic<zx_status_t> res;
bool woken;
} WAITERS[8];
// If things go wrong, and we bail out early, do out best to shut down all
// of the threads we may have started before unwinding our stack state out
// from under them.
auto cleanup = fbl::MakeAutoCall([&]() {
zx_futex_wake(&the_futex, std::numeric_limits<uint32_t>::max());
for (auto& waiter : WAITERS) {
waiter.thread.Stop();
}
});
// Run this test 2 times. The first time, use a traditional wake which
// should always set the futex to "unowned". The second time, use the
// wake_single_owner variant which should assign ownership to the thread
// which was woken.
for (uint32_t pass = 0; pass < 2; ++pass) {
// Start a bunch of threads and have them all declare us to be
// the_futex's owner.
for (auto& waiter : WAITERS) {
waiter.res.store(ZX_ERR_INTERNAL);
waiter.woken = false;
ASSERT_NO_FATAL_FAILURES(waiter.thread.Start(
"wake_test_waiter", [&waiter, &the_futex, test_thread_handle]() -> int {
waiter.res.store(zx_futex_wait(&the_futex, 0, test_thread_handle, ZX_TIME_INFINITE));
return 0;
}));
}
// Wait until all of the threads are blocked.
res = ZX_ERR_INTERNAL;
ASSERT_TRUE(WaitFor(ZX_MSEC(1000), [&WAITERS, &res]() -> bool {
for (const auto& waiter : WAITERS) {
// If we fail to fetch thread state, stop waiting.
uint32_t state;
res = waiter.thread.GetRunState(&state);
if (res != ZX_OK) {
return true;
}
// If this thread is not blocked yet, keep waiting.
if (state != ZX_THREAD_STATE_BLOCKED_FUTEX) {
return false;
}
// If this thread is blocked, but is not in the RUNNING state,
// then it is blocked on the wrong futex (in this case, the
// Thread's stop_event's futex). Stop waiting and report the
// error.
if (waiter.thread.state() != Thread::State::RUNNING) {
res = ZX_ERR_BAD_STATE;
return true;
}
}
// All threads are blocked, we are finished.
return true;
}));
ASSERT_OK(res);
// We should currently be the owner of the futex.
res = zx_futex_get_owner(&the_futex, &koid);
ASSERT_OK(res);
ASSERT_EQ(koid, test_thread_koid);
// If we are testing the wake behavior of zx_futex_requeue_*, then make
// sure that attempting to do a wake op when the wake-futex value verification
// fails does nothing to change the ownership of the futex.
if constexpr (OPERATION == OpType::kRequeue) {
fbl::futex_t requeue_futex(1);
if (pass == 0) {
res = zx_futex_requeue(&the_futex, 1u, 1, &requeue_futex, 0u, ZX_HANDLE_INVALID);
} else {
res = zx_futex_requeue_single_owner(&the_futex, 1, &requeue_futex, 0u, ZX_HANDLE_INVALID);
}
ASSERT_EQ(res, ZX_ERR_BAD_STATE);
// We should still be the owner of the futex.
res = zx_futex_get_owner(&the_futex, &koid);
ASSERT_OK(res);
ASSERT_EQ(koid, test_thread_koid);
// All waiters should still be blocked on our futex.
for (const auto& waiter : WAITERS) {
uint32_t state;
res = waiter.thread.GetRunState(&state);
ASSERT_OK(res);
ASSERT_EQ(state, ZX_THREAD_STATE_BLOCKED_FUTEX);
}
}
// Now wake all of the threads. We don't know or have any guarantee as
// to which thread the kernel is going to choose to wake, so we cannot
// make any assumptions here, just that some thread will be woken.
//
// ++ Pass 0 validation uses the traditonal wake and should result in no
// owner.
// ++ Pass 1 validation uses wake_single_owner and should assign
// ownership to the thread which was woken, until the last thread is
// woken (at which point, there should be no owner as there are no
// waiters).
//
for (uint32_t i = 0; i < fbl::count_of(WAITERS); ++i) {
if (!pass) {
// Wake a thread.
res = do_op::wake(the_futex, 1u);
} else {
res = do_op::wake_single_owner(the_futex);
}
ASSERT_OK(res);
// Wait until at least one thread has finished its lambda, which we
// have not noticed before.
WaiterState* woken_waiter = nullptr;
res = ZX_ERR_INTERNAL;
ASSERT_TRUE(WaitFor(ZX_MSEC(1000), [&WAITERS, &woken_waiter]() -> bool {
for (auto& waiter : WAITERS) {
if (!waiter.woken) {
if (waiter.thread.state() == Thread::State::WAITING_TO_STOP) {
waiter.woken = true;
woken_waiter = &waiter;
return true;
}
}
}
return false;
}));
ASSERT_NOT_NULL(woken_waiter);
ASSERT_OK(woken_waiter->res.load());
// Now check to be sure that ownership was updated properly. It
// should be INVALID if this is pass 0, or if we just woke up the
// last thread.
zx_koid_t expected_koid = (!pass || ((i + 1) == fbl::count_of(WAITERS)))
? ZX_KOID_INVALID
: woken_waiter->thread.koid();
res = zx_futex_get_owner(&the_futex, &koid);
ASSERT_OK(res);
ASSERT_EQ(koid, expected_koid);
// Recycle our thread for the next pass.
ASSERT_OK(woken_waiter->thread.Stop());
}
}
cleanup.cancel();
}
TEST(FutexOwnershipTestCase, Wake) {
ASSERT_NO_FATAL_FAILURES(WakeOwnershipTest<OpType::kStandard>());
}
TEST(FutexOwnershipTestCase, RequeueWake) {
ASSERT_NO_FATAL_FAILURES(WakeOwnershipTest<OpType::kRequeue>());
}
template <OpType OPERATION>
static void WakeZeroOwnershipTest() {
using do_op = WakeOperation<OPERATION>;
fbl::futex_t the_futex(0);
zx_status_t res;
Thread thread1;
std::atomic<zx_status_t> t1_res;
zx_handle_t test_thread_handle = zx_thread_self();
zx_koid_t test_thread_koid = CurrentThreadKoid();
zx_koid_t koid;
uint32_t state;
// If things go wrong, and we bail out early, do out best to shut down all
// of the threads we may have started before unwinding our stack state out
// from under them.
auto cleanup = fbl::MakeAutoCall([&]() {
zx_futex_wake(&the_futex, std::numeric_limits<uint32_t>::max());
thread1.Stop();
});
// Start a thread and have it declare us to be the owner of the futex.
koid = ~ZX_KOID_INVALID;
t1_res.store(ZX_ERR_INTERNAL);
ASSERT_NO_FATAL_FAILURES(thread1.Start("thread_1", [&]() -> int {
t1_res.store(zx_futex_wait(&the_futex, 0, test_thread_handle, ZX_TIME_INFINITE));
return 0;
}));
// Wait until the thread has become blocked on the futex
ASSERT_TRUE(WaitFor(ZX_MSEC(1000), [&]() -> bool {
res = thread1.GetRunState(&state);
// Stop waiting if we fail to fetch the run state, or the thread has
// reached our desired state.
return ((res != ZX_OK) || (state == ZX_THREAD_STATE_BLOCKED_FUTEX));
}));
ASSERT_OK(res);
ASSERT_EQ(state, ZX_THREAD_STATE_BLOCKED_FUTEX);
// We should now be the owner of the futex
res = zx_futex_get_owner(&the_futex, &koid);
ASSERT_OK(res);
ASSERT_EQ(koid, test_thread_koid);
ASSERT_EQ(t1_res.load(), ZX_ERR_INTERNAL); // thread1 is still waiting.
// Attempt to wake zero threads. This should succeed, thread1 should still
// blocked on the futex, and the owner of the futex should now be no one.
res = do_op::wake(the_futex, 0);
ASSERT_OK(res);
// Wait up to 100mSec for the thread to unblock. If it is still blocked on
// the futex after 100mSec, then assume that it is going to remain blocked.
//
// TODO(johngro): Look into changing the need for this. The issue here is
// that the run state of user mode threads is tracked using a helper class
// in ThreadDispatcher called "AutoBlocked". When a thread blocks on a
// futex (for example), it puts an AutoBlocked(BY_FUTEX) on its local stack,
// joins a wait queue, and is suspended. When it resumes and the AutoBlock
// destructor runs, it restores the thread's previous run state.
//
// Because of this, when Thread A wakes Thread B from a futex wait queue,
// the user-mode run state state of thread B is not updated atomically as
// the thread is removed from the wait queue by thread A. If it takes a bit
// of time for thread B to be scheduled again (and run the AutoBlocked
// destructor), then it will appear to be blocked by a futex still, even
// though the thread is actually run-able. Failure to wait for a little bit
// here can lead to a flaky test (esp. under qemu).
//
// Still, as long as this state is not atomically updated by the wake
// operation, the test is always has the potential to flaky, which is why
// the TODO.
ASSERT_FALSE(WaitFor(ZX_MSEC(100), [&]() -> bool {
res = thread1.GetRunState(&state);
return ((res != ZX_OK) || (state != ZX_THREAD_STATE_BLOCKED_FUTEX));
}));
ASSERT_OK(res);
ASSERT_EQ(state, ZX_THREAD_STATE_BLOCKED_FUTEX);
res = zx_futex_get_owner(&the_futex, &koid);
ASSERT_OK(res);
ASSERT_EQ(koid, ZX_KOID_INVALID);
// Finished. Wake up the thread and shut down.
res = zx_futex_wake(&the_futex, std::numeric_limits<uint32_t>::max());
ASSERT_OK(res);
ASSERT_OK(thread1.Stop());
ASSERT_OK(t1_res.load());
cleanup.cancel();
}
TEST(FutexOwnershipTestCase, WakeZero) {
ASSERT_NO_FATAL_FAILURES(WakeZeroOwnershipTest<OpType::kStandard>());
}
TEST(FutexOwnershipTestCase, RequeueWakeZero) {
ASSERT_NO_FATAL_FAILURES(WakeZeroOwnershipTest<OpType::kRequeue>());
}
TEST(FutexOwnershipTestCase, Requeue) {
fbl::futex_t wake_futex(0);
fbl::futex_t requeue_futex(1);
ExternalThread external;
zx::event not_a_thread;
zx_handle_t test_thread_handle = zx_thread_self();
zx_koid_t test_thread_koid = CurrentThreadKoid();
zx_koid_t koid;
zx_status_t res;
struct WaiterState {
Thread thread;
std::atomic<zx_status_t> res;
bool woken;
} WAITERS[8];
// If things go wrong, and we bail out early, do out best to shut down all
// of the threads we may have started before unwinding our stack state out
// from under them.
auto cleanup = fbl::MakeAutoCall([&]() {
zx_futex_wake(&wake_futex, std::numeric_limits<uint32_t>::max());
zx_futex_wake(&requeue_futex, std::numeric_limits<uint32_t>::max());
external.Stop();
for (auto& waiter : WAITERS) {
waiter.thread.Stop();
}
});
// Start a bunch of threads and have them all declare us to be
// the_futex's owner.
for (auto& waiter : WAITERS) {
waiter.res.store(ZX_ERR_INTERNAL);
waiter.woken = false;
ASSERT_NO_FATAL_FAILURES(waiter.thread.Start(
"requeue_test_waiter", [&waiter, &wake_futex, test_thread_handle]() -> int {
waiter.res.store(zx_futex_wait(&wake_futex, 0, test_thread_handle, ZX_TIME_INFINITE));
return 0;
}));
}
// Wait until all of the threads are blocked.
res = ZX_ERR_INTERNAL;
ASSERT_TRUE(WaitFor(ZX_MSEC(1000), [&WAITERS, &res]() -> bool {
for (const auto& waiter : WAITERS) {
// If we fail to fetch thread state, stop waiting.
uint32_t state;
res = waiter.thread.GetRunState(&state);
if (res != ZX_OK) {
return true;
}
// If this thread is not blocked yet, keep waiting.
if (state != ZX_THREAD_STATE_BLOCKED_FUTEX) {
return false;
}
}
// All threads are blocked, we are finished.
return true;
}));
ASSERT_OK(res);
// Create a valid handle which is not a thread. We will need it to make
// sure that it is illegal to set the requeue target to something which is a
// valid handle, but not a thread.
res = zx::event::create(0, &not_a_thread);
ASSERT_OK(res);
// Start a thread in another process. We will need one to make sure that we
// are not allowed to change the owner of the requeue futex to a thread from
// a another process.
ASSERT_NO_FATAL_FAILURES(external.Start());
// A small helper lambda we use to reduce the boilerplate state checks we
// are about to do a number of times.
auto VerifyState = [&](zx_koid_t expected_wake_owner, zx_koid_t expected_requeue_owner) -> void {
zx_koid_t koid;
zx_status_t res;
// Check the owners.
res = zx_futex_get_owner(&wake_futex, &koid);
ASSERT_OK(res);
ASSERT_EQ(koid, expected_wake_owner);
res = zx_futex_get_owner(&requeue_futex, &koid);
ASSERT_OK(res);
ASSERT_EQ(koid, expected_requeue_owner);
// Check each of the waiters.
for (const auto& waiter : WAITERS) {
uint32_t state;
res = waiter.thread.GetRunState(&state);
ASSERT_OK(res);
if (!waiter.woken) {
ASSERT_EQ(state, ZX_THREAD_STATE_BLOCKED_FUTEX);
}
}
};
// OK, basic setup is complete. We should be the owner of the wait futex, no one
// should own the requeue futex, and all threads should be blocked waiting
// on the wait futex (although, at this point in the test, we can only check
// to be sure that the are all blocked by a futex... we don't know which
// one).
ASSERT_NO_FATAL_FAILURES(VerifyState(test_thread_koid, ZX_KOID_INVALID));
// Wake a single thread assigning ownership of the wake thread to it in the
// process, and requeue a single thread from the wake futex to the requeue
// futex (we have no good way to know which one gets requeued, just that it
// has been). Assign ownership of the requeue futex to ourselves in the
// process.
res = zx_futex_requeue_single_owner(&wake_futex, 0, &requeue_futex, 1, test_thread_handle);
ASSERT_OK(res);
// Find the thread we just woke up.
const WaiterState* woken_waiter = nullptr;
res = zx_futex_get_owner(&wake_futex, &koid);
ASSERT_OK(res);
ASSERT_NE(koid, ZX_KOID_INVALID);
ASSERT_NE(koid, test_thread_koid);
for (auto& waiter : WAITERS) {
if (!waiter.woken && (waiter.thread.koid() == koid)) {
waiter.woken = true;
woken_waiter = &waiter;
}
}
ASSERT_NOT_NULL(woken_waiter);
// Wait until it has finished its lambda and waiting for our permission to stop.
ASSERT_TRUE(WaitFor(ZX_MSEC(1000), [woken_waiter]() -> bool {
return (woken_waiter->thread.state() == Thread::State::WAITING_TO_STOP);
}));
zx_koid_t woken_thread_koid = woken_waiter->thread.koid();
ASSERT_NO_FATAL_FAILURES(VerifyState(woken_thread_koid, test_thread_koid));
// Next, start a sequence of failure tests. In each of the tests, attempt
// to wake no threads, but requeue a single thread declaring the owner of
// the requeue futex to be no one.
//
// After each of these tests, nothing should have changed. We should own
// the requeue futex, the thread which was woken during setup should own the
// wake futex, and all of our threads (except the woken thread) should be
// blocked on a futex (we just don't know which one).
//
// Failure Test #1:
// It is illegal to specify either nullptr or a misaligned futex for the
// wake futex.
//
res = zx_futex_requeue(nullptr, 1u, 0, &requeue_futex, 1, ZX_HANDLE_INVALID);
ASSERT_EQ(res, ZX_ERR_INVALID_ARGS);
ASSERT_NO_FATAL_FAILURES(VerifyState(woken_thread_koid, test_thread_koid));
const zx_futex_t* misaligned_wake_futex =
reinterpret_cast<const zx_futex_t*>(reinterpret_cast<uintptr_t>(&wake_futex) + 1);
res = zx_futex_requeue(misaligned_wake_futex, 1u, 0, &requeue_futex, 1, ZX_HANDLE_INVALID);
ASSERT_EQ(res, ZX_ERR_INVALID_ARGS);
ASSERT_NO_FATAL_FAILURES(VerifyState(woken_thread_koid, test_thread_koid));
// Failure Test #2:
// It is illegal to specify either nullptr or a misaligned futex for the
// requeue futex.
//
res = zx_futex_requeue(&wake_futex, 1u, 0, nullptr, 1, ZX_HANDLE_INVALID);
ASSERT_EQ(res, ZX_ERR_INVALID_ARGS);
ASSERT_NO_FATAL_FAILURES(VerifyState(woken_thread_koid, test_thread_koid));
const zx_futex_t* misaligned_requeue_futex =
reinterpret_cast<const zx_futex_t*>(reinterpret_cast<uintptr_t>(&requeue_futex) + 1);
res = zx_futex_requeue(&wake_futex, 1u, 0, misaligned_requeue_futex, 1, ZX_HANDLE_INVALID);
ASSERT_EQ(res, ZX_ERR_INVALID_ARGS);
ASSERT_NO_FATAL_FAILURES(VerifyState(woken_thread_koid, test_thread_koid));
// Failure Test #3:
// It is illegal to use the same futex for both wake and requeue.
//
res = zx_futex_requeue(&wake_futex, 1u, 0, &wake_futex, 1, ZX_HANDLE_INVALID);
ASSERT_EQ(res, ZX_ERR_INVALID_ARGS);
ASSERT_NO_FATAL_FAILURES(VerifyState(woken_thread_koid, test_thread_koid));
// Failure Test #4:
// It is illegal to use an invalid handle value as the new requeue owner
// which is not ZX_HANDLE_INVALID
//
res = zx_futex_requeue(&wake_futex, 1u, 0, &requeue_futex, 1, ZX_HANDLE_BAD_BUT_NOT_INVALID);
ASSERT_EQ(res, ZX_ERR_BAD_HANDLE);
ASSERT_NO_FATAL_FAILURES(VerifyState(woken_thread_koid, test_thread_koid));
// Failure Test #5:
// It is illegal to use an valid handle value which is not a thread.
//
res = zx_futex_requeue(&wake_futex, 1u, 0, &requeue_futex, 1, not_a_thread.get());
ASSERT_EQ(res, ZX_ERR_WRONG_TYPE);
ASSERT_NO_FATAL_FAILURES(VerifyState(woken_thread_koid, test_thread_koid));
// Failure Test #6:
// It is illegal to use an valid thread handle handle from another process.
//
res = zx_futex_requeue(&wake_futex, 1u, 0, &requeue_futex, 1, external.thread().get());
ASSERT_EQ(res, ZX_ERR_INVALID_ARGS);
ASSERT_NO_FATAL_FAILURES(VerifyState(woken_thread_koid, test_thread_koid));
// We don't need our external process anymore.
external.Stop();
// Failure Test #7:
// It is illegal to a thread currently in waiting in either the wait queue
// or the requeue queue. We don't really know which thread is which at this
// point in time, but trying them all should cover both cases.
//
for (const auto& waiter : WAITERS) {
if (waiter.woken) {
continue;
}
res = zx_futex_requeue(&wake_futex, 1u, 0, &requeue_futex, 1, waiter.thread.handle().get());
ASSERT_EQ(res, ZX_ERR_INVALID_ARGS);
ASSERT_NO_FATAL_FAILURES(VerifyState(woken_thread_koid, test_thread_koid));
}
// Failure Test #8:
// Nothing should change if we fail to validate the wake futex state.
//
res = zx_futex_requeue(&wake_futex, 1u, 1, &requeue_futex, 1, ZX_HANDLE_INVALID);
ASSERT_EQ(res, ZX_ERR_BAD_STATE);
ASSERT_NO_FATAL_FAILURES(VerifyState(woken_thread_koid, test_thread_koid));
// Failure Test #9:
// If we pass a bad/invalid handle as the new requeue owner, _and_ we pass a
// value which does not equal the current futex state by the time we make it
// into the futex context lock in the kernel, then the operation should fail
// and error code we get back should be BAD_STATE, not BAD_HANDLE. The
// state needs to be validated _before_ we concern ourselves with the
// validating the potential new owner.
res = zx_futex_requeue(&wake_futex, 1u, 1, &requeue_futex, 1, ZX_HANDLE_BAD_BUT_NOT_INVALID);
ASSERT_EQ(res, ZX_ERR_BAD_STATE);
ASSERT_NO_FATAL_FAILURES(VerifyState(woken_thread_koid, test_thread_koid));
// Time for success tests.
//
// During setup, we woke exactly one thread from the wake futex, and
// requeued exactly one thread from the wake to the requeue futex. So we
// should have 1 thread ready to stop, 1 thread blocked on the requeue
// futex, and the rest of the threads blocked on the wake futex.
//
// Verify that exactly one thread was waiting in the requeue futex by waking
// everyone on the requeue_futex and waiting a little bit to see who becomes
// blocked on the exit event.
//
// Note: See TODO above about possibly eliminating the need to perform this
// arbitrary wait.
auto CountJustWoken = [&WAITERS](bool* timed_out) -> uint32_t {
uint32_t just_woken = 0;
*timed_out = !WaitFor(ZX_MSEC(100), [&]() -> bool {
for (auto& waiter : WAITERS) {
if (!waiter.woken) {
if (waiter.thread.state() == Thread::State::WAITING_TO_STOP) {
++just_woken;
waiter.woken = true;
}
}
}
return false;
});
return just_woken;
};
res = zx_futex_wake(&requeue_futex, std::numeric_limits<uint32_t>::max());
ASSERT_OK(res);
bool timed_out;
uint32_t just_woken;
just_woken = CountJustWoken(&timed_out);
ASSERT_TRUE(timed_out);
ASSERT_EQ(just_woken, 1u);
ASSERT_NO_FATAL_FAILURES(VerifyState(woken_thread_koid, ZX_KOID_INVALID));
// Now requeue exactly two threads, setting the owner to the thread that we
// originally woke up in the process.
res =
zx_futex_requeue(&wake_futex, 0u, 0, &requeue_futex, 2, woken_waiter->thread.handle().get());
ASSERT_OK(res);
ASSERT_NO_FATAL_FAILURES(VerifyState(ZX_KOID_INVALID, woken_thread_koid));
res = zx_futex_wake(&requeue_futex, std::numeric_limits<uint32_t>::max());
ASSERT_OK(res);
just_woken = CountJustWoken(&timed_out);
ASSERT_TRUE(timed_out);
ASSERT_EQ(just_woken, 2u);
ASSERT_NO_FATAL_FAILURES(VerifyState(ZX_KOID_INVALID, ZX_KOID_INVALID));
// Finally, requeue the rest of the threads, setting ownership of the
// requeue futex back to ourselves in the process.
res = zx_futex_requeue(&wake_futex, 0u, 0, &requeue_futex, std::numeric_limits<uint32_t>::max(),
test_thread_handle);
ASSERT_NO_FATAL_FAILURES(VerifyState(ZX_KOID_INVALID, test_thread_koid));
// Verify that all threads were requeued by waking up everyone on the
// requeue futex, and stopping threads.
res = zx_futex_wake(&requeue_futex, std::numeric_limits<uint32_t>::max());
ASSERT_OK(res);
for (auto& waiter : WAITERS) {
ASSERT_OK(waiter.thread.Stop());
waiter.woken = true;
ASSERT_OK(waiter.res.load());
}
// Success!
cleanup.cancel();
}
TEST(FutexOwnershipTestCase, OwnerExit) {
fbl::futex_t the_futex(0);
Thread the_owner;
Thread the_waiter;
std::atomic<zx_status_t> waiter_res;
zx_status_t res;
// If things go wrong, and we bail out early, do out best to shut down all
// of the threads.
auto cleanup = fbl::MakeAutoCall([&]() {
zx_futex_wake(&the_futex, std::numeric_limits<uint32_t>::max());
the_owner.Stop();
the_waiter.Stop();
});
// Start the "owner" thread. Have it do nothing at all. It will end up
// blocking on an internal signal, waiting for us to tell it to stop.
ASSERT_NO_FATAL_FAILURES(the_owner.Start("OwnerExitTest owner", []() -> int { return 0; }));
// Start the "waiter" thread. Have it wait on the futex, and declare the
// owner thread to be the owner of the_futex.
waiter_res.store(ZX_ERR_INTERNAL);
ASSERT_NO_FATAL_FAILURES(the_waiter.Start(
"OwnerExitTest waiter",
[&waiter_res, &the_futex, test_thread_handle = the_owner.handle().get()]() -> int {
waiter_res.store(zx_futex_wait(&the_futex, 0, test_thread_handle, ZX_TIME_INFINITE));
return 0;
}));
// Wait until our waiter has become blocked by the futex.
ASSERT_TRUE(WaitFor(ZX_MSEC(1000), [&the_waiter, &res]() -> bool {
// If we fail to fetch thread state, stop waiting.
uint32_t state;
res = the_waiter.GetRunState(&state);
if (res != ZX_OK) {
return true;
}
// We are done if the thread has reached the BLOCKED_FUTEX state
return (state == ZX_THREAD_STATE_BLOCKED_FUTEX);
}));
ASSERT_OK(res);
// Verify that our futex is owned by our owner thread.
zx_koid_t koid = ~ZX_KOID_INVALID;
res = zx_futex_get_owner(&the_futex, &koid);
ASSERT_OK(res);
ASSERT_EQ(koid, the_owner.koid());
// OK, now let the owner thread exit and wait for ownership of the futex to become
// automatically released.
//
// Note: We cannot actually synchronize with this operation with a
// simple thrd_join for a number of reasons.
//
// 1) A successful join on a thread in the zircon C runtime only
// establishes that the thread has entered into the kernel for the
// last time, never to return again. The thread _will_ achieve
// eventually death at some point in the future, but there is no
// guarantee that it has done so yet.
//
// 2) Final ownership of the OwnedWaitQueue used by the futex is
// released when the kernel portion of the thread achieves kernel
// thread state of THREAD_DEATH. This is a different state from the
// observable user-mode thread state, which becomes
// ZX_THREAD_STATE_DEATH at the very last instant before the thread
// enters the thread lock and transitions the kernel state to
// THREAD_DEATH (releasing ownership in the process).
//
// 3) The only real way to synchronize with achieving kernel
// THREAD_DEATH is during destruction of the kernel ThreadDispatcher
// object. Unfortunately, simply closing the very last user-mode
// handle to the thread is no guarantee of this either as the kernel
// also holds references the ThreadDispatcher is certain situations.
//
// So, the only real choice here is to just wait. We know that since we
// have signalled the thread to exit, and we have successfully joined
// the thread, that it is only a matter of time before it actually
// exits. If something goes wrong here, either our local (absurdly large)
// timeout will fire, or the test framework watchdog will fire.
ASSERT_OK(the_owner.Stop());
res = ZX_ERR_INTERNAL;
ASSERT_TRUE(WaitFor(ZX_SEC(10), [&the_futex, &res]() -> bool {
zx_koid_t koid = ~ZX_KOID_INVALID;
res = zx_futex_get_owner(&the_futex, &koid);
// If we fail to fetch the ownership info, stop waiting.
if (res != ZX_OK) {
return true;
}
// We are done if the futex owner is now INVALID.
return (koid == ZX_KOID_INVALID);
}));
ASSERT_OK(res);
// Release our waiter thread and shut down.
res = zx_futex_wake(&the_futex, std::numeric_limits<uint32_t>::max());
ASSERT_OK(res);
ASSERT_OK(the_waiter.Stop());
ASSERT_OK(waiter_res.load());
cleanup.cancel();
}
int main(int argc, char** argv) {
ExternalThread::SetProgramName(argv[0]);
if ((argc == 2) && !strcmp(argv[1], ExternalThread::helper_flag())) {
return ExternalThread::DoHelperThread();
}
return RUN_ALL_TESTS(argc, argv);
}