cef/libcef/common/cef_crash_report_upload_thr...

248 lines
7.3 KiB
C++
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

// 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 "third_party/crashpad/crashpad/client/settings.h"
using namespace crashpad;
namespace {
// Calls CrashReportDatabase::RecordUploadAttempt() with |successful| set to
// false upon destruction unless disarmed by calling Fire() or Disarm(). Fire()
// triggers an immediate call. Armed upon construction.
class CallRecordUploadAttempt {
public:
CallRecordUploadAttempt(CrashReportDatabase* database,
const CrashReportDatabase::Report* report)
: database_(database),
report_(report) {
}
~CallRecordUploadAttempt() {
Fire();
}
void Fire() {
if (report_) {
database_->RecordUploadAttempt(report_, false, std::string());
}
Disarm();
}
void Disarm() {
report_ = nullptr;
}
private:
CrashReportDatabase* database_; // weak
const CrashReportDatabase::Report* report_; // weak
DISALLOW_COPY_AND_ASSIGN(CallRecordUploadAttempt);
};
} // namespace
CefCrashReportUploadThread::CefCrashReportUploadThread(
CrashReportDatabase* database,
const std::string& url,
bool rate_limit,
int max_uploads)
: CrashReportUploadThread(database, url, rate_limit),
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()) {
// Dont attempt an upload if theres no URL or if uploads have been
// disabled in the databases 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;
}
const CrashReportDatabase::Report* 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 its 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;
}
CallRecordUploadAttempt call_record_upload_attempt(database_, upload_report);
std::string response_body;
UploadResult upload_result = UploadReport(upload_report, &response_body);
switch (upload_result) {
case UploadResult::kSuccess:
// The upload completed successfully.
call_record_upload_attempt.Disarm();
database_->RecordUploadAttempt(upload_report, true, response_body);
if (MaxUploadsEnabled())
recent_upload_ct_++;
ResetBackoff();
break;
case UploadResult::kPermanentFailure:
// The upload should never be retried.
call_record_upload_attempt.Fire();
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;
}
}
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 rate_limit_ && max_uploads_ > 0;
}
bool CefCrashReportUploadThread::MaxUploadsExceeded() const {
return MaxUploadsEnabled() && recent_upload_ct_ >= max_uploads_;
}
bool CefCrashReportUploadThread::BackoffPending() const {
if (!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 (!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 (!rate_limit_)
return;
Settings* settings = database_->GetSettings();
settings->SetBackoffStep(0);
settings->SetNextUploadAttemptTime(0);
}