Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(344)

Side by Side Diff: handler/mac/crash_report_upload_thread.cc

Issue 1295363002: Port CrashReportUploadThread to Windows (Closed) Base URL: https://chromium.googlesource.com/crashpad/crashpad@master
Patch Set: fixes Created 5 years, 4 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch
« no previous file with comments | « handler/mac/crash_report_upload_thread.h ('k') | handler/mac/main.cc » ('j') | no next file with comments »
Toggle Intra-line Diffs ('i') | Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
OLDNEW
(Empty)
1 // Copyright 2015 The Crashpad Authors. All rights reserved.
2 //
3 // Licensed under the Apache License, Version 2.0 (the "License");
4 // you may not use this file except in compliance with the License.
5 // You may obtain a copy of the License at
6 //
7 // http://www.apache.org/licenses/LICENSE-2.0
8 //
9 // Unless required by applicable law or agreed to in writing, software
10 // distributed under the License is distributed on an "AS IS" BASIS,
11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 // See the License for the specific language governing permissions and
13 // limitations under the License.
14
15 #include "handler/mac/crash_report_upload_thread.h"
16
17 #include <errno.h>
18 #include <time.h>
19
20 #include <map>
21 #include <vector>
22
23 #include "base/logging.h"
24 #include "base/memory/scoped_ptr.h"
25 #include "client/settings.h"
26 #include "snapshot/minidump/process_snapshot_minidump.h"
27 #include "snapshot/module_snapshot.h"
28 #include "util/file/file_reader.h"
29 #include "util/misc/uuid.h"
30 #include "util/net/http_body.h"
31 #include "util/net/http_multipart_builder.h"
32 #include "util/net/http_transport.h"
33 #include "util/stdlib/map_insert.h"
34
35 namespace crashpad {
36
37 namespace {
38
39 void InsertOrReplaceMapEntry(std::map<std::string, std::string>* map,
40 const std::string& key,
41 const std::string& value) {
42 std::string old_value;
43 if (!MapInsertOrReplace(map, key, value, &old_value)) {
44 LOG(WARNING) << "duplicate key " << key << ", discarding value "
45 << old_value;
46 }
47 }
48
49 // Given a minidump file readable by |minidump_file_reader|, returns a map of
50 // key-value pairs to use as HTTP form parameters for upload to a Breakpad
51 // server. The map is built by combining the process simple annotations map with
52 // each module’s simple annotations map. In the case of duplicate keys, the map
53 // will retain the first value found for any key, and will log a warning about
54 // discarded values. Each module’s annotations vector is also examined and built
55 // into a single string value, with distinct elements separated by newlines, and
56 // stored at the key named “list_annotations”, which supersedes any other key
57 // found by that name. The client ID stored in the minidump is converted to
58 // a string and stored at the key named “guid”, which supersedes any other key
59 // found by that name.
60 //
61 // In the event of an error reading the minidump file, a message will be logged.
62 std::map<std::string, std::string> BreakpadHTTPFormParametersFromMinidump(
63 FileReader* minidump_file_reader) {
64 ProcessSnapshotMinidump minidump_process_snapshot;
65 if (!minidump_process_snapshot.Initialize(minidump_file_reader)) {
66 return std::map<std::string, std::string>();
67 }
68
69 std::map<std::string, std::string> parameters =
70 minidump_process_snapshot.AnnotationsSimpleMap();
71
72 std::string list_annotations;
73 for (const ModuleSnapshot* module : minidump_process_snapshot.Modules()) {
74 for (const auto& kv : module->AnnotationsSimpleMap()) {
75 if (!parameters.insert(kv).second) {
76 LOG(WARNING) << "duplicate key " << kv.first << ", discarding value "
77 << kv.second;
78 }
79 }
80
81 for (std::string annotation : module->AnnotationsVector()) {
82 list_annotations.append(annotation);
83 list_annotations.append("\n");
84 }
85 }
86
87 if (!list_annotations.empty()) {
88 // Remove the final newline character.
89 list_annotations.resize(list_annotations.size() - 1);
90
91 InsertOrReplaceMapEntry(&parameters, "list_annotations", list_annotations);
92 }
93
94 UUID client_id;
95 minidump_process_snapshot.ClientID(&client_id);
96 InsertOrReplaceMapEntry(&parameters, "guid", client_id.ToString());
97
98 return parameters;
99 }
100
101 // Calls CrashReportDatabase::RecordUploadAttempt() with |successful| set to
102 // false upon destruction unless disarmed by calling Fire() or Disarm(). Fire()
103 // triggers an immediate call. Armed upon construction.
104 class CallRecordUploadAttempt {
105 public:
106 CallRecordUploadAttempt(CrashReportDatabase* database,
107 const CrashReportDatabase::Report* report)
108 : database_(database),
109 report_(report) {
110 }
111
112 ~CallRecordUploadAttempt() {
113 Fire();
114 }
115
116 void Fire() {
117 if (report_) {
118 database_->RecordUploadAttempt(report_, false, std::string());
119 }
120
121 Disarm();
122 }
123
124 void Disarm() {
125 report_ = nullptr;
126 }
127
128 private:
129 CrashReportDatabase* database_; // weak
130 const CrashReportDatabase::Report* report_; // weak
131
132 DISALLOW_COPY_AND_ASSIGN(CallRecordUploadAttempt);
133 };
134
135 } // namespace
136
137 CrashReportUploadThread::CrashReportUploadThread(CrashReportDatabase* database,
138 const std::string& url)
139 : url_(url),
140 database_(database),
141 semaphore_(0),
142 thread_(0),
143 running_(false) {
144 }
145
146 CrashReportUploadThread::~CrashReportUploadThread() {
147 DCHECK(!running_);
148 DCHECK(!thread_);
149 }
150
151 void CrashReportUploadThread::Start() {
152 DCHECK(!running_);
153 DCHECK(!thread_);
154
155 running_ = true;
156 if ((errno = pthread_create(&thread_, nullptr, RunThreadMain, this)) != 0) {
157 PLOG(ERROR) << "pthread_create";
158 DCHECK(false);
159 running_ = false;
160 }
161 }
162
163 void CrashReportUploadThread::Stop() {
164 DCHECK(running_);
165 DCHECK(thread_);
166
167 if (!running_) {
168 return;
169 }
170
171 running_ = false;
172 semaphore_.Signal();
173
174 if ((errno = pthread_join(thread_, nullptr)) != 0) {
175 PLOG(ERROR) << "pthread_join";
176 DCHECK(false);
177 }
178
179 thread_ = 0;
180 }
181
182 void CrashReportUploadThread::ReportPending() {
183 semaphore_.Signal();
184 }
185
186 void CrashReportUploadThread::ThreadMain() {
187 while (running_) {
188 ProcessPendingReports();
189
190 // Check for pending reports every 15 minutes, even in the absence of a
191 // signal from the handler thread. This allows for failed uploads to be
192 // retried periodically, and for pending reports written by other processes
193 // to be recognized.
194 semaphore_.TimedWait(15 * 60);
195 }
196 }
197
198 void CrashReportUploadThread::ProcessPendingReports() {
199 std::vector<CrashReportDatabase::Report> reports;
200 if (database_->GetPendingReports(&reports) != CrashReportDatabase::kNoError) {
201 // The database is sick. It might be prudent to stop trying to poke it from
202 // this thread by abandoning the thread altogether. On the other hand, if
203 // the problem is transient, it might be possible to talk to it again on the
204 // next pass. For now, take the latter approach.
205 return;
206 }
207
208 for (const CrashReportDatabase::Report& report : reports) {
209 ProcessPendingReport(report);
210
211 // Respect Stop() being called after at least one attempt to process a
212 // report.
213 if (!running_) {
214 return;
215 }
216 }
217 }
218
219 void CrashReportUploadThread::ProcessPendingReport(
220 const CrashReportDatabase::Report& report) {
221 Settings* const settings = database_->GetSettings();
222
223 bool uploads_enabled;
224 if (!settings->GetUploadsEnabled(&uploads_enabled) ||
225 !uploads_enabled ||
226 url_.empty()) {
227 // If the upload-enabled state can’t be determined, uploads are disabled, or
228 // there’s no URL to upload to, don’t attempt to upload the new report.
229 database_->SkipReportUpload(report.uuid);
230 return;
231 }
232
233 // This currently implements very simplistic rate-limiting, compatible with
234 // the Breakpad client, where the strategy is to permit one upload attempt per
235 // hour, and retire reports that would exceed this limit or for which the
236 // upload fails on the first attempt.
237 //
238 // TODO(mark): Provide a proper rate-limiting strategy and allow for failed
239 // upload attempts to be retried.
240 time_t last_upload_attempt_time;
241 if (settings->GetLastUploadAttemptTime(&last_upload_attempt_time)) {
242 time_t now = time(nullptr);
243 if (now >= last_upload_attempt_time) {
244 // If the most recent upload attempt occurred within the past hour, don’t
245 // attempt to upload the new report. If it happened longer ago, attempt to
246 // upload the report.
247 const int kUploadAttemptIntervalSeconds = 60 * 60; // 1 hour
248 if (now - last_upload_attempt_time < kUploadAttemptIntervalSeconds) {
249 database_->SkipReportUpload(report.uuid);
250 return;
251 }
252 } else {
253 // The most recent upload attempt purportedly occurred in the future. If
254 // it “happened” at least one day in the future, assume that the last
255 // upload attempt time is bogus, and attempt to upload the report. If the
256 // most recent upload time is in the future but within one day, accept it
257 // and don’t attempt to upload the report.
258 const int kBackwardsClockTolerance = 60 * 60 * 24; // 1 day
259 if (last_upload_attempt_time - now < kBackwardsClockTolerance) {
260 database_->SkipReportUpload(report.uuid);
261 return;
262 }
263 }
264 }
265
266 const CrashReportDatabase::Report* upload_report;
267 CrashReportDatabase::OperationStatus status =
268 database_->GetReportForUploading(report.uuid, &upload_report);
269 switch (status) {
270 case CrashReportDatabase::kNoError:
271 break;
272
273 case CrashReportDatabase::kBusyError:
274 return;
275
276 case CrashReportDatabase::kReportNotFound:
277 case CrashReportDatabase::kFileSystemError:
278 case CrashReportDatabase::kDatabaseError:
279 // In these cases, SkipReportUpload() might not work either, but it’s best
280 // to at least try to get the report out of the way.
281 database_->SkipReportUpload(report.uuid);
282 return;
283 }
284
285 CallRecordUploadAttempt call_record_upload_attempt(database_, upload_report);
286
287 std::string response_body;
288 UploadResult upload_result = UploadReport(upload_report, &response_body);
289 switch (upload_result) {
290 case UploadResult::kSuccess:
291 call_record_upload_attempt.Disarm();
292 database_->RecordUploadAttempt(upload_report, true, response_body);
293 break;
294 case UploadResult::kPermanentFailure:
295 case UploadResult::kRetry:
296 call_record_upload_attempt.Fire();
297
298 // TODO(mark): Deal with retries properly: don’t call SkipReportUplaod()
299 // if the result was kRetry and the report hasn’t already been retried
300 // too many times.
301 database_->SkipReportUpload(report.uuid);
302 break;
303 }
304 }
305
306 CrashReportUploadThread::UploadResult CrashReportUploadThread::UploadReport(
307 const CrashReportDatabase::Report* report,
308 std::string* response_body) {
309 std::map<std::string, std::string> parameters;
310
311 {
312 FileReader minidump_file_reader;
313 if (!minidump_file_reader.Open(report->file_path)) {
314 // If the minidump file can’t be opened, all hope is lost.
315 return UploadResult::kPermanentFailure;
316 }
317
318 // If the minidump file could be opened, ignore any errors that might occur
319 // when attempting to interpret it. This may result in its being uploaded
320 // with few or no parameters, but as long as there’s a dump file, the server
321 // can decide what to do with it.
322 parameters = BreakpadHTTPFormParametersFromMinidump(&minidump_file_reader);
323 }
324
325 HTTPMultipartBuilder http_multipart_builder;
326
327 const char kMinidumpKey[] = "upload_file_minidump";
328
329 for (const auto& kv : parameters) {
330 if (kv.first == kMinidumpKey) {
331 LOG(WARNING) << "reserved key " << kv.first << ", discarding value "
332 << kv.second;
333 } else {
334 http_multipart_builder.SetFormData(kv.first, kv.second);
335 }
336 }
337
338 http_multipart_builder.SetFileAttachment(kMinidumpKey,
339 report->file_path.BaseName().value(),
340 report->file_path,
341 "application/octet-stream");
342
343 scoped_ptr<HTTPTransport> http_transport(HTTPTransport::Create());
344 http_transport->SetURL(url_);
345 HTTPHeaders::value_type content_type =
346 http_multipart_builder.GetContentType();
347 http_transport->SetHeader(content_type.first, content_type.second);
348 http_transport->SetBodyStream(http_multipart_builder.GetBodyStream().Pass());
349 // TODO(mark): The timeout should be configurable by the client.
350 http_transport->SetTimeout(60.0); // 1 minute.
351
352 if (!http_transport->ExecuteSynchronously(response_body)) {
353 return UploadResult::kRetry;
354 }
355
356 return UploadResult::kSuccess;
357 }
358
359 // static
360 void* CrashReportUploadThread::RunThreadMain(void* arg) {
361 CrashReportUploadThread* self = static_cast<CrashReportUploadThread*>(arg);
362 self->ThreadMain();
363 return nullptr;
364 }
365
366 } // namespace crashpad
OLDNEW
« no previous file with comments | « handler/mac/crash_report_upload_thread.h ('k') | handler/mac/main.cc » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698