blob: 7043ded97428a23d73b9394ec89437e12bf92580 [file] [log] [blame]
// Copyright 2020 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 "test_thread.h"
#include <lib/fdio/directory.h>
#include <lib/zx/clock.h>
#include <lib/zx/thread.h>
#include <lib/zx/time.h>
#include <zircon/threads.h>
#include <algorithm>
#include <memory>
#include <random>
#include <fbl/auto_lock.h>
#include "global_stats.h"
#include "random.h"
#include "sync_obj.h"
TestThread::TestThread(const TestThreadBehavior& behavior, zx::profile profile)
: behavior_(behavior), profile_(std::move(profile)) {
ZX_ASSERT(behavior_.path_len_dist.min() <= behavior_.path_len_dist.max());
ZX_ASSERT(behavior_.path_len_dist.max() <= std::size(sync_objs_));
ZX_ASSERT(std::size(sync_obj_deck_) == std::size(sync_objs_));
// Build an array of indices which we will shuffle and use as our sync
// object "deck" when determining which locks we will obtain during an
// iteration.
for (size_t i = 0; i < std::size(sync_obj_deck_); ++i) {
sync_obj_deck_[i] = i;
}
}
TestThread::~TestThread() { ZX_ASSERT(!thread_.has_value()); }
zx_status_t TestThread::InitStatics() {
{
zx::channel channel0, channel1;
zx_status_t status;
if ((status = zx::channel::create(0u, &channel0, &channel1)) != ZX_OK) {
return status;
}
if ((status = fdio_service_connect(
(std::string("/svc/") + fuchsia::scheduler::ProfileProvider::Name_).c_str(),
channel0.release())) != ZX_OK) {
return status;
}
profile_provider_ =
std::make_unique<fuchsia::scheduler::ProfileProvider_SyncProxy>(std::move(channel1));
}
// Create the proper number of mutexes and cond_vars, then shuffle the array
// of object pointers so that the acquisition ordering requirements are
// randomized.
for (size_t i = 0; i < std::size(sync_objs_); ++i) {
if (i < kNumMutexes) {
sync_objs_[i] = std::make_unique<MutexSyncObj>();
} else {
sync_objs_[i] = std::make_unique<CondVarSyncObj>();
}
}
Random::Shuffle(sync_objs_);
return ZX_OK;
}
zx_status_t TestThread::AddThread(const TestThreadBehavior& behavior) {
zx::profile profile;
zx_status_t fidl_status;
zx_status_t status;
if (behavior.profile_type == ProfileType::Fair) {
status =
profile_provider_->GetProfile(behavior.priority, "pi_stress/fair", &fidl_status, &profile);
} else {
status =
profile_provider_->GetDeadlineProfile(behavior.capacity, behavior.deadline, behavior.period,
"pi_stress/deadline", &fidl_status, &profile);
}
if (status != ZX_OK) {
return status;
}
if (fidl_status != ZX_OK) {
return fidl_status;
}
threads_.emplace_back(std::unique_ptr<TestThread>(new TestThread(behavior, std::move(profile))));
thread_dist_ = std::uniform_int_distribution<size_t>{0, threads_.size() - 1};
return ZX_OK;
}
void TestThread::Shutdown() {
// Set the global shutdown flag, in addition to all of the CondVarSyncObj
// shutdown flags.
shutdown_now_.store(true);
for (auto& obj : sync_objs_) {
obj->Shutdown();
}
// Join all of our test threads, then destroy them.
for (auto& t : threads_) {
t->Join();
}
threads_.clear();
profile_provider_.reset();
}
TestThread& TestThread::random_thread() {
ZX_ASSERT(threads_.size() > 0);
return *threads_[Random::Get(thread_dist_)];
}
void TestThread::Start() {
ZX_ASSERT(!thread_.has_value());
thread_ = std::thread([](TestThread* thiz) { thiz->Run(); }, this);
}
void TestThread::ChangeProfile() {
fbl::AutoLock lock{&profile_lock_};
if (!self_->is_valid()) {
return;
}
// If were borrowing a profile, revert to our base profile. Otherwise, apply
// the profile of another thread selected at random.
zx_status_t status = ZX_ERR_INTERNAL;
if (profile_borrowed_) {
status = self_->set_profile(profile_, 0);
global_stats.profiles_reverted.fetch_add(1u);
} else {
status = self_->set_profile(random_thread().profile_, 0);
global_stats.profiles_changed.fetch_add(1u);
}
ZX_ASSERT(status == ZX_OK);
profile_borrowed_ = !profile_borrowed_;
}
void TestThread::Join() {
ZX_ASSERT(shutdown_now_.load());
if (!thread_.has_value()) {
return;
}
thread_->join();
thread_.reset();
}
void TestThread::HoldLocks(size_t deck_ndx) {
// Obtain the next sync object in the sequence
ZX_ASSERT(deck_ndx < path_len_);
SyncObj& sync_obj = *sync_objs_[sync_obj_deck_[deck_ndx]];
sync_obj.Acquire(behavior_);
// Randomly change our profile if needed
if (Random::RollDice(behavior_.self_profile_change_prob)) {
ChangeProfile();
}
// Choose our linger behavior (intermediate or final) and then linger if we
// need to.
size_t next_ndx = deck_ndx + 1;
const bool intermediate = (next_ndx != path_len_);
LingerBehavior& lb = intermediate ? behavior_.intermediate_linger : behavior_.final_linger;
if (Random::RollDice(lb.linger_probability)) {
zx::duration linger_time{Random::Get(lb.time_dist)};
if (Random::RollDice(lb.spin_probability)) {
zx::time deadline = zx::deadline_after(linger_time);
while (zx::clock::get_monotonic() < deadline) {
}
(intermediate ? global_stats.intermediate_spins : global_stats.final_spins).fetch_add(1u);
} else {
zx::nanosleep(zx::deadline_after(linger_time));
(intermediate ? global_stats.intermediate_sleeps : global_stats.final_sleeps).fetch_add(1u);
}
}
// If we have not hit the end, recurse and obtain the next sync object
if (intermediate) {
HoldLocks(next_ndx);
}
sync_obj.Release();
}
void TestThread::Run() {
// Set our profile.
{
fbl::AutoLock profile_lock{&profile_lock_};
ZX_ASSERT(!self_->is_valid());
self_ = zx::unowned_thread{thrd_get_zx_handle(thrd_current())};
zx_status_t status = self_->set_profile(profile_, 0);
ZX_ASSERT(status == ZX_OK);
profile_borrowed_ = false;
}
while (!shutdown_now_.load()) {
// Shuffle the deck
Random::Shuffle(sync_obj_deck_);
// Determine how long our path for this pass will be, then sort the top
// lock_path elements in the deck so that we don't ever have an A/B vs. B/A
// lock ordering problem.
path_len_ = Random::Get(behavior_.path_len_dist);
std::sort(sync_obj_deck_.begin(), sync_obj_deck_.begin() + path_len_);
// Recursively obtain the sync objects, lingering when we hit the final one, then
// release.
HoldLocks();
}
}