• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 // Copyright 2016 The Chromium Embedded Framework Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4 
5 #include "libcef/common/cef_crash_report_upload_thread.h"
6 
7 #include "base/notreached.h"
8 #include "libcef/common/cef_crash_report_utils.h"
9 #include "third_party/crashpad/crashpad/client/settings.h"
10 
11 using namespace crashpad;
12 
CefCrashReportUploadThread(CrashReportDatabase * database,const std::string & url,const Options & options,int max_uploads)13 CefCrashReportUploadThread::CefCrashReportUploadThread(
14     CrashReportDatabase* database,
15     const std::string& url,
16     const Options& options,
17     int max_uploads)
18     : CrashReportUploadThread(database, url, options),
19       max_uploads_(max_uploads) {}
20 
~CefCrashReportUploadThread()21 CefCrashReportUploadThread::~CefCrashReportUploadThread() {}
22 
ProcessPendingReports()23 void CefCrashReportUploadThread::ProcessPendingReports() {
24   if (BackoffPending()) {
25     // Try again later.
26     return;
27   }
28 
29   if (MaxUploadsEnabled()) {
30     // Retrieve all completed reports.
31     std::vector<CrashReportDatabase::Report> reports;
32     if (database_->GetCompletedReports(&reports) !=
33         CrashReportDatabase::kNoError) {
34       // The database is sick. It might be prudent to stop trying to poke it
35       // from this thread by abandoning the thread altogether. On the other
36       // hand, if the problem is transient, it might be possible to talk to it
37       // again on the next pass. For now, take the latter approach.
38       return;
39     }
40 
41     const time_t now = time(nullptr);
42     const int kSeconds = 60 * 60 * 24;  // 24 hours
43 
44     // Count how many reports have completed in the last 24 hours.
45     recent_upload_ct_ = 0;
46     for (const CrashReportDatabase::Report& report : reports) {
47       if (report.last_upload_attempt_time > now - kSeconds)
48         recent_upload_ct_++;
49     }
50   }
51 
52   // Continue with processing pending reports.
53   CrashReportUploadThread::ProcessPendingReports();
54 }
55 
ProcessPendingReport(const CrashReportDatabase::Report & report)56 void CefCrashReportUploadThread::ProcessPendingReport(
57     const CrashReportDatabase::Report& report) {
58   // Always allow upload if it's been explicitly requested by the user.
59   if (!report.upload_explicitly_requested) {
60     if (!UploadsEnabled()) {
61       // Don’t attempt an upload if there’s no URL or if uploads have been
62       // disabled in the database’s settings.
63       database_->SkipReportUpload(
64           report.uuid, Metrics::CrashSkippedReason::kUploadsDisabled);
65       return;
66     }
67 
68     if (MaxUploadsExceeded()) {
69       // Don't send uploads if the rate limit has been exceeded.
70       database_->SkipReportUpload(
71           report.uuid, Metrics::CrashSkippedReason::kUploadThrottled);
72       return;
73     }
74   }
75 
76   if (BackoffPending()) {
77     // Try again later.
78     return;
79   }
80 
81   std::unique_ptr<const CrashReportDatabase::UploadReport> upload_report;
82   CrashReportDatabase::OperationStatus status =
83       database_->GetReportForUploading(report.uuid, &upload_report);
84   switch (status) {
85     case CrashReportDatabase::kNoError:
86       break;
87 
88     case CrashReportDatabase::kBusyError:
89       return;
90 
91     case CrashReportDatabase::kReportNotFound:
92     case CrashReportDatabase::kFileSystemError:
93     case CrashReportDatabase::kDatabaseError:
94       // In these cases, SkipReportUpload() might not work either, but it’s best
95       // to at least try to get the report out of the way.
96       database_->SkipReportUpload(report.uuid,
97                                   Metrics::CrashSkippedReason::kDatabaseError);
98       return;
99 
100     case CrashReportDatabase::kCannotRequestUpload:
101       NOTREACHED();
102       return;
103   }
104 
105   std::string response_body;
106   UploadResult upload_result =
107       UploadReport(upload_report.get(), &response_body);
108   switch (upload_result) {
109     case UploadResult::kSuccess:
110       // The upload completed successfully.
111       database_->RecordUploadComplete(std::move(upload_report), response_body);
112       if (MaxUploadsEnabled())
113         recent_upload_ct_++;
114       ResetBackoff();
115       break;
116     case UploadResult::kPermanentFailure:
117       // The upload should never be retried.
118       database_->SkipReportUpload(report.uuid,
119                                   Metrics::CrashSkippedReason::kUploadFailed);
120       break;
121     case UploadResult::kRetry:
122       // The upload will be retried after a reasonable backoff delay. Since we
123       // didn't successfully upload it we won't count it against the rate limit.
124       IncreaseBackoff();
125       break;
126   }
127 }
128 
129 CrashReportUploadThread::ParameterMap
FilterParameters(const ParameterMap & parameters)130 CefCrashReportUploadThread::FilterParameters(const ParameterMap& parameters) {
131   return crash_report_utils::FilterParameters(parameters);
132 }
133 
UploadsEnabled() const134 bool CefCrashReportUploadThread::UploadsEnabled() const {
135   Settings* const settings = database_->GetSettings();
136   bool uploads_enabled;
137   return !url_.empty() && settings->GetUploadsEnabled(&uploads_enabled) &&
138          uploads_enabled;
139 }
140 
MaxUploadsEnabled() const141 bool CefCrashReportUploadThread::MaxUploadsEnabled() const {
142   return options_.rate_limit && max_uploads_ > 0;
143 }
144 
MaxUploadsExceeded() const145 bool CefCrashReportUploadThread::MaxUploadsExceeded() const {
146   return MaxUploadsEnabled() && recent_upload_ct_ >= max_uploads_;
147 }
148 
BackoffPending() const149 bool CefCrashReportUploadThread::BackoffPending() const {
150   if (!options_.rate_limit)
151     return false;
152 
153   Settings* const settings = database_->GetSettings();
154 
155   time_t next_upload_time;
156   if (settings->GetNextUploadAttemptTime(&next_upload_time) &&
157       next_upload_time > 0) {
158     const time_t now = time(nullptr);
159     if (now < next_upload_time)
160       return true;
161   }
162 
163   return false;
164 }
165 
IncreaseBackoff()166 void CefCrashReportUploadThread::IncreaseBackoff() {
167   if (!options_.rate_limit)
168     return;
169 
170   const int kHour = 60 * 60;  // 1 hour
171   const int kBackoffSchedule[] = {
172       kHour / 4,   // 15 minutes
173       kHour,       // 1 hour
174       kHour * 2,   // 2 hours
175       kHour * 4,   // 4 hours
176       kHour * 8,   // 8 hours
177       kHour * 24,  // 24 hours
178   };
179   const int kBackoffScheduleSize =
180       sizeof(kBackoffSchedule) / sizeof(kBackoffSchedule[0]);
181 
182   Settings* settings = database_->GetSettings();
183 
184   int backoff_step = 0;
185   if (settings->GetBackoffStep(&backoff_step) && backoff_step < 0)
186     backoff_step = 0;
187   if (++backoff_step > kBackoffScheduleSize)
188     backoff_step = kBackoffScheduleSize;
189 
190   time_t next_upload_time = time(nullptr);  // now
191   next_upload_time += kBackoffSchedule[backoff_step - 1];
192 
193   settings->SetBackoffStep(backoff_step);
194   settings->SetNextUploadAttemptTime(next_upload_time);
195 
196   if (max_uploads_ > 1) {
197     // If the server is having trouble then we don't want to send many crash
198     // reports after the backoff expires. Reduce max uploads to 1 per 24 hours
199     // until the client is restarted.
200     max_uploads_ = 1;
201   }
202 }
203 
ResetBackoff()204 void CefCrashReportUploadThread::ResetBackoff() {
205   if (!options_.rate_limit)
206     return;
207 
208   Settings* settings = database_->GetSettings();
209   settings->SetBackoffStep(0);
210   settings->SetNextUploadAttemptTime(0);
211 }
212