[ios] Add an optional upload complete observation callback to the in-process handler

Breakpad offers a callback when uploads complete:
    https://source.chromium.org/chromium/chromium/src/+/main:third_party/breakpad/breakpad/src/client/ios/BreakpadController.h;l=103;drc=1fc9cc0d0e1dfafb8d29dba8d01f09587d870026

This adds an equivalent observation callback to Crashpad on iOS which is invoked each time an upload attempt completes (whether it succeeds or fails).

I couldn't find any existing unit tests for the upload thread, but
I tested this manually by integrating it into a client. Please
let me know the best way to test this.

Change-Id: I17822af5e63c8634484606a6470ce83b2c385676
Reviewed-on: https://chromium-review.googlesource.com/c/crashpad/crashpad/+/3852399
Reviewed-by: Justin Cohen <justincohen@chromium.org>
Commit-Queue: Justin Cohen <justincohen@chromium.org>
Reviewed-by: Robert Sesek <rsesek@chromium.org>
This commit is contained in:
Ben Hamilton 2022-09-09 15:25:08 -06:00 committed by Crashpad LUCI CQ
parent 6278690abe
commit ca3cf2f4e3
9 changed files with 112 additions and 17 deletions

View File

@ -15,6 +15,7 @@
#ifndef CRASHPAD_CLIENT_CRASHPAD_CLIENT_H_
#define CRASHPAD_CLIENT_CRASHPAD_CLIENT_H_
#include <functional>
#include <map>
#include <set>
#include <string>
@ -461,6 +462,17 @@ class CrashpadClient {
// BUILDFLAG(IS_CHROMEOS) || DOXYGEN
#if BUILDFLAG(IS_IOS) || DOXYGEN
//! \brief Observation callback invoked each time this object finishes
//! processing and attempting to upload on-disk crash reports (whether or
//! not the uploads succeeded).
//!
//! This callback is copied into this object. Any references or pointers
//! inside must outlive this object.
//!
//! The callback might be invoked on a background thread, so clients must
//! synchronize appropriately.
using ProcessPendingReportsObservationCallback = std::function<void()>;
//! \brief Configures the process to direct its crashes to the iOS in-process
//! Crashpad handler.
//!
@ -469,11 +481,16 @@ class CrashpadClient {
//! \param[in] database The path to a Crashpad database.
//! \param[in] url The URL of an upload server.
//! \param[in] annotations Process annotations to set in each crash report.
//! \param[in] callback Optional callback invoked zero or more times
//! on a background thread each time the handler finishes
//! processing and attempting to upload on-disk crash reports.
//! If this callback is empty, it is not invoked.
//! \return `true` on success, `false` on failure with a message logged.
static bool StartCrashpadInProcessHandler(
const base::FilePath& database,
const std::string& url,
const std::map<std::string, std::string>& annotations);
const std::map<std::string, std::string>& annotations,
ProcessPendingReportsObservationCallback callback);
//! \brief Requests that the handler convert intermediate dumps into
//! minidumps and trigger an upload if possible.

View File

@ -72,11 +72,14 @@ class CrashHandler : public Thread,
instance_ = nullptr;
}
bool Initialize(const base::FilePath& database,
const std::string& url,
const std::map<std::string, std::string>& annotations) {
bool Initialize(
const base::FilePath& database,
const std::string& url,
const std::map<std::string, std::string>& annotations,
internal::InProcessHandler::ProcessPendingReportsObservationCallback
callback) {
INITIALIZATION_STATE_SET_INITIALIZING(initialized_);
if (!in_process_handler_.Initialize(database, url, annotations) ||
if (!in_process_handler_.Initialize(database, url, annotations, callback) ||
!InstallMachExceptionHandler() ||
// xnu turns hardware faults into Mach exceptions, so the only signal
// left to register is SIGABRT, which never starts off as a hardware
@ -411,10 +414,11 @@ CrashpadClient::~CrashpadClient() {}
bool CrashpadClient::StartCrashpadInProcessHandler(
const base::FilePath& database,
const std::string& url,
const std::map<std::string, std::string>& annotations) {
const std::map<std::string, std::string>& annotations,
ProcessPendingReportsObservationCallback callback) {
CrashHandler* crash_handler = CrashHandler::Get();
DCHECK(crash_handler);
return crash_handler->Initialize(database, url, annotations);
return crash_handler->Initialize(database, url, annotations, callback);
}
// static

View File

@ -37,7 +37,10 @@ class CrashpadIOSClient : public PlatformTest {
void SetUp() override {
ASSERT_TRUE(client_.StartCrashpadInProcessHandler(
base::FilePath(database_dir.path()), "", {}));
base::FilePath(database_dir.path()),
"",
{},
CrashpadClient::ProcessPendingReportsObservationCallback()));
database_ = CrashReportDatabase::Initialize(database_dir.path());
}

View File

@ -71,7 +71,8 @@ InProcessHandler::~InProcessHandler() {
bool InProcessHandler::Initialize(
const base::FilePath& database,
const std::string& url,
const std::map<std::string, std::string>& annotations) {
const std::map<std::string, std::string>& annotations,
ProcessPendingReportsObservationCallback callback) {
INITIALIZATION_STATE_SET_INITIALIZING(initialized_);
annotations_ = annotations;
database_ = CrashReportDatabase::Initialize(database);
@ -92,7 +93,7 @@ bool InProcessHandler::Initialize(
upload_thread_options.identify_client_via_url = true;
upload_thread_.reset(new CrashReportUploadThread(
database_.get(), url, upload_thread_options));
database_.get(), url, upload_thread_options, callback));
}
if (!CreateDirectory(database))

View File

@ -16,6 +16,7 @@
#include <stdint.h>
#include <atomic>
#include <functional>
#include <map>
#include <string>
#include <vector>
@ -42,6 +43,17 @@ class InProcessHandler {
InProcessHandler(const InProcessHandler&) = delete;
InProcessHandler& operator=(const InProcessHandler&) = delete;
//! \brief Observation callback invoked each time this object finishes
//! processing and attempting to upload on-disk crash reports (whether or
//! not the uploads succeeded).
//!
//! This callback is copied into this object. Any references or pointers
//! inside must outlive this object.
//!
//! The callback might be invoked on a background thread, so clients must
//! synchronize appropriately.
using ProcessPendingReportsObservationCallback = std::function<void()>;
//! \brief Initializes the in-process handler.
//!
//! This method must be called only once, and must be successfully called
@ -50,11 +62,16 @@ class InProcessHandler {
//! \param[in] database The path to a Crashpad database.
//! \param[in] url The URL of an upload server.
//! \param[in] annotations Process annotations to set in each crash report.
//! \param[in] callback Optional callback invoked zero or more times
//! on a background thread each time this object finishes
//! processing and attempting to upload on-disk crash reports.
//! \return `true` if a handler to a pending intermediate dump could be
//! opened.
bool Initialize(const base::FilePath& database,
const std::string& url,
const std::map<std::string, std::string>& annotations);
const std::map<std::string, std::string>& annotations,
ProcessPendingReportsObservationCallback callback =
ProcessPendingReportsObservationCallback());
//! \brief Generate an intermediate dump from a signal handler exception.
//! Writes the dump with the cached writer does not allow concurrent

View File

@ -63,12 +63,36 @@ const int kRetryWorkIntervalSeconds = 15 * 60;
const int kRetryAttempts = 5;
#endif
// Wraps a reference to a no-args function (which can be empty). When this
// object goes out of scope, invokes the function if it is non-empty.
//
// The lifetime of the function must outlive the lifetime of this object.
class ScopedFunctionInvoker final {
public:
ScopedFunctionInvoker(const std::function<void()>& function)
: function_(function) {}
ScopedFunctionInvoker(const ScopedFunctionInvoker&) = delete;
ScopedFunctionInvoker& operator=(const ScopedFunctionInvoker&) = delete;
~ScopedFunctionInvoker() {
if (function_) {
function_();
}
}
private:
const std::function<void()>& function_;
};
} // namespace
CrashReportUploadThread::CrashReportUploadThread(CrashReportDatabase* database,
const std::string& url,
const Options& options)
CrashReportUploadThread::CrashReportUploadThread(
CrashReportDatabase* database,
const std::string& url,
const Options& options,
ProcessPendingReportsObservationCallback callback)
: options_(options),
callback_(callback),
url_(url),
// When watching for pending reports, check every 15 minutes, even in the
// absence of a signal from the handler thread. This allows for failed
@ -105,6 +129,10 @@ void CrashReportUploadThread::ProcessPendingReports() {
internal::ScopedBackgroundTask scoper("CrashReportUploadThread");
#endif // BUILDFLAG(IS_IOS)
// If callback_ is non-empty, invoke it when this function returns after
// uploads complete (regardless of whether or not that succeeded).
ScopedFunctionInvoker scoped_function_invoker(callback_);
std::vector<UUID> known_report_uuids = known_pending_report_uuids_.Drain();
for (const UUID& report_uuid : known_report_uuids) {
CrashReportDatabase::Report report;

View File

@ -15,6 +15,7 @@
#ifndef CRASHPAD_HANDLER_CRASH_REPORT_UPLOAD_THREAD_H_
#define CRASHPAD_HANDLER_CRASH_REPORT_UPLOAD_THREAD_H_
#include <functional>
#include <memory>
#include <string>
#include <unordered_map>
@ -63,14 +64,30 @@ class CrashReportUploadThread : public WorkerThread::Delegate,
bool watch_pending_reports;
};
//! \brief Observation callback invoked each time the in-process handler
//! finishes processing and attempting to upload on-disk crash reports
//! (whether or not the uploads succeeded).
//!
//! This callback is copied into this object. Any references or pointers
//! inside must outlive this object.
//!
//! The callback might be invoked on a background thread, so clients must
//! synchronize appropriately.
using ProcessPendingReportsObservationCallback = std::function<void()>;
//! \brief Constructs a new object.
//!
//! \param[in] database The database to upload crash reports from.
//! \param[in] url The URL of the server to upload crash reports to.
//! \param[in] options Options for the report uploads.
//! \param[in] callback Optional callback invoked zero or more times
//! on a background thread each time the this object finishes
//! processing and attempting to upload on-disk crash reports.
//! If this callback is empty, it is not invoked.
CrashReportUploadThread(CrashReportDatabase* database,
const std::string& url,
const Options& options);
const Options& options,
ProcessPendingReportsObservationCallback callback);
CrashReportUploadThread(const CrashReportUploadThread&) = delete;
CrashReportUploadThread& operator=(const CrashReportUploadThread&) = delete;
@ -207,6 +224,7 @@ class CrashReportUploadThread : public WorkerThread::Delegate,
#endif
const Options options_;
const ProcessPendingReportsObservationCallback callback_;
const std::string url_;
WorkerThread thread_;
ThreadSafeVector<UUID> known_pending_report_uuids_;

View File

@ -1029,7 +1029,10 @@ int HandlerMain(int argc,
upload_thread_options.watch_pending_reports = options.periodic_tasks;
upload_thread.Reset(new CrashReportUploadThread(
database.get(), options.url, upload_thread_options));
database.get(),
options.url,
upload_thread_options,
CrashReportUploadThread::ProcessPendingReportsObservationCallback()));
upload_thread.Get()->Start();
}

View File

@ -164,7 +164,11 @@ UIWindow* GetAnyWindow() {
{"crashpad", "no"}};
}
if (client_.StartCrashpadInProcessHandler(
GetDatabaseDir(), "", annotations)) {
GetDatabaseDir(),
"",
annotations,
crashpad::CrashpadClient::
ProcessPendingReportsObservationCallback())) {
client_.ProcessIntermediateDumps();
}