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