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; }