blob: 608d3de944289fbde1fafe73235095b216c480f9 [file] [log] [blame]
// Copyright 2023 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 <lib/driver/runtime/testing/cpp/internal/test_dispatcher_builder.h>
#include <lib/driver/testing/cpp/driver_runtime.h>
#include <lib/fdf/cpp/dispatcher.h>
#include <lib/fdf/cpp/env.h>
#include <lib/fdf/testing.h>
#include <lib/sync/cpp/completion.h>
#include "src/devices/bin/driver_runtime/dispatcher.h"
#include "src/devices/bin/driver_runtime/runtime_test_case.h"
namespace driver_runtime {
extern DispatcherCoordinator& GetDispatcherCoordinator();
}
class DispatcherDumpTest : public RuntimeTestCase {
public:
void SetUp() override;
void TearDown() override;
protected:
static constexpr std::string_view kDispatcherName = "synchronized";
static constexpr std::string_view kAdditionalDispatcherName = "additional";
fdf_testing::internal::DriverRuntimeEnv runtime_env;
fdf::SynchronizedDispatcher dispatcher_;
libsync::Completion shutdown_completion_;
fdf::SynchronizedDispatcher dispatcher2_;
libsync::Completion shutdown_completion2_;
const void* fake_driver_ = nullptr;
const void* fake_driver2_ = nullptr;
};
void DispatcherDumpTest::SetUp() {
driver_runtime::GetDispatcherCoordinator().Reset();
fake_driver_ = CreateFakeDriver();
auto shutdown_handler = [&](fdf_dispatcher_t* shutdown_dispatcher) {
shutdown_completion_.Signal();
};
auto dispatcher = fdf_internal::TestDispatcherBuilder::CreateUnmanagedSynchronizedDispatcher(
fake_driver_, {}, kDispatcherName, shutdown_handler);
ASSERT_OK(dispatcher.status_value());
dispatcher_ = std::move(*dispatcher);
fake_driver2_ = CreateFakeDriver();
auto shutdown_handler2 = [&](fdf_dispatcher_t* shutdown_dispatcher) {
shutdown_completion2_.Signal();
};
auto dispatcher2 = fdf_internal::TestDispatcherBuilder::CreateUnmanagedSynchronizedDispatcher(
fake_driver2_, {}, kAdditionalDispatcherName, shutdown_handler2);
ASSERT_OK(dispatcher2.status_value());
dispatcher2_ = std::move(*dispatcher2);
}
void DispatcherDumpTest::TearDown() {
dispatcher_.ShutdownAsync();
dispatcher2_.ShutdownAsync();
ASSERT_OK(fdf_testing_run_until_idle());
shutdown_completion_.Wait();
shutdown_completion2_.Wait();
}
TEST_F(DispatcherDumpTest, DumpNoTasks) {
{
driver_runtime::Dispatcher::DumpState state;
driver_runtime::Dispatcher* runtime_dispatcher =
static_cast<driver_runtime::Dispatcher*>(dispatcher_.get());
runtime_dispatcher->Dump(&state);
ASSERT_EQ(state.running_dispatcher, nullptr);
ASSERT_EQ(state.running_driver, nullptr);
ASSERT_EQ(state.dispatcher_to_dump, dispatcher_.get());
ASSERT_EQ(state.driver_owner, fake_driver_);
ASSERT_EQ(state.state, driver_runtime::Dispatcher::DispatcherState::kRunning);
ASSERT_EQ(state.name.data(), kDispatcherName);
ASSERT_TRUE(state.synchronized);
ASSERT_FALSE(state.allow_sync_calls);
ASSERT_EQ(state.queued_tasks.size(), 0);
}
}
TEST_F(DispatcherDumpTest, DumpFromTask) {
// Queue 2 tasks, and dump the dispatcher state during each task.
libsync::Completion task_completion;
ASSERT_OK(async::PostTask(dispatcher_.async_dispatcher(), [&] {
{
driver_runtime::Dispatcher::DumpState state;
driver_runtime::Dispatcher* runtime_dispatcher =
static_cast<driver_runtime::Dispatcher*>(dispatcher_.get());
runtime_dispatcher->Dump(&state);
ASSERT_EQ(state.running_dispatcher, dispatcher_.get());
ASSERT_EQ(state.running_driver, fake_driver_);
ASSERT_EQ(state.dispatcher_to_dump, dispatcher_.get());
ASSERT_EQ(state.driver_owner, fake_driver_);
ASSERT_EQ(state.state, driver_runtime::Dispatcher::DispatcherState::kRunning);
ASSERT_EQ(state.name.data(), kDispatcherName);
ASSERT_EQ(state.queued_tasks.size(),
1); // We will post a second task before running the runtime thread.
}
task_completion.Signal();
}));
libsync::Completion task_completion2;
ASSERT_OK(async::PostTask(dispatcher_.async_dispatcher(), [&] {
{
driver_runtime::Dispatcher::DumpState state;
driver_runtime::Dispatcher* runtime_dispatcher =
static_cast<driver_runtime::Dispatcher*>(dispatcher_.get());
runtime_dispatcher->Dump(&state);
ASSERT_EQ(state.running_dispatcher, dispatcher_.get());
ASSERT_EQ(state.running_driver, fake_driver_);
ASSERT_EQ(state.dispatcher_to_dump, dispatcher_.get());
ASSERT_EQ(state.driver_owner, fake_driver_);
ASSERT_EQ(state.state, driver_runtime::Dispatcher::DispatcherState::kRunning);
ASSERT_EQ(state.name.data(), kDispatcherName);
ASSERT_EQ(state.queued_tasks.size(), 0); // This is the last task that was queued.
}
task_completion2.Signal();
}));
ASSERT_OK(fdf_testing_run_until_idle());
task_completion.Wait();
task_completion2.Wait();
}
TEST_F(DispatcherDumpTest, DumpFromAnotherDispatcher) {
constexpr std::string_view kAdditionalDispatcherName = "additional_dispatcher";
auto fake_driver2 = CreateFakeDriver();
libsync::Completion shutdown_completion;
auto shutdown_handler = [&](fdf_dispatcher_t* shutdown_dispatcher) {
shutdown_completion.Signal();
};
auto dispatcher2 = fdf_internal::TestDispatcherBuilder::CreateUnmanagedSynchronizedDispatcher(
fake_driver2, {}, kAdditionalDispatcherName, shutdown_handler);
ASSERT_OK(dispatcher2.status_value());
libsync::Completion task_completion;
ASSERT_OK(async::PostTask(dispatcher_.async_dispatcher(), [&] {
{
driver_runtime::Dispatcher::DumpState state;
driver_runtime::Dispatcher* runtime_dispatcher =
static_cast<driver_runtime::Dispatcher*>(dispatcher2->get());
runtime_dispatcher->Dump(&state);
// The dispatcher being dumped is different from the dispatcher running on the current
// thread.
ASSERT_EQ(state.running_dispatcher, dispatcher_.get());
ASSERT_EQ(state.running_driver, fake_driver_);
ASSERT_EQ(state.dispatcher_to_dump, dispatcher2->get());
ASSERT_EQ(state.driver_owner, fake_driver2);
ASSERT_EQ(state.state, driver_runtime::Dispatcher::DispatcherState::kRunning);
ASSERT_EQ(state.name.data(), kAdditionalDispatcherName);
ASSERT_EQ(state.queued_tasks.size(), 0);
}
task_completion.Signal();
}));
ASSERT_OK(fdf_testing_run_until_idle());
task_completion.Wait();
dispatcher2->ShutdownAsync();
ASSERT_OK(fdf_testing_run_until_idle());
shutdown_completion.Wait();
}
TEST_F(DispatcherDumpTest, QueueTaskFromAnotherDispatcher) {
constexpr std::string_view kAdditionalDispatcherName = "additional_dispatcher";
auto fake_driver2 = CreateFakeDriver();
libsync::Completion shutdown_completion;
auto shutdown_handler = [&](fdf_dispatcher_t* shutdown_dispatcher) {
shutdown_completion.Signal();
};
auto dispatcher2 = fdf_internal::TestDispatcherBuilder::CreateUnmanagedSynchronizedDispatcher(
fake_driver2, {}, kAdditionalDispatcherName, shutdown_handler);
ASSERT_OK(dispatcher2.status_value());
libsync::Completion task_completion;
libsync::Completion task_completion2;
ASSERT_OK(async::PostTask(dispatcher_.async_dispatcher(), [&] {
// Queue a task on the other dispatcher and print dump the details.
ASSERT_OK(async::PostTask(dispatcher2->async_dispatcher(), [&] { task_completion2.Signal(); }));
{
driver_runtime::Dispatcher::DumpState state;
driver_runtime::Dispatcher* runtime_dispatcher2 =
static_cast<driver_runtime::Dispatcher*>(dispatcher2->get());
runtime_dispatcher2->Dump(&state);
// The dispatcher being dumped is different from the dispatcher running on the current
// thread.
ASSERT_EQ(state.running_dispatcher, dispatcher_.get());
ASSERT_EQ(state.running_driver, fake_driver_);
ASSERT_EQ(state.dispatcher_to_dump, dispatcher2->get());
ASSERT_EQ(state.driver_owner, fake_driver2);
ASSERT_EQ(state.state, driver_runtime::Dispatcher::DispatcherState::kRunning);
ASSERT_EQ(state.name.data(), kAdditionalDispatcherName);
ASSERT_EQ(state.queued_tasks.size(), 1);
ASSERT_EQ(state.queued_tasks[0].initiating_dispatcher, dispatcher_.get());
ASSERT_EQ(state.queued_tasks[0].initiating_driver, fake_driver_);
}
task_completion.Signal();
}));
ASSERT_OK(fdf_testing_run_until_idle());
task_completion.Wait();
task_completion2.Wait();
dispatcher2->ShutdownAsync();
ASSERT_OK(fdf_testing_run_until_idle());
shutdown_completion.Wait();
}
// We will use the C API for the shutdown test, as async_task_t is not automatically dropped
// on cancellation like async::Task is.
struct Task {
async_task_t task;
libsync::Completion completion;
};
void TaskHandler(async_dispatcher_t* dispatcher, async_task_t* task, zx_status_t result) {
auto* typed_task = reinterpret_cast<Task*>(task);
typed_task->completion.Signal();
}
TEST_F(DispatcherDumpTest, DumpDuringShutdown) {
Task task;
task.task.state = ASYNC_STATE_INIT;
task.task.handler = &TaskHandler;
EXPECT_OK(async_post_task(dispatcher_.async_dispatcher(), &task.task));
dispatcher_.ShutdownAsync();
{
driver_runtime::Dispatcher::DumpState state;
driver_runtime::Dispatcher* runtime_dispatcher =
static_cast<driver_runtime::Dispatcher*>(dispatcher_.get());
runtime_dispatcher->Dump(&state);
ASSERT_EQ(state.running_dispatcher, nullptr);
ASSERT_EQ(state.running_driver, nullptr);
ASSERT_EQ(state.dispatcher_to_dump, dispatcher_.get());
ASSERT_EQ(state.driver_owner, fake_driver_);
ASSERT_EQ(state.state, driver_runtime::Dispatcher::DispatcherState::kShuttingDown);
ASSERT_EQ(state.name.data(), kDispatcherName);
ASSERT_EQ(state.queued_tasks.size(), 1);
ASSERT_EQ(state.queued_tasks[0].ptr, &task.task);
ASSERT_EQ(state.queued_tasks[0].initiating_dispatcher, nullptr);
ASSERT_EQ(state.queued_tasks[0].initiating_driver, nullptr);
}
ASSERT_OK(fdf_testing_run_until_idle());
task.completion.Wait();
shutdown_completion_.Wait();
}
TEST_F(DispatcherDumpTest, DumpUnsynchronizedDispatcher) {
constexpr std::string_view kDispatcherName = "unsynchronized";
auto fake_driver2 = CreateFakeDriver();
libsync::Completion completion;
auto shutdown_handler = [&](fdf_dispatcher_t* shutdown_dispatcher) { completion.Signal(); };
auto dispatcher = fdf_internal::TestDispatcherBuilder::CreateUnmanagedUnsynchronizedDispatcher(
fake_driver2, {}, kDispatcherName, shutdown_handler);
libsync::Completion task_completion;
ASSERT_OK(async::PostTask(dispatcher_.async_dispatcher(), [&] {
{
driver_runtime::Dispatcher::DumpState state;
driver_runtime::Dispatcher* runtime_dispatcher =
static_cast<driver_runtime::Dispatcher*>(dispatcher->get());
runtime_dispatcher->Dump(&state);
ASSERT_EQ(state.running_dispatcher, dispatcher_.get());
ASSERT_EQ(state.running_driver, fake_driver_);
ASSERT_EQ(state.dispatcher_to_dump, dispatcher->get());
ASSERT_EQ(state.driver_owner, fake_driver2);
ASSERT_EQ(state.state, driver_runtime::Dispatcher::DispatcherState::kRunning);
ASSERT_EQ(state.name.data(), kDispatcherName);
ASSERT_FALSE(state.synchronized);
ASSERT_FALSE(state.allow_sync_calls);
ASSERT_EQ(state.queued_tasks.size(), 0);
}
task_completion.Signal();
}));
ASSERT_OK(fdf_testing_run_until_idle());
task_completion.Wait();
dispatcher->ShutdownAsync();
ASSERT_OK(fdf_testing_run_until_idle());
completion.Wait();
}
TEST_F(DispatcherDumpTest, DumpRequestsCount) {
// Post a non-inlined task.
ASSERT_OK(async::PostTask(dispatcher_.async_dispatcher(), [&] {}));
ASSERT_OK(fdf_testing_run_until_idle());
{
driver_runtime::Dispatcher::DumpState state;
driver_runtime::Dispatcher* runtime_dispatcher =
static_cast<driver_runtime::Dispatcher*>(dispatcher_.get());
runtime_dispatcher->Dump(&state);
ASSERT_EQ(state.debug_stats.num_inlined_requests, 0);
ASSERT_EQ(state.debug_stats.num_total_requests, 1);
}
// Send an inlined request from end1 to end0 of the channel, which will be serviced by
// |dispatcher_|.
auto channels = fdf::ChannelPair::Create(0);
ASSERT_OK(channels.status_value());
auto channel_read = std::make_unique<fdf::ChannelRead>(
channels->end0.get(), 0,
[&](fdf_dispatcher_t* dispatcher, fdf::ChannelRead* channel_read, zx_status_t status) {});
ASSERT_OK(channel_read->Begin(dispatcher_.get()));
auto fake_driver2 = CreateFakeDriver();
auto shutdown_handler = [&](fdf_dispatcher_t* shutdown_dispatcher) {};
auto dispatcher2 = fdf_internal::TestDispatcherBuilder::CreateUnmanagedSynchronizedDispatcher(
fake_driver2, {}, "additional_dispatcher", shutdown_handler);
ASSERT_OK(dispatcher2.status_value());
fdf::Arena arena('TEST');
ASSERT_OK(async::PostTask(dispatcher2->async_dispatcher(), [&] {
ASSERT_OK(
channels->end1.Write(0, arena, nullptr, 0, cpp20::span<zx_handle_t>()).status_value());
}));
ASSERT_OK(fdf_testing_run_until_idle());
// We should see 1 inlined request in the dump.
{
driver_runtime::Dispatcher::DumpState state;
driver_runtime::Dispatcher* runtime_dispatcher =
static_cast<driver_runtime::Dispatcher*>(dispatcher_.get());
runtime_dispatcher->Dump(&state);
ASSERT_EQ(state.debug_stats.num_inlined_requests, 1);
ASSERT_EQ(state.debug_stats.num_total_requests, 2);
}
dispatcher2->ShutdownAsync();
ASSERT_OK(fdf_testing_run_until_idle());
}
TEST_F(DispatcherDumpTest, DumpChannelWaitNotYetRegistered) {
auto channels = fdf::ChannelPair::Create(0);
ASSERT_OK(channels.status_value());
// Write to one end of the channel before registering a read on the other end.
fdf::Arena arena('TEST');
ASSERT_OK(async::PostTask(dispatcher_.async_dispatcher(), [&] {
ASSERT_OK(
channels->end0.Write(0, arena, nullptr, 0, cpp20::span<zx_handle_t>()).status_value());
}));
ASSERT_OK(fdf_testing_run_until_idle());
auto channel_read = std::make_unique<fdf::ChannelRead>(
channels->end1.get(), 0,
[&](fdf_dispatcher_t* dispatcher, fdf::ChannelRead* channel_read, zx_status_t status) {});
ASSERT_OK(async::PostTask(dispatcher2_.async_dispatcher(),
[&] { ASSERT_OK(channel_read->Begin(dispatcher2_.get())); }));
ASSERT_OK(fdf_testing_run_until_idle());
{
driver_runtime::Dispatcher::DumpState state;
driver_runtime::Dispatcher* runtime_dispatcher =
static_cast<driver_runtime::Dispatcher*>(dispatcher2_.get());
runtime_dispatcher->Dump(&state);
ASSERT_EQ(state.debug_stats.num_inlined_requests, 0);
ASSERT_EQ(state.debug_stats.num_total_requests, 2);
ASSERT_EQ(state.debug_stats.non_inlined.task, 1);
ASSERT_EQ(state.debug_stats.non_inlined.channel_wait_not_yet_registered, 1);
}
}
TEST_F(DispatcherDumpTest, DumpChannelWaitRegistered) {
auto channels = fdf::ChannelPair::Create(0);
ASSERT_OK(channels.status_value());
auto channel_read = std::make_unique<fdf::ChannelRead>(
channels->end1.get(), 0,
[&](fdf_dispatcher_t* dispatcher, fdf::ChannelRead* channel_read, zx_status_t status) {});
ASSERT_OK(async::PostTask(dispatcher2_.async_dispatcher(),
[&] { ASSERT_OK(channel_read->Begin(dispatcher2_.get())); }));
ASSERT_OK(fdf_testing_run_until_idle());
// Write to one end of the channel after the read has been registered on the other end.
fdf::Arena arena('TEST');
ASSERT_OK(async::PostTask(dispatcher_.async_dispatcher(), [&] {
ASSERT_OK(
channels->end0.Write(0, arena, nullptr, 0, cpp20::span<zx_handle_t>()).status_value());
}));
ASSERT_OK(fdf_testing_run_until_idle());
{
driver_runtime::Dispatcher::DumpState state;
driver_runtime::Dispatcher* runtime_dispatcher =
static_cast<driver_runtime::Dispatcher*>(dispatcher2_.get());
runtime_dispatcher->Dump(&state);
ASSERT_EQ(state.debug_stats.num_inlined_requests, 1);
ASSERT_EQ(state.debug_stats.num_total_requests, 2);
ASSERT_EQ(state.debug_stats.non_inlined.task, 1);
}
}