From 4b6d54b2e1c9f5d507ea760d09688edd108c175a Mon Sep 17 00:00:00 2001 From: Mark Mentovai Date: Thu, 12 Feb 2015 15:03:59 -0500 Subject: [PATCH] handler: Add crash report upload. Almost. MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Upload isn’t actually hooked up yet, but this establishes the upload thread and provides all of the plumbing to process pending reports. For the time being, SkipReportUpload() is called for all pending reports. R=rsesek@chromium.org Review URL: https://codereview.chromium.org/918743002 --- handler/handler.gyp | 2 + handler/mac/crash_report_exception_handler.cc | 8 +- handler/mac/crash_report_exception_handler.h | 7 +- handler/mac/crash_report_upload_thread.cc | 118 ++++++++++++++++++ handler/mac/crash_report_upload_thread.h | 108 ++++++++++++++++ handler/mac/main.cc | 10 +- 6 files changed, 249 insertions(+), 4 deletions(-) create mode 100644 handler/mac/crash_report_upload_thread.cc create mode 100644 handler/mac/crash_report_upload_thread.h diff --git a/handler/handler.gyp b/handler/handler.gyp index 427c3b2e..f7a4164c 100644 --- a/handler/handler.gyp +++ b/handler/handler.gyp @@ -34,6 +34,8 @@ 'sources': [ 'mac/crash_report_exception_handler.cc', 'mac/crash_report_exception_handler.h', + 'mac/crash_report_upload_thread.cc', + 'mac/crash_report_upload_thread.h', 'mac/exception_handler_server.cc', 'mac/exception_handler_server.h', 'mac/main.cc', diff --git a/handler/mac/crash_report_exception_handler.cc b/handler/mac/crash_report_exception_handler.cc index 2f497c6b..ec23e301 100644 --- a/handler/mac/crash_report_exception_handler.cc +++ b/handler/mac/crash_report_exception_handler.cc @@ -58,8 +58,10 @@ class CallErrorWritingCrashReport { } // namespace CrashReportExceptionHandler::CrashReportExceptionHandler( - CrashReportDatabase* database) - : database_(database) { + CrashReportDatabase* database, + CrashReportUploadThread* upload_thread) + : database_(database), + upload_thread_(upload_thread) { } CrashReportExceptionHandler::~CrashReportExceptionHandler() { @@ -132,6 +134,8 @@ kern_return_t CrashReportExceptionHandler::CatchMachException( return KERN_FAILURE; } + upload_thread_->ReportPending(); + return ExcServerSuccessfulReturnValue(behavior, false); } diff --git a/handler/mac/crash_report_exception_handler.h b/handler/mac/crash_report_exception_handler.h index 65cc4da2..8f81102e 100644 --- a/handler/mac/crash_report_exception_handler.h +++ b/handler/mac/crash_report_exception_handler.h @@ -19,6 +19,7 @@ #include "base/basictypes.h" #include "client/crash_report_database.h" +#include "handler/mac/crash_report_upload_thread.h" #include "util/mach/exc_server_variants.h" namespace crashpad { @@ -30,7 +31,10 @@ class CrashReportExceptionHandler : public UniversalMachExcServer::Interface { //! \brief Creates a new object that will store crash reports in \a database. //! //! \param[in] database The database to store crash reports in. Weak. - explicit CrashReportExceptionHandler(CrashReportDatabase* database); + //! \param[in] upload_thread The upload thread to notify when a new crash + //! report is written into \a database. + CrashReportExceptionHandler(CrashReportDatabase* database, + CrashReportUploadThread* upload_thread); ~CrashReportExceptionHandler(); @@ -56,6 +60,7 @@ class CrashReportExceptionHandler : public UniversalMachExcServer::Interface { private: CrashReportDatabase* database_; // weak + CrashReportUploadThread* upload_thread_; // weak DISALLOW_COPY_AND_ASSIGN(CrashReportExceptionHandler); }; diff --git a/handler/mac/crash_report_upload_thread.cc b/handler/mac/crash_report_upload_thread.cc new file mode 100644 index 00000000..55e2d291 --- /dev/null +++ b/handler/mac/crash_report_upload_thread.cc @@ -0,0 +1,118 @@ +// Copyright 2015 The Crashpad Authors. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "handler/mac/crash_report_upload_thread.h" + +#include + +#include + +#include "base/logging.h" + +namespace crashpad { + +CrashReportUploadThread::CrashReportUploadThread(CrashReportDatabase* database) + : database_(database), + semaphore_(0), + thread_(0), + running_(false) { +} + +CrashReportUploadThread::~CrashReportUploadThread() { + DCHECK(!running_); + DCHECK(!thread_); +} + +void CrashReportUploadThread::Start() { + DCHECK(!running_); + DCHECK(!thread_); + + running_ = true; + if ((errno = pthread_create(&thread_, nullptr, RunThreadMain, this)) != 0) { + PLOG(ERROR) << "pthread_create"; + DCHECK(false); + running_ = false; + } +} + +void CrashReportUploadThread::Stop() { + DCHECK(running_); + DCHECK(thread_); + + if (!running_) { + return; + } + + running_ = false; + semaphore_.Signal(); + + if ((errno = pthread_join(thread_, nullptr)) != 0) { + PLOG(ERROR) << "pthread_join"; + DCHECK(false); + } + + thread_ = 0; +} + +void CrashReportUploadThread::ReportPending() { + semaphore_.Signal(); +} + +void CrashReportUploadThread::ThreadMain() { + while (running_) { + ProcessPendingReports(); + + // Check for pending reports every 15 minutes, even in the absence of a + // signal from the handler thread. This allows for failed uploads to be + // retried periodically, and for pending reports written by other processes + // to be recognized. + semaphore_.TimedWait(15 * 60); + } +} + +void CrashReportUploadThread::ProcessPendingReports() { + std::vector reports; + if (database_->GetPendingReports(&reports) != CrashReportDatabase::kNoError) { + // The database is sick. It might be prudent to stop trying to poke it from + // this thread by abandoning the thread altogether. On the other hand, if + // the problem is transient, it might be possible to talk to it again on the + // next pass. For now, take the latter approach. + return; + } + + for (const CrashReportDatabase::Report& report : reports) { + ProcessPendingReport(report); + + // Respect Stop() being called after at least one attempt to process a + // report. + if (!running_) { + return; + } + } +} + +void CrashReportUploadThread::ProcessPendingReport( + const CrashReportDatabase::Report& report) { + // TODO(mark): Actually upload the report, if uploads are enabled. + database_->SkipReportUpload(report.uuid); +} + +// static +void* CrashReportUploadThread::RunThreadMain(void* arg) { + CrashReportUploadThread* self = static_cast(arg); + self->ThreadMain(); + return nullptr; +} + +} // namespace crashpad diff --git a/handler/mac/crash_report_upload_thread.h b/handler/mac/crash_report_upload_thread.h new file mode 100644 index 00000000..721cd798 --- /dev/null +++ b/handler/mac/crash_report_upload_thread.h @@ -0,0 +1,108 @@ +// Copyright 2015 The Crashpad Authors. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef CRASHPAD_HANDLER_MAC_CRASH_REPORT_UPLOAD_THREAD_H_ +#define CRASHPAD_HANDLER_MAC_CRASH_REPORT_UPLOAD_THREAD_H_ + +#include "base/basictypes.h" + +#include + +#include "client/crash_report_database.h" +#include "util/synchronization/semaphore.h" + +namespace crashpad { + +//! \brief A thread that processes pending crash reports in a +//! CrashReportDatabase by uploading them or marking them as completed +//! without upload, as desired. +//! +//! A producer of crash reports should notify an object of this class that a new +//! report has been added to the database by calling ReportPending(). +//! +//! Independently of being triggered by ReportPending(), objects of this class +//! periodically examine the database for pending reports. This allows failed +//! upload attempts for reports left in the pending state to be retried. It also +//! catches reports that are added without a ReportPending() signal being +//! caught. This may happen if crash reports are added to the database by other +//! processes. +class CrashReportUploadThread { + public: + explicit CrashReportUploadThread(CrashReportDatabase* database); + ~CrashReportUploadThread(); + + //! \brief Starts a dedicated upload thread, which executes ThreadMain(). + //! + //! This method may only be be called on a newly-constructed object or after + //! a call to Stop(). + void Start(); + + //! \brief Stops the upload thread. + //! + //! The upload thread will terminate after completing whatever task it is + //! performing. If it is not performing any task, it will terminate + //! immediately. This method blocks while waiting for the upload thread to + //! terminate. + //! + //! This method must only be called after Start(). If Start() has been called, + //! this method must be called before destroying an object of this class. + //! + //! This method may be called from any thread other than the upload thread. + //! It is expected to only be called from the same thread that called Start(). + void Stop(); + + //! \brief Informs the upload thread that a new pending report has been added + //! to the database. + //! + //! This method may be called from any thread. + void ReportPending(); + + private: + //! \brief Calls ProcessPendingReports() in response to ReportPending() having + //! been called on any thread, as well as periodically on a timer. + void ThreadMain(); + + //! \brief Obtains all pending reports from the database, and calls + //! ProcessPendingReport() to process each one. + void ProcessPendingReports(); + + //! \brief Processes a single pending report from the database. + //! + //! \param[in] report The crash report to process. + //! + //! If report upload is enabled, this method attempts to upload \a report. If + //! the upload is successful, the report will be marked as “completed” in the + //! database. If the upload fails and more retries are desired, the report’s + //! upload-attempt count and last-upload-attempt time will be updated in the + //! database and it will remain in the “pending” state. If the upload fails + //! and no more retries are desired, or report upload is disabled, it will be + //! marked as “completed” in the database without ever having been uploaded. + void ProcessPendingReport(const CrashReportDatabase::Report& report); + + //! \brief Cals ThreadMain(). + //! + //! \param[in] arg A pointer to the object on which to invoke ThreadMain(). + //! + //! \return `nullptr`. + static void* RunThreadMain(void* arg); + + CrashReportDatabase* database_; // weak + Semaphore semaphore_; // TODO(mark): Use a condition variable instead? + pthread_t thread_; + bool running_; +}; + +} // namespace crashpad + +#endif // CRASHPAD_HANDLER_MAC_CRASH_REPORT_UPLOAD_THREAD_H_ diff --git a/handler/mac/main.cc b/handler/mac/main.cc index c5d152f7..57baedd4 100644 --- a/handler/mac/main.cc +++ b/handler/mac/main.cc @@ -19,14 +19,17 @@ #include #include "base/files/file_path.h" +#include "base/logging.h" #include "base/memory/scoped_ptr.h" #include "client/crash_report_database.h" #include "tools/tool_support.h" #include "handler/mac/crash_report_exception_handler.h" +#include "handler/mac/crash_report_upload_thread.h" #include "handler/mac/exception_handler_server.h" #include "util/mach/child_port_handshake.h" #include "util/posix/close_stdio.h" #include "util/stdlib/string_number_conversion.h" +#include "util/synchronization/semaphore.h" namespace crashpad { namespace { @@ -129,10 +132,15 @@ int HandlerMain(int argc, char* argv[]) { return EXIT_FAILURE; } - CrashReportExceptionHandler exception_handler(database.get()); + CrashReportUploadThread upload_thread(database.get()); + upload_thread.Start(); + + CrashReportExceptionHandler exception_handler(database.get(), &upload_thread); exception_handler_server.Run(&exception_handler); + upload_thread.Stop(); + return EXIT_SUCCESS; }