blob: 82e3c574a386fb5982bdacb25e8c56fb33021472 [file] [log] [blame]
// Copyright 2019 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.
#define _ALL_SOURCE
#include <lib/syslog/cpp/macros.h>
#include <zircon/status.h>
#include <zircon/syscalls.h>
#include <zircon/threads.h>
#include <mutex>
#include <string>
#include <vector>
#include "src/lib/fxl/strings/string_printf.h"
// This binary is meant to be a playground for testing different multi-threading
// behaviour/signaling/border cases.
//
// No code should depends on this, but rather is meant to be a sandox for
// zxdb developers.
namespace {
constexpr int kThreadCount = 5;
struct ThreadContext {
int index = 0;
std::string name;
thrd_t handle;
zx_handle_t zx_handle;
};
const char* ThreadStateToString(uint32_t state) {
switch (state) {
case ZX_THREAD_STATE_NEW:
return "ZX_THREAD_STATE_NEW";
case ZX_THREAD_STATE_RUNNING:
return "ZX_THREAD_STATE_RUNNING";
case ZX_THREAD_STATE_SUSPENDED:
return "ZX_THREAD_STATE_SUSPENDED";
case ZX_THREAD_STATE_BLOCKED:
return "ZX_THREAD_STATE_BLOCKED";
case ZX_THREAD_STATE_DYING:
return "ZX_THREAD_STATE_DYING";
case ZX_THREAD_STATE_DEAD:
return "ZX_THREAD_STATE_DEAD";
case ZX_THREAD_STATE_BLOCKED_EXCEPTION:
return "ZX_THREAD_STATE_BLOCKED_EXCEPTION";
case ZX_THREAD_STATE_BLOCKED_SLEEPING:
return "ZX_THREAD_STATE_BLOCKED_SLEEPING";
case ZX_THREAD_STATE_BLOCKED_FUTEX:
return "ZX_THREAD_STATE_BLOCKED_FUTEX";
case ZX_THREAD_STATE_BLOCKED_PORT:
return "ZX_THREAD_STATE_BLOCKED_PORT";
case ZX_THREAD_STATE_BLOCKED_CHANNEL:
return "ZX_THREAD_STATE_BLOCKED_CHANNEL";
case ZX_THREAD_STATE_BLOCKED_WAIT_ONE:
return "ZX_THREAD_STATE_BLOCKED_WAIT_ONE";
case ZX_THREAD_STATE_BLOCKED_WAIT_MANY:
return "ZX_THREAD_STATE_BLOCKED_WAIT_MANY";
case ZX_THREAD_STATE_BLOCKED_INTERRUPT:
return "ZX_THREAD_STATE_BLOCKED_INTERRUPT";
case ZX_THREAD_STATE_BLOCKED_PAGER:
return "ZX_THREAD_STATE_BLOCKED_PAGER";
default:
break;
}
return "<unknown>";
}
std::mutex kMutex;
#define PRINT(...) \
printf(__VA_ARGS__); \
fflush(stdout)
void __NO_INLINE PrintFunction(ThreadContext* ctx, int i) {
PRINT("%s: message %d\n", ctx->name.c_str(), i);
};
int __NO_INLINE ThreadFunction(void* in) {
ThreadContext* ctx = reinterpret_cast<ThreadContext*>(in);
for (int i = 0; i < 50; i++) {
{
std::lock_guard<std::mutex> lock(kMutex);
PrintFunction(ctx, i);
}
zx_nanosleep(zx_deadline_after(ZX_MSEC(500 * (ctx->index + 1))));
}
return 0;
}
std::unique_ptr<ThreadContext> CreateThread(const std::string& name, thrd_start_t func,
void* ctx = nullptr) {
auto context = std::make_unique<ThreadContext>();
context->name = name;
thrd_create_with_name(&context->handle, func, ctx ? ctx : context.get(), context->name.c_str());
context->zx_handle = thrd_get_zx_handle(context->handle);
return context;
}
std::vector<std::unique_ptr<ThreadContext>> CreateThreads(int count) {
std::vector<std::unique_ptr<ThreadContext>> contexts;
contexts.reserve(count);
for (int i = 0; i < kThreadCount; i++) {
auto context = std::make_unique<ThreadContext>();
context->index = i;
context->name = fxl::StringPrintf("thread-%d", i);
thrd_create_with_name(&context->handle, ThreadFunction, context.get(), context->name.c_str());
context->zx_handle = thrd_get_zx_handle(context->handle);
contexts.push_back(std::move(context));
}
return contexts;
}
// Printing --------------------------------------------------------------------
// Simple application that prints from several threads.
void MultithreadedPrinting() {
auto contexts = CreateThreads(kThreadCount);
for (auto& context : contexts) {
int res = 0;
thrd_join(context->handle, &res);
}
}
// Suspending ------------------------------------------------------------------
void Suspending() {
auto contexts = CreateThreads(kThreadCount);
PRINT("Suspending all the threads.\n");
for (int i = 0; i < kThreadCount; i++) {
zx_handle_t suspend_token;
zx_status_t status = zx_task_suspend(contexts[i]->zx_handle, &suspend_token);
if (status != ZX_OK) {
PRINT("Could not suspend thread %d: %s\n", i, zx_status_get_string(status));
exit(1);
}
}
PRINT("Waiting for suspend notifications.\n");
for (int i = 0; i < kThreadCount; i++) {
zx_signals_t signals;
zx_status_t status = zx_object_wait_one(contexts[i]->zx_handle, ZX_THREAD_SUSPENDED,
zx_deadline_after(ZX_MSEC(100)), &signals);
if (status != ZX_OK) {
PRINT("Could not wait for signal for thread %d: %s\n", i, zx_status_get_string(status));
exit(1);
}
if ((signals & ZX_THREAD_SUSPENDED) == 0) {
PRINT("Did not get suspended signal for thread %d: %d\n", i, signals);
exit(1);
}
PRINT("Successfully suspended thread %i\n", i);
}
}
// Wait State ------------------------------------------------------------------
std::atomic<bool> gEntered = false;
std::atomic<bool> gExit = false;
int InfiniteFunction(void*) {
while (!gExit) {
gEntered = true;
zx_nanosleep(zx_deadline_after(ZX_MSEC(500)));
}
return 0;
}
std::atomic<bool> gSecondStarted = false;
int WaitOnThread(void* ctx) {
gSecondStarted = true;
ThreadContext* first_thread = reinterpret_cast<ThreadContext*>(ctx);
int res = 1;
thrd_join(first_thread->handle, &res);
FX_DCHECK(res == 0);
return 0;
}
zx_info_thread GetThreadState(zx_handle_t thread_handle) {
zx_info_thread info;
zx_status_t status =
zx_object_get_info(thread_handle, ZX_INFO_THREAD, &info, sizeof(info), nullptr, nullptr);
FX_DCHECK(status == ZX_OK) << "Got: " << zx_status_get_string(status);
return info;
}
void WaitState() {
auto first_thread = CreateThread("infinite", InfiniteFunction);
while (!gEntered)
zx_nanosleep(zx_deadline_after(ZX_MSEC(100)));
FX_LOGS(INFO) << "Thread entered infinite loop.";
auto second_thread = CreateThread("wait-on-first", WaitOnThread, first_thread.get());
while (!gSecondStarted)
zx_nanosleep(zx_deadline_after(ZX_MSEC(100)));
FX_LOGS(INFO) << "Created second thread.";
// Print the state of the second thread.
for (int i = 0; i < 10; i++) {
auto info = GetThreadState(second_thread->zx_handle);
FX_LOGS(INFO) << "Got status: " << ThreadStateToString(info.state);
if (info.state == ZX_THREAD_STATE_BLOCKED_FUTEX)
break;
zx_nanosleep(zx_deadline_after(ZX_SEC(1)));
}
{
// Suspend the thread.
zx_handle_t suspend_token;
zx_status_t status = zx_task_suspend(second_thread->zx_handle, &suspend_token);
FX_DCHECK(status == ZX_OK) << "Got: " << zx_status_get_string(status);
// Wait for signal.
zx_signals_t observed;
status = zx_object_wait_one(second_thread->zx_handle, ZX_THREAD_SUSPENDED,
zx_deadline_after(ZX_SEC(1)), &observed);
FX_DCHECK(status == ZX_OK) << "Got: " << zx_status_get_string(status);
FX_DCHECK((observed & ZX_THREAD_SUSPENDED) != 0);
auto info = GetThreadState(second_thread->zx_handle);
FX_LOGS(INFO) << "Got status: " << ThreadStateToString(info.state);
zx_handle_close(suspend_token);
}
FX_LOGS(INFO) << "Exiting.";
gExit = true;
int res = 2000;
FX_DCHECK(thrd_join(second_thread->handle, &res) == thrd_success);
FX_DCHECK(res == 0) << "Res: " << res;
FX_DCHECK(thrd_join(first_thread->handle, &res) == thrd_success);
FX_DCHECK(res == 0) << "Res: " << res;
}
} // namespace
int main(int argc, char* argv[]) {
if (argc == 1 || strcmp(argv[1], "printing") == 0) {
MultithreadedPrinting();
} else if (strcmp(argv[1], "suspending") == 0) {
Suspending();
} else if (strcmp(argv[1], "wait_state") == 0) {
WaitState();
} else {
PRINT("Unknown option: %s\n", argv[1]);
return 1;
}
return 0;
}