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