diff --git a/client/crashpad_client.h b/client/crashpad_client.h index bf36506d..fc224674 100644 --- a/client/crashpad_client.h +++ b/client/crashpad_client.h @@ -15,6 +15,7 @@ #ifndef CRASHPAD_CLIENT_CRASHPAD_CLIENT_H_ #define CRASHPAD_CLIENT_CRASHPAD_CLIENT_H_ +#include #include #include #include @@ -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; + //! \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& annotations); + const std::map& annotations, + ProcessPendingReportsObservationCallback callback); //! \brief Requests that the handler convert intermediate dumps into //! minidumps and trigger an upload if possible. diff --git a/client/crashpad_client_ios.cc b/client/crashpad_client_ios.cc index d03549a3..7ac14bca 100644 --- a/client/crashpad_client_ios.cc +++ b/client/crashpad_client_ios.cc @@ -72,11 +72,14 @@ class CrashHandler : public Thread, instance_ = nullptr; } - bool Initialize(const base::FilePath& database, - const std::string& url, - const std::map& annotations) { + bool Initialize( + const base::FilePath& database, + const std::string& url, + const std::map& 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& annotations) { + const std::map& 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 diff --git a/client/crashpad_client_ios_test.mm b/client/crashpad_client_ios_test.mm index 190bcba2..29f3df65 100644 --- a/client/crashpad_client_ios_test.mm +++ b/client/crashpad_client_ios_test.mm @@ -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()); } diff --git a/client/ios_handler/in_process_handler.cc b/client/ios_handler/in_process_handler.cc index 8f80172f..7ec943ac 100644 --- a/client/ios_handler/in_process_handler.cc +++ b/client/ios_handler/in_process_handler.cc @@ -71,7 +71,8 @@ InProcessHandler::~InProcessHandler() { bool InProcessHandler::Initialize( const base::FilePath& database, const std::string& url, - const std::map& annotations) { + const std::map& 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)) diff --git a/client/ios_handler/in_process_handler.h b/client/ios_handler/in_process_handler.h index b813f083..62797b32 100644 --- a/client/ios_handler/in_process_handler.h +++ b/client/ios_handler/in_process_handler.h @@ -16,6 +16,7 @@ #include #include +#include #include #include #include @@ -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; + //! \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& annotations); + const std::map& 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 diff --git a/handler/crash_report_upload_thread.cc b/handler/crash_report_upload_thread.cc index 256927e6..5bd2889e 100644 --- a/handler/crash_report_upload_thread.cc +++ b/handler/crash_report_upload_thread.cc @@ -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& function) + : function_(function) {} + ScopedFunctionInvoker(const ScopedFunctionInvoker&) = delete; + ScopedFunctionInvoker& operator=(const ScopedFunctionInvoker&) = delete; + + ~ScopedFunctionInvoker() { + if (function_) { + function_(); + } + } + + private: + const std::function& 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 known_report_uuids = known_pending_report_uuids_.Drain(); for (const UUID& report_uuid : known_report_uuids) { CrashReportDatabase::Report report; diff --git a/handler/crash_report_upload_thread.h b/handler/crash_report_upload_thread.h index 546f5e24..22bb26e3 100644 --- a/handler/crash_report_upload_thread.h +++ b/handler/crash_report_upload_thread.h @@ -15,6 +15,7 @@ #ifndef CRASHPAD_HANDLER_CRASH_REPORT_UPLOAD_THREAD_H_ #define CRASHPAD_HANDLER_CRASH_REPORT_UPLOAD_THREAD_H_ +#include #include #include #include @@ -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; + //! \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 known_pending_report_uuids_; diff --git a/handler/handler_main.cc b/handler/handler_main.cc index f2f73cb8..b7ba6b14 100644 --- a/handler/handler_main.cc +++ b/handler/handler_main.cc @@ -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(); } diff --git a/test/ios/host/cptest_application_delegate.mm b/test/ios/host/cptest_application_delegate.mm index 2ad6e12d..0af106e7 100644 --- a/test/ios/host/cptest_application_delegate.mm +++ b/test/ios/host/cptest_application_delegate.mm @@ -164,7 +164,11 @@ UIWindow* GetAnyWindow() { {"crashpad", "no"}}; } if (client_.StartCrashpadInProcessHandler( - GetDatabaseDir(), "", annotations)) { + GetDatabaseDir(), + "", + annotations, + crashpad::CrashpadClient:: + ProcessPendingReportsObservationCallback())) { client_.ProcessIntermediateDumps(); }