blob: 575c8a0280d5c6c8e28e364dd555c0e8e651751a [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 <errno.h>
#include <lib/fit/defer.h>
#include <lib/zx/thread.h>
#include <sched.h>
#include <threads.h>
#include <zircon/syscalls.h>
#include <zircon/threads.h>
#include <thread>
#include <zxtest/zxtest.h>
namespace {
TEST(C11ThreadTest, ThreadLocalErrno) {
constexpr int kNumThreads = 4;
thrd_t thread[kNumThreads];
struct Args {
int thread_number;
int final_errno;
} args[kNumThreads];
auto ThreadLocalHelper = [](void* arg) -> int {
auto args = static_cast<Args*>(arg);
errno = args->thread_number;
zx::nanosleep(zx::deadline_after(zx::msec(100)));
args->final_errno = errno;
return args->thread_number;
};
for (int i = 0; i < kNumThreads; ++i) {
args[i].thread_number = i;
ASSERT_EQ(thrd_success,
thrd_create_with_name(&thread[i], ThreadLocalHelper, &args[i], "c11 thread test"));
}
for (int i = 0; i < kNumThreads; ++i) {
int return_value = 99;
ASSERT_EQ(thrd_join(thread[i], &return_value), thrd_success);
ASSERT_EQ(return_value, i);
ASSERT_EQ(args[i].final_errno, i);
}
}
TEST(C11ThreadTest, NullNameThreadShouldSucceed) {
auto NameHelper = [](void* arg) -> int { return 0; };
constexpr auto kNullArg = nullptr;
constexpr auto kNullName = nullptr;
constexpr int* kIgnoreRet = nullptr;
thrd_t thread;
ASSERT_EQ(thrd_create_with_name(&thread, NameHelper, kNullArg, kNullName), thrd_success);
ASSERT_EQ(thrd_join(thread, kIgnoreRet), thrd_success);
}
TEST(C11ThreadTest, CreateAndVerifyThreadHandle) {
constexpr int kRandomRet = 5;
auto ThreadHandleHelper = [](void* arg) -> int {
std::atomic<bool>* keep_running = static_cast<std::atomic<bool>*>(arg);
while (*keep_running) {
zx::nanosleep(zx::time::infinite_past());
}
return kRandomRet;
};
thrd_t thread;
std::atomic<bool> keep_running = true;
ASSERT_EQ(thrd_create(&thread, ThreadHandleHelper, &keep_running), thrd_success);
zx_handle_t handle = thrd_get_zx_handle(thread);
ASSERT_NE(handle, ZX_HANDLE_INVALID, "got invalid thread handle");
// Prove this is a valid handle by duplicating it.
zx_handle_t dup_handle;
ASSERT_OK(zx_handle_duplicate(handle, ZX_RIGHT_SAME_RIGHTS, &dup_handle));
ASSERT_OK(zx_handle_close(dup_handle), "failed to close duplicate handle");
keep_running = false;
int return_value;
ASSERT_EQ(thrd_join(thread, &return_value), thrd_success);
ASSERT_EQ(return_value, kRandomRet, "Incorrect return from thread");
}
// Not really C11 threads, but <zircon/threads.h> declares it and no better
// place for the test really.
TEST(CppThreadTest, CreateAndVerifyThreadHandle) {
std::atomic<bool> keep_running = true;
std::thread thread([&keep_running]() {
while (keep_running.load()) {
zx::nanosleep(zx::time::infinite_past());
}
});
auto cleanup = fit::defer([&keep_running, &thread]() {
keep_running.store(false);
thread.join();
});
zx::unowned_thread handle{native_thread_get_zx_handle(thread.native_handle())};
ASSERT_TRUE(handle->is_valid(), "got invalid thread handle");
// Prove this is a valid handle by duplicating it.
zx::thread dup_handle;
ASSERT_OK(handle->duplicate(ZX_RIGHT_SAME_RIGHTS, &dup_handle));
ASSERT_TRUE(dup_handle.is_valid());
dup_handle.reset();
ASSERT_FALSE(dup_handle.is_valid());
}
TEST(C11ThreadTest, DetachedThreadKeepsRunning) {
constexpr zx::duration kWaitEachIteration = zx::usec(10);
constexpr zx::duration kWaitMax = zx::usec(20000);
struct Args {
std::atomic<bool> keep_running;
std::atomic<bool> thread_done;
std::atomic<int> thread_iterations;
const zx::duration kWaitEachIteration;
} args = {true, false, 0, kWaitEachIteration};
auto HandleHelper = [](void* arg) -> int {
Args* args = static_cast<Args*>(arg);
while (args->keep_running.load()) {
zx::nanosleep(zx::deadline_after(args->kWaitEachIteration));
args->thread_iterations.fetch_add(1);
}
args->thread_done.store(true);
return 0;
};
thrd_t thread;
ASSERT_EQ(thrd_create(&thread, HandleHelper, &args), thrd_success);
ASSERT_EQ(thrd_detach(thread), thrd_success);
// observe the thread is still operating
int recorded_thread_iterations = args.thread_iterations.load();
zx::duration time_waited;
while (recorded_thread_iterations == args.thread_iterations.load() && time_waited < kWaitMax) {
zx::nanosleep(zx::deadline_after(kWaitEachIteration));
time_waited += kWaitEachIteration;
}
ASSERT_EQ(args.thread_done.load(), false);
args.keep_running.store(false);
time_waited = zx::duration();
while (!args.thread_done.load() && time_waited < kWaitMax) {
zx::nanosleep(zx::deadline_after(kWaitEachIteration));
time_waited += kWaitEachIteration;
}
ASSERT_TRUE(args.thread_done.load());
}
TEST(C11ThreadTest, LongNameSucceeds) {
auto LongNameHelper = [](void* arg) { return 0; };
// Creating a thread with a super long name should succeed.
constexpr char kLongName[] =
"0123456789012345678901234567890123456789"
"0123456789012345678901234567890123456789";
ASSERT_GT(strlen(kLongName), static_cast<size_t>(ZX_MAX_NAME_LEN - 1), "too short to truncate");
thrd_t thread;
ASSERT_EQ(thrd_create_with_name(&thread, LongNameHelper, nullptr, kLongName), thrd_success);
// Clean up.
EXPECT_EQ(thrd_join(thread, nullptr), thrd_success);
}
TEST(C11ThreadTest, SelfDetachAndFree) {
constexpr int kNumThreads = 1000;
std::atomic<int> num_threads_completed = 0;
struct Args {
thrd_t* thread;
std::atomic<int> detach_status;
std::atomic<int>* num_threads_completed;
} args[kNumThreads];
auto DetachHelper = [](void* arg) -> int {
Args* args = static_cast<Args*>(arg);
args->detach_status.store(thrd_detach(*args->thread));
delete args->thread;
args->num_threads_completed->fetch_add(1);
return 0;
};
for (size_t i = 0; i < kNumThreads; i++) {
args[i].thread = new thrd_t;
args[i].num_threads_completed = &num_threads_completed;
ASSERT_EQ(thrd_create(args[i].thread, DetachHelper, &args[i]), thrd_success);
}
while (num_threads_completed.load() != kNumThreads) {
sched_yield();
}
for (size_t i = 0; i < kNumThreads; i++) {
ASSERT_EQ(args[i].detach_status.load(), thrd_success);
}
}
} // namespace