blob: 2dbda6255bccaa7cfac596d6ed87f518cd0cb9e1 [file]
// Copyright 2026 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 "src/developer/debug/zxdb/debug_adapter/async_backtrace_subscription.h"
#include <lib/syslog/cpp/macros.h>
#include "src/developer/debug/zxdb/client/async_task.h"
#include "src/developer/debug/zxdb/client/async_task_tree.h"
#include "src/developer/debug/zxdb/client/process.h"
#include "src/developer/debug/zxdb/client/session.h"
#include "src/developer/debug/zxdb/client/source_file_provider_impl.h"
#include "src/developer/debug/zxdb/client/target.h"
#include "src/developer/debug/zxdb/client/thread.h"
#include "src/developer/debug/zxdb/common/string_util.h"
#include "src/developer/debug/zxdb/symbols/location.h"
namespace dap {
DAP_IMPLEMENT_STRUCT_TYPEINFO(AsyncTaskNode, "", DAP_FIELD(id, "id"), DAP_FIELD(name, "name"),
DAP_FIELD(file, "file"), DAP_FIELD(line, "line"),
DAP_FIELD(children, "children"))
DAP_IMPLEMENT_STRUCT_TYPEINFO(AsyncBacktraceUpdate, "vscode-fuchsia.updateAsyncBacktrace",
DAP_FIELD(id, "id"), DAP_FIELD(name, "name"),
DAP_FIELD(tasks, "tasks"))
} // namespace dap
namespace zxdb {
AsyncBacktraceSubscription::AsyncBacktraceSubscription(fxl::WeakPtr<Session> session,
std::shared_ptr<dap::Session> dap)
: session_(std::move(session)), dap_(std::move(dap)), weak_factory_(this) {
session_->thread_observers().AddObserver(this);
}
AsyncBacktraceSubscription::~AsyncBacktraceSubscription() {
if (session_) {
session_->thread_observers().RemoveObserver(this);
}
}
void AsyncBacktraceSubscription::DidCreateThread(Thread* thread) {
CancelPendingBacktrace(thread);
// Notify DAP client that a new thread has been created.
dap_->send(dap::AsyncBacktraceUpdate{
.id = static_cast<int64_t>(thread->GetKoid()),
.name = thread->GetName(),
.tasks = dap::array<dap::AsyncTaskNode>(),
});
}
void AsyncBacktraceSubscription::OnThreadStopped(Thread* thread, const StopInfo& info) {
CancelPendingBacktrace(thread);
// Syncing the async task tree is expensive, so skip syncing when no frames are available.
if (!thread->CurrentStopSupportsFrames()) {
dap_->send(dap::AsyncBacktraceUpdate{
.id = static_cast<int64_t>(thread->GetKoid()),
.name = thread->GetName(),
.tasks = dap::array<dap::AsyncTaskNode>(),
});
return;
}
CollectAndReportAsyncBacktrace(thread);
}
void AsyncBacktraceSubscription::DidUpdateStackFrames(Thread* thread) {
// Notify DAP client to reset this thread's async backtrace subtree, since it has resumed.
if (!thread->CurrentStopSupportsFrames()) {
CancelPendingBacktrace(thread);
dap_->send(dap::AsyncBacktraceUpdate{
.id = static_cast<int64_t>(thread->GetKoid()),
.name = thread->GetName(),
.tasks = dap::array<dap::AsyncTaskNode>(),
});
return;
}
}
void AsyncBacktraceSubscription::WillDestroyThread(Thread* thread) {
CancelPendingBacktrace(thread);
// Notify DAP client to remove this thread from the async backtrace view.
dap_->send(dap::AsyncBacktraceUpdate{.id = static_cast<int64_t>(thread->GetKoid()),
.name = thread->GetName()});
}
void AsyncBacktraceSubscription::CancelPendingBacktrace(const Thread* thread) {
if (auto it = pending_backtraces_.find(thread->GetKoid()); it != pending_backtraces_.end()) {
it->second.Cancel();
pending_backtraces_.erase(it);
}
}
void AsyncBacktraceSubscription::CollectAndReportAsyncBacktrace(Thread* thread) {
// Enqueue into `pending_backtraces_` before collecting the async backtrace into an
// `AsyncBacktraceUpdate` DAP event, allowing this backtrace to be cancelled if the thread state
// happens to change in quick succession (e.g. thread resumes, or thread is destroyed).
pending_backtraces_.emplace(thread->GetKoid(), [weak_this = weak_factory_.GetWeakPtr(),
weak_thread = thread->GetWeakPtr()](
const Err& err, const Frame* /*frame*/) {
if (!weak_this || !weak_thread) {
return;
}
// A common source of errors is when async tasks aren't found (e.g. thread is not running async
// code). In these cases, we report an empty task tree to the DAP client for this thread.
if (err.has_error()) {
FX_LOGS(DEBUG) << "Failed to collect async task tree for thread \"" << weak_thread->GetKoid()
<< "\": " << err.msg();
weak_this->dap_->send(dap::AsyncBacktraceUpdate{
.id = static_cast<int64_t>(weak_thread->GetKoid()),
.name = weak_thread->GetName(),
.tasks = dap::array<dap::AsyncTaskNode>(),
});
return;
}
auto file_provider = SourceFileProviderImpl(weak_thread->GetProcess()->GetTarget()->settings());
weak_this->dap_->send(dap::AsyncBacktraceUpdate{
.id = static_cast<int64_t>(weak_thread->GetKoid()),
.name = weak_thread->GetName(),
.tasks = weak_thread->GetAsyncTaskTree().Map<dap::AsyncTaskNode>(
[&file_provider](const zxdb::AsyncTask& task, dap::AsyncTaskNode* node) {
if (uint64_t task_id = task.GetId(); task_id != 0) {
node->id = zxdb::to_hex_string(task_id);
}
node->name = task.GetIdentifier().GetFullName();
const zxdb::Location& location = task.GetLocation();
if (location.file_line().is_valid()) {
auto file_metadata = file_provider.GetFileMetadata(location.file_line().file(),
location.file_line().comp_dir());
if (!file_metadata.has_error()) {
node->file = file_metadata.take_value().full_path;
node->line = location.file_line().line();
}
}
},
[](dap::AsyncTaskNode* node) { return &node->children; }),
});
});
thread->GetAsyncTaskTree().Sync(thread->GetStack(),
pending_backtraces_[thread->GetKoid()].callback());
}
} // namespace zxdb