blob: 5069a8745aa441188ac8e4682df28e7318947a8a [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 "utils.h"
#include <lib/fdio/spawn.h>
#include <lib/fit/defer.h>
#include <lib/zx/clock.h>
#include <lib/zx/process.h>
#include <lib/zx/time.h>
#include <zircon/process.h>
#include <zircon/processargs.h>
#include <zxtest/zxtest.h>
const char* ExternalThread::program_name_ = nullptr;
const char* ExternalThread::helper_flag_ = "futex-owner-helper";
bool WaitFor(zx::duration timeout, WaitFn wait_fn) {
constexpr zx::duration WAIT_POLL_INTERVAL = zx::msec(1);
ZX_ASSERT(timeout >= zx::sec(0));
zx::time deadline = zx::deadline_after(timeout);
while (!wait_fn()) {
if (zx::clock::get_monotonic() > deadline) {
return false;
}
zx::nanosleep(zx::deadline_after(WAIT_POLL_INTERVAL));
}
return true;
}
zx_koid_t CurrentThreadKoid() {
zx_info_handle_basic info;
zx_status_t res = zx_object_get_info(zx_thread_self(), ZX_INFO_HANDLE_BASIC, &info, sizeof(info),
nullptr, nullptr);
ZX_ASSERT(res == ZX_OK);
return info.koid;
}
zx_status_t Event::Wait(zx::duration timeout) {
zx::time deadline = zx::deadline_after(timeout);
while (signaled_.load(fbl::memory_order_relaxed) == 0) {
zx_status_t res = zx_futex_wait(&signaled_, 0, ZX_HANDLE_INVALID, deadline.get());
if ((res != ZX_OK) && (res != ZX_ERR_BAD_STATE)) {
return res;
}
}
return ZX_OK;
}
void Event::Signal() {
if (signaled_.load(fbl::memory_order_relaxed) == 0) {
signaled_.store(1, fbl::memory_order_relaxed);
zx_futex_wake(&signaled_, UINT32_MAX);
}
}
void Event::Reset() { signaled_.store(0, fbl::memory_order_relaxed); }
void Thread::Reset() {
handle_.reset();
koid_ = ZX_KOID_INVALID;
SetState(State::WAITING_TO_START);
thunk_ = nullptr;
started_evt_.Reset();
stop_evt_.Reset();
}
void Thread::Start(const char* name, Thunk thunk) {
ZX_ASSERT(static_cast<bool>(thunk_) == false);
thunk_ = std::move(thunk);
auto internal_thunk = [](void* ctx) -> int {
auto t = reinterpret_cast<Thread*>(ctx);
// Create a clone of the zx_thread_self handle. This handle is owned by
// the runtime, not owned by us. The runtime will automatically close
// this handle when the thread exits, invalidating it in the process.
// If we want to be able to do things like test to see if a thread state
// has reached DEAD, we need to make our own handle to hold onto. Do so
// now.
//
// Success or fail, make sure we flag ourselves as started before moving
// on. We don't want to hold up the test framework. They will discover
// that we failed to start when they check our handle and discover that
// it failed to duplicate.
zx::unowned_thread thread_self(zx_thread_self());
int ret = static_cast<int>(thread_self->duplicate(ZX_RIGHT_SAME_RIGHTS, &(t->handle_)));
t->koid_ = CurrentThreadKoid();
t->started_evt_.Signal();
if (ret == static_cast<int>(ZX_OK)) {
t->SetState(State::RUNNING);
ret = t->thunk_();
}
t->SetState(State::WAITING_TO_STOP);
t->stop_evt_.Wait(zx::duration::infinite());
t->SetState(State::STOPPED);
return ret;
};
int res = thrd_create_with_name(&thread_, internal_thunk, this, name);
ASSERT_EQ(res, 0);
ASSERT_OK(started_evt_.Wait(THREAD_TIMEOUT));
ASSERT_TRUE(handle_.is_valid());
ASSERT_NE(koid_, ZX_KOID_INVALID);
}
zx_status_t Thread::Stop() {
if (!handle_.is_valid()) {
return ZX_ERR_BAD_STATE;
}
stop_evt_.Signal();
zx::time deadline = zx::deadline_after(THREAD_TIMEOUT);
while (state() != State::STOPPED) {
if (zx::clock::get_monotonic() > deadline) {
return ZX_ERR_TIMED_OUT;
}
zx::nanosleep(zx::deadline_after(THREAD_POLL_INTERVAL));
}
thrd_join(thread_, nullptr);
Reset();
return ZX_OK;
}
zx_status_t Thread::GetRunState(uint32_t* run_state) const {
ZX_DEBUG_ASSERT(run_state != nullptr);
if (!handle_.is_valid()) {
return ZX_ERR_BAD_STATE;
}
zx_info_thread_t info;
zx_status_t res = handle_.get_info(ZX_INFO_THREAD, &info, sizeof(info), nullptr, nullptr);
if (res != ZX_OK) {
return res;
}
*run_state = info.state;
return ZX_OK;
}
int ExternalThread::DoHelperThread() {
// Get our channel to our parent from our environment.
zx::channel remote(zx_take_startup_handle(PA_HND(PA_USER0, 0)));
if (!remote.is_valid()) {
return -__LINE__;
}
// Duplicate our thread handle.
zx::unowned_thread cur_thread(zx_thread_self());
zx::thread thread_copy;
if (cur_thread->duplicate(ZX_RIGHT_SAME_RIGHTS, &thread_copy) != ZX_OK) {
return -__LINE__;
}
// Send a copy of our thread handle back to our our parent.
if (zx_handle_t leaked = thread_copy.release();
remote.write(0, nullptr, 0, &leaked, 1) != ZX_OK) {
return -__LINE__;
}
// Block until our parent closes our control channel, then exit. Do not
// block forever... If the worst happens, we don't want to be leaking
// processes in our test environment. For now, waiting 2 minutes seems like
// a Very Long Time to wait for our parent to give us the all clear.
constexpr zx::duration TIMEOUT(ZX_SEC(120));
zx_status_t wait_res;
wait_res = remote.wait_one(ZX_CHANNEL_PEER_CLOSED, zx::deadline_after(TIMEOUT), nullptr);
return (wait_res == ZX_OK) ? 0 : -__LINE__;
}
void ExternalThread::Start() {
auto on_failure = fit::defer([this]() { Stop(); });
// Make sure that we have a program name and have not already started.
ASSERT_NOT_NULL(program_name_);
ASSERT_EQ(external_thread_.get(), ZX_HANDLE_INVALID);
ASSERT_EQ(control_channel_.get(), ZX_HANDLE_INVALID);
// Create the channel we will use for talking to our external thread.
zx::channel local, remote;
ASSERT_OK(zx::channel::create(0, &local, &remote));
const char* args[] = {program_name_, helper_flag_, nullptr};
struct fdio_spawn_action transfer_channel_action = {
.action = FDIO_SPAWN_ACTION_ADD_HANDLE,
.h = {.id = PA_HND(PA_USER0, 0), .handle = remote.release()}};
zx::process proc;
char err_msg_out[FDIO_SPAWN_ERR_MSG_MAX_LENGTH];
zx_status_t res =
fdio_spawn_etc(ZX_HANDLE_INVALID, FDIO_SPAWN_CLONE_ALL, program_name_, args, nullptr, 1,
&transfer_channel_action, proc.reset_and_get_address(), err_msg_out);
ASSERT_OK(res, "%s", err_msg_out);
// Get our child's thread handle, but do not wait forever.
constexpr zx::duration TIMEOUT(ZX_MSEC(2500));
constexpr zx_signals_t WAKE_SIGS = ZX_CHANNEL_READABLE | ZX_CHANNEL_PEER_CLOSED;
zx_signals_t sigs = 0;
ASSERT_OK(local.wait_one(WAKE_SIGS, zx::deadline_after(TIMEOUT), &sigs));
ASSERT_NE(sigs & ZX_CHANNEL_READABLE, 0);
uint32_t rxed_handles = 0;
ASSERT_EQ(local.read(0, nullptr, external_thread_.reset_and_get_address(), 0, 1, nullptr,
&rxed_handles),
ZX_OK);
ASSERT_EQ(rxed_handles, 1u);
// Things went well! Cancel our on_failure cleanup routine and Stash our
// control channel endpoint, we will close it when it is time for our
// external thread and process to terminate.
control_channel_ = std::move(local);
on_failure.cancel();
}
void ExternalThread::Stop() {
external_thread_.reset();
control_channel_.reset();
}