| // Copyright 2016 The Chromium Embedded Framework Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include "libcef/common/cef_crash_report_upload_thread.h" |
| |
| #include "libcef/common/cef_crash_report_utils.h" |
| #include "third_party/crashpad/crashpad/client/settings.h" |
| |
| using namespace crashpad; |
| |
| CefCrashReportUploadThread::CefCrashReportUploadThread( |
| CrashReportDatabase* database, |
| const std::string& url, |
| const Options& options, |
| int max_uploads) |
| : CrashReportUploadThread(database, url, options), |
| max_uploads_(max_uploads) {} |
| |
| CefCrashReportUploadThread::~CefCrashReportUploadThread() {} |
| |
| void CefCrashReportUploadThread::ProcessPendingReports() { |
| if (BackoffPending()) { |
| // Try again later. |
| return; |
| } |
| |
| if (MaxUploadsEnabled()) { |
| // Retrieve all completed reports. |
| std::vector<CrashReportDatabase::Report> reports; |
| if (database_->GetCompletedReports(&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; |
| } |
| |
| const time_t now = time(nullptr); |
| const int kSeconds = 60 * 60 * 24; // 24 hours |
| |
| // Count how many reports have completed in the last 24 hours. |
| recent_upload_ct_ = 0; |
| for (const CrashReportDatabase::Report& report : reports) { |
| if (report.last_upload_attempt_time > now - kSeconds) |
| recent_upload_ct_++; |
| } |
| } |
| |
| // Continue with processing pending reports. |
| CrashReportUploadThread::ProcessPendingReports(); |
| } |
| |
| void CefCrashReportUploadThread::ProcessPendingReport( |
| const CrashReportDatabase::Report& report) { |
| // Always allow upload if it's been explicitly requested by the user. |
| if (!report.upload_explicitly_requested) { |
| if (!UploadsEnabled()) { |
| // Don’t attempt an upload if there’s no URL or if uploads have been |
| // disabled in the database’s settings. |
| database_->SkipReportUpload( |
| report.uuid, Metrics::CrashSkippedReason::kUploadsDisabled); |
| return; |
| } |
| |
| if (MaxUploadsExceeded()) { |
| // Don't send uploads if the rate limit has been exceeded. |
| database_->SkipReportUpload( |
| report.uuid, Metrics::CrashSkippedReason::kUploadThrottled); |
| return; |
| } |
| } |
| |
| if (BackoffPending()) { |
| // Try again later. |
| return; |
| } |
| |
| std::unique_ptr<const CrashReportDatabase::UploadReport> upload_report; |
| CrashReportDatabase::OperationStatus status = |
| database_->GetReportForUploading(report.uuid, &upload_report); |
| switch (status) { |
| case CrashReportDatabase::kNoError: |
| break; |
| |
| case CrashReportDatabase::kBusyError: |
| return; |
| |
| case CrashReportDatabase::kReportNotFound: |
| case CrashReportDatabase::kFileSystemError: |
| case CrashReportDatabase::kDatabaseError: |
| // In these cases, SkipReportUpload() might not work either, but it’s best |
| // to at least try to get the report out of the way. |
| database_->SkipReportUpload(report.uuid, |
| Metrics::CrashSkippedReason::kDatabaseError); |
| return; |
| |
| case CrashReportDatabase::kCannotRequestUpload: |
| NOTREACHED(); |
| return; |
| } |
| |
| std::string response_body; |
| UploadResult upload_result = |
| UploadReport(upload_report.get(), &response_body); |
| switch (upload_result) { |
| case UploadResult::kSuccess: |
| // The upload completed successfully. |
| database_->RecordUploadComplete(std::move(upload_report), response_body); |
| if (MaxUploadsEnabled()) |
| recent_upload_ct_++; |
| ResetBackoff(); |
| break; |
| case UploadResult::kPermanentFailure: |
| // The upload should never be retried. |
| database_->SkipReportUpload(report.uuid, |
| Metrics::CrashSkippedReason::kUploadFailed); |
| break; |
| case UploadResult::kRetry: |
| // The upload will be retried after a reasonable backoff delay. Since we |
| // didn't successfully upload it we won't count it against the rate limit. |
| IncreaseBackoff(); |
| break; |
| } |
| } |
| |
| CrashReportUploadThread::ParameterMap |
| CefCrashReportUploadThread::FilterParameters(const ParameterMap& parameters) { |
| return crash_report_utils::FilterParameters(parameters); |
| } |
| |
| bool CefCrashReportUploadThread::UploadsEnabled() const { |
| Settings* const settings = database_->GetSettings(); |
| bool uploads_enabled; |
| return !url_.empty() && settings->GetUploadsEnabled(&uploads_enabled) && |
| uploads_enabled; |
| } |
| |
| bool CefCrashReportUploadThread::MaxUploadsEnabled() const { |
| return options_.rate_limit && max_uploads_ > 0; |
| } |
| |
| bool CefCrashReportUploadThread::MaxUploadsExceeded() const { |
| return MaxUploadsEnabled() && recent_upload_ct_ >= max_uploads_; |
| } |
| |
| bool CefCrashReportUploadThread::BackoffPending() const { |
| if (!options_.rate_limit) |
| return false; |
| |
| Settings* const settings = database_->GetSettings(); |
| |
| time_t next_upload_time; |
| if (settings->GetNextUploadAttemptTime(&next_upload_time) && |
| next_upload_time > 0) { |
| const time_t now = time(nullptr); |
| if (now < next_upload_time) |
| return true; |
| } |
| |
| return false; |
| } |
| |
| void CefCrashReportUploadThread::IncreaseBackoff() { |
| if (!options_.rate_limit) |
| return; |
| |
| const int kHour = 60 * 60; // 1 hour |
| const int kBackoffSchedule[] = { |
| kHour / 4, // 15 minutes |
| kHour, // 1 hour |
| kHour * 2, // 2 hours |
| kHour * 4, // 4 hours |
| kHour * 8, // 8 hours |
| kHour * 24, // 24 hours |
| }; |
| const int kBackoffScheduleSize = |
| sizeof(kBackoffSchedule) / sizeof(kBackoffSchedule[0]); |
| |
| Settings* settings = database_->GetSettings(); |
| |
| int backoff_step = 0; |
| if (settings->GetBackoffStep(&backoff_step) && backoff_step < 0) |
| backoff_step = 0; |
| if (++backoff_step > kBackoffScheduleSize) |
| backoff_step = kBackoffScheduleSize; |
| |
| time_t next_upload_time = time(nullptr); // now |
| next_upload_time += kBackoffSchedule[backoff_step - 1]; |
| |
| settings->SetBackoffStep(backoff_step); |
| settings->SetNextUploadAttemptTime(next_upload_time); |
| |
| if (max_uploads_ > 1) { |
| // If the server is having trouble then we don't want to send many crash |
| // reports after the backoff expires. Reduce max uploads to 1 per 24 hours |
| // until the client is restarted. |
| max_uploads_ = 1; |
| } |
| } |
| |
| void CefCrashReportUploadThread::ResetBackoff() { |
| if (!options_.rate_limit) |
| return; |
| |
| Settings* settings = database_->GetSettings(); |
| settings->SetBackoffStep(0); |
| settings->SetNextUploadAttemptTime(0); |
| } |