| // 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; | |
| } |