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