
Motivation: At the moment it is hard to attribute a clangd crash to a specific request out of all in-flight requests that might be processed concurrently. So before we can act on production clangd crashes, we have to do quite some digging through the log tables populated by our in-house VSCode extension or sometimes even directly reach out to the affected developer. Having all the details needed to reproduce a crash printed alongside its stack trace has a potential to save us quite some time, that could better be spent on fixing the actual problems. Implementation approach: * introduce `ThreadCrashReporter` class that allows to set a temporary signal handler for the current thread * follow RAII pattern to simplify printing context for crashes occurring within a particular scope * hold `std::function` as a handler to allow capturing context to print * set local `ThreadCrashReporter` within `JSONTransport::loop()` to print request JSON for main thread crashes, and in `ASTWorker::run()` to print the file paths, arguments and contents for worker thread crashes `ThreadCrashReporter` currently allows only one active handler per thread, but the approach can be extended to support stacked handlers printing context incrementally. Example output for main thread crashes: ``` ... #15 0x00007f7ddc819493 __libc_start_main (/lib64/libc.so.6+0x23493) #16 0x000000000249775e _start (/home/emmablink/local/llvm-project/build/bin/clangd+0x249775e) Signalled while processing message: {"jsonrpc": "2.0", "method": "textDocument/didOpen", "params": {"textDocument": {"uri": "file:///home/emmablink/test.cpp", "languageId": "cpp", "version": 1, "text": "template <typename>\nclass Bar {\n Bar<int> *variables_to_modify;\n foo() {\n for (auto *c : *variables_to_modify)\n delete c;\n }\n};\n"}}} ``` Example output for AST worker crashes: ``` ... #41 0x00007fb18304c14a start_thread pthread_create.c:0:0 #42 0x00007fb181bfcdc3 clone (/lib64/libc.so.6+0xfcdc3) Signalled during AST action: Filename: test.cpp Directory: /home/emmablink Command Line: /usr/bin/clang -resource-dir=/data/users/emmablink/llvm-project/build/lib/clang/14.0.0 -- /home/emmablink/test.cpp Version: 1 Contents: template <typename> class Bar { Bar<int> *variables_to_modify; foo() { for (auto *c : *variables_to_modify) delete c; } }; ``` Testing: The unit test covers the thread-localitity and nesting aspects of `ThreadCrashReporter`. There might be way to set up a lit-based integration test that would spawn clangd, send a message to it, signal it immediately and check the standard output, but this might be prone to raceconditions. Reviewed By: sammccall Differential Revision: https://reviews.llvm.org/D109506
59 lines
2.3 KiB
C++
59 lines
2.3 KiB
C++
//===--- ThreadCrashReporter.h - Thread local signal handling ----*- C++-*-===//
|
|
//
|
|
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
|
|
// See https://llvm.org/LICENSE.txt for license information.
|
|
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
|
|
//
|
|
//===----------------------------------------------------------------------===//
|
|
|
|
#ifndef LLVM_CLANG_TOOLS_EXTRA_CLANGD_SUPPORT_THREADCRASHREPORTER_H
|
|
#define LLVM_CLANG_TOOLS_EXTRA_CLANGD_SUPPORT_THREADCRASHREPORTER_H
|
|
|
|
#include "llvm/ADT/FunctionExtras.h"
|
|
|
|
namespace clang {
|
|
namespace clangd {
|
|
|
|
/// Allows setting per-thread abort/kill signal callbacks, to print additional
|
|
/// information about the crash depending on which thread got signalled.
|
|
class ThreadCrashReporter {
|
|
public:
|
|
using SignalCallback = llvm::unique_function<void(void)>;
|
|
|
|
/// Registers the callback as the first one in thread-local callback chain.
|
|
///
|
|
/// Asserts if the current thread's callback is already set. The callback is
|
|
/// likely to be invoked in a signal handler. Most LLVM signal handling is not
|
|
/// strictly async-signal-safe. However reporters should avoid accessing data
|
|
/// structures likely to be in a bad state on crash.
|
|
ThreadCrashReporter(SignalCallback ThreadLocalCallback);
|
|
/// Resets the current thread's callback to nullptr.
|
|
~ThreadCrashReporter();
|
|
|
|
/// Moves are disabled to ease nesting and escaping considerations.
|
|
ThreadCrashReporter(ThreadCrashReporter &&RHS) = delete;
|
|
ThreadCrashReporter(const ThreadCrashReporter &) = delete;
|
|
ThreadCrashReporter &operator=(ThreadCrashReporter &&) = delete;
|
|
ThreadCrashReporter &operator=(const ThreadCrashReporter &) = delete;
|
|
|
|
/// Calls all currently-active ThreadCrashReporters for the current thread.
|
|
///
|
|
/// To be called from sys::AddSignalHandler() callback. Any signal filtering
|
|
/// is the responsibility of the caller. While this function is intended to be
|
|
/// called from signal handlers, it is not strictly async-signal-safe - see
|
|
/// constructor comment.
|
|
///
|
|
/// When several reporters are nested, the callbacks are called in LIFO order.
|
|
static void runCrashHandlers();
|
|
|
|
private:
|
|
SignalCallback Callback;
|
|
/// Points to the next reporter up the stack.
|
|
ThreadCrashReporter *Next;
|
|
};
|
|
|
|
} // namespace clangd
|
|
} // namespace clang
|
|
|
|
#endif
|