blob: 1c81feb29a3aba6ab02e38e17e09e0e637b7d504 [file] [log] [blame]
// 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);
}