blob: c13ec0ce2e9e0e87be59e6ffd766f0f8d2e73c52 [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 <atomic>
#include <fbl/function.h>
#include <fbl/futex.h>
#include <fbl/macros.h>
#include <lib/zx/channel.h>
#include <lib/zx/thread.h>
#include <zircon/assert.h>
#include <zircon/syscalls.h>
#include <zircon/threads.h>
#include <zircon/types.h>
// Allow up to 4 pointers worth of storage for any lambdas we need to capture
// into a fbl::InlineFunction object (instead of falling back on the implicit
// heap allocation behavior of fbl::Function or std::function)
static constexpr size_t kMaxLambdaStorage = sizeof(void*) * 4;
// A small helper which allows us to poll with a timeout for a condition to
// become true. Sadly, some of the futex-ownership tests require this as there
// is no opportunity for a thread which has become blocked on a futex to signal
// another thread without unblocking. When testing the state of the system
// while we have a thread blocked via zx_futex_wait, the best we can do is have
// the thread give us a signal, and then wait a "reasonable" amount of time for
// the system to achieve the desired state (or not).
using WaitFn = fbl::InlineFunction<bool(void), kMaxLambdaStorage>;
bool WaitFor(zx_duration_t timeout, WaitFn wait_fn);
// A small helper which fetches the Koid for the current thread.
zx_koid_t CurrentThreadKoid();
// A lightweight signal based on an unowned futex which can be used to
// block/unblock threads. Used extensively in the futex ownership tests for
// sequencing thread behavior which would typically just be a bunch of timing
// races in real code.
class Event {
public:
Event() = default;
DISALLOW_COPY_ASSIGN_AND_MOVE(Event);
zx_status_t Wait(zx_duration_t timeout);
void Signal();
void Reset();
private:
fbl::futex_t signaled_ = {0};
};
// A lightweight wrapper for threads which allow us to create threads and have
// then run a quick lambda, while automating much of the boilerplate we need for
// the ownership tests (things like fetching a thread's KOID)
class Thread {
public:
using Thunk = fbl::InlineFunction<int(void), kMaxLambdaStorage>;
enum class State : uint32_t {
WAITING_TO_START,
RUNNING,
WAITING_TO_STOP,
STOPPED,
};
Thread() { Reset(); }
DISALLOW_COPY_ASSIGN_AND_MOVE(Thread);
void Start(const char* name, Thunk thunk);
zx_status_t Stop();
zx_status_t GetRunState(uint32_t* run_state) const;
const zx::thread& handle() const { return handle_; }
zx_koid_t koid() const { return koid_; }
State state() const { return state_.load(); }
private:
static constexpr zx_duration_t THREAD_TIMEOUT = ZX_SEC(15);
static constexpr zx_duration_t THREAD_POLL_INTERVAL = ZX_MSEC(1);
void SetState(State state) { state_.store(state); }
void Reset();
thrd_t thread_;
zx::thread handle_;
zx_koid_t koid_;
Event started_evt_;
Event stop_evt_;
std::atomic<State> state_{State::STOPPED};
Thunk thunk_;
};
// A small wrapper used to launch a process which creates a thread and sends us
// a handle to the thread, then waits until we tell it to terminate. This
// allows us to test the requirement that a process is not allowed to declare a
// thread from another process as the owner of one of its mutexes.
class ExternalThread {
public:
static void SetProgramName(const char* program_name) { program_name_ = program_name; }
static int DoHelperThread();
static const char* helper_flag() { return helper_flag_; }
ExternalThread() = default;
~ExternalThread() { Stop(); }
DISALLOW_COPY_ASSIGN_AND_MOVE(ExternalThread);
void Start();
void Stop();
const zx::thread& thread() const { return external_thread_; }
private:
static const char* program_name_;
static const char* helper_flag_;
zx::thread external_thread_;
zx::channel control_channel_;
};