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

Side by Side Diff: content/browser/dom_storage/dom_storage_area.cc

Issue 896643002: [DOMStorage] Rate limiting writes to disk. (Closed) Base URL: https://chromium.googlesource.com/chromium/src.git@master
Patch Set: Created 5 years, 10 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
OLDNEW
1 // Copyright 2013 The Chromium Authors. All rights reserved. 1 // Copyright 2013 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be 2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file. 3 // found in the LICENSE file.
4 4
5 #include "content/browser/dom_storage/dom_storage_area.h" 5 #include "content/browser/dom_storage/dom_storage_area.h"
6 6
7 #include <algorithm>
8
7 #include "base/bind.h" 9 #include "base/bind.h"
8 #include "base/location.h" 10 #include "base/location.h"
9 #include "base/logging.h" 11 #include "base/logging.h"
10 #include "base/metrics/histogram.h" 12 #include "base/metrics/histogram.h"
11 #include "base/strings/utf_string_conversions.h" 13 #include "base/strings/utf_string_conversions.h"
12 #include "base/time/time.h" 14 #include "base/time/time.h"
13 #include "content/browser/dom_storage/dom_storage_namespace.h" 15 #include "content/browser/dom_storage/dom_storage_namespace.h"
14 #include "content/browser/dom_storage/dom_storage_task_runner.h" 16 #include "content/browser/dom_storage/dom_storage_task_runner.h"
15 #include "content/browser/dom_storage/local_storage_database_adapter.h" 17 #include "content/browser/dom_storage/local_storage_database_adapter.h"
16 #include "content/browser/dom_storage/session_storage_database.h" 18 #include "content/browser/dom_storage/session_storage_database.h"
17 #include "content/browser/dom_storage/session_storage_database_adapter.h" 19 #include "content/browser/dom_storage/session_storage_database_adapter.h"
18 #include "content/common/dom_storage/dom_storage_map.h" 20 #include "content/common/dom_storage/dom_storage_map.h"
19 #include "content/common/dom_storage/dom_storage_types.h" 21 #include "content/common/dom_storage/dom_storage_types.h"
20 #include "storage/browser/database/database_util.h" 22 #include "storage/browser/database/database_util.h"
21 #include "storage/common/database/database_identifier.h" 23 #include "storage/common/database/database_identifier.h"
22 #include "storage/common/fileapi/file_system_util.h" 24 #include "storage/common/fileapi/file_system_util.h"
23 25
24 using storage::DatabaseUtil; 26 using storage::DatabaseUtil;
25 27
26 namespace content { 28 namespace content {
27 29
28 static const int kCommitTimerSeconds = 1; 30 // Delay for a moment after a value is set in anticipation
31 // of other values being set, so changes are batched.
32 static const int kCommitTimerDefaultDelay = 5;
33
34 // Avoid committing too frequently regardless of the amount of data
35 // being written.
36 static const int kMaxCommitsPerHour = 6;
37
38 // A data rate limit applies to the size of the key/value pairs being written.
39 // A rate of 500k per hour is enough to fully populate an origins area two
40 // times over.
41 static const int kMaxDataPerHour = 500 * 1024;;
cmumford 2015/02/03 00:45:24 double semicolon
michaeln 2015/02/03 19:59:14 Done. Also made this a function of the constant pe
29 42
30 DOMStorageArea::CommitBatch::CommitBatch() 43 DOMStorageArea::CommitBatch::CommitBatch()
31 : clear_all_first(false) { 44 : clear_all_first(false) {
32 } 45 }
33 DOMStorageArea::CommitBatch::~CommitBatch() {} 46 DOMStorageArea::CommitBatch::~CommitBatch() {}
34 47
48 size_t DOMStorageArea::CommitBatch::GetDataSize() {
49 return DOMStorageMap::CountBytes(changed_values);
50 }
35 51
36 // static 52 // static
37 const base::FilePath::CharType DOMStorageArea::kDatabaseFileExtension[] = 53 const base::FilePath::CharType DOMStorageArea::kDatabaseFileExtension[] =
38 FILE_PATH_LITERAL(".localstorage"); 54 FILE_PATH_LITERAL(".localstorage");
39 55
40 // static 56 // static
41 base::FilePath DOMStorageArea::DatabaseFileNameFromOrigin(const GURL& origin) { 57 base::FilePath DOMStorageArea::DatabaseFileNameFromOrigin(const GURL& origin) {
42 std::string filename = storage::GetIdentifierFromOrigin(origin); 58 std::string filename = storage::GetIdentifierFromOrigin(origin);
43 // There is no base::FilePath.AppendExtension() method, so start with just the 59 // There is no base::FilePath.AppendExtension() method, so start with just the
44 // extension as the filename, and then InsertBeforeExtension the desired 60 // extension as the filename, and then InsertBeforeExtension the desired
(...skipping 13 matching lines...) Expand all
58 DOMStorageArea::DOMStorageArea( 74 DOMStorageArea::DOMStorageArea(
59 const GURL& origin, const base::FilePath& directory, 75 const GURL& origin, const base::FilePath& directory,
60 DOMStorageTaskRunner* task_runner) 76 DOMStorageTaskRunner* task_runner)
61 : namespace_id_(kLocalStorageNamespaceId), origin_(origin), 77 : namespace_id_(kLocalStorageNamespaceId), origin_(origin),
62 directory_(directory), 78 directory_(directory),
63 task_runner_(task_runner), 79 task_runner_(task_runner),
64 map_(new DOMStorageMap(kPerStorageAreaQuota + 80 map_(new DOMStorageMap(kPerStorageAreaQuota +
65 kPerStorageAreaOverQuotaAllowance)), 81 kPerStorageAreaOverQuotaAllowance)),
66 is_initial_import_done_(true), 82 is_initial_import_done_(true),
67 is_shutdown_(false), 83 is_shutdown_(false),
68 commit_batches_in_flight_(0) { 84 commit_batches_in_flight_(0),
85 start_time_(base::Time::Now()),
86 data_rate_limiter_(kMaxDataPerHour, base::TimeDelta::FromHours(1)),
87 commit_rate_limiter_(kMaxCommitsPerHour, base::TimeDelta::FromHours(1)) {
69 if (!directory.empty()) { 88 if (!directory.empty()) {
70 base::FilePath path = directory.Append(DatabaseFileNameFromOrigin(origin_)); 89 base::FilePath path = directory.Append(DatabaseFileNameFromOrigin(origin_));
71 backing_.reset(new LocalStorageDatabaseAdapter(path)); 90 backing_.reset(new LocalStorageDatabaseAdapter(path));
72 is_initial_import_done_ = false; 91 is_initial_import_done_ = false;
73 } 92 }
74 } 93 }
75 94
76 DOMStorageArea::DOMStorageArea( 95 DOMStorageArea::DOMStorageArea(
77 int64 namespace_id, 96 int64 namespace_id,
78 const std::string& persistent_namespace_id, 97 const std::string& persistent_namespace_id,
79 const GURL& origin, 98 const GURL& origin,
80 SessionStorageDatabase* session_storage_backing, 99 SessionStorageDatabase* session_storage_backing,
81 DOMStorageTaskRunner* task_runner) 100 DOMStorageTaskRunner* task_runner)
82 : namespace_id_(namespace_id), 101 : namespace_id_(namespace_id),
83 persistent_namespace_id_(persistent_namespace_id), 102 persistent_namespace_id_(persistent_namespace_id),
84 origin_(origin), 103 origin_(origin),
85 task_runner_(task_runner), 104 task_runner_(task_runner),
86 map_(new DOMStorageMap(kPerStorageAreaQuota + 105 map_(new DOMStorageMap(kPerStorageAreaQuota +
87 kPerStorageAreaOverQuotaAllowance)), 106 kPerStorageAreaOverQuotaAllowance)),
88 session_storage_backing_(session_storage_backing), 107 session_storage_backing_(session_storage_backing),
89 is_initial_import_done_(true), 108 is_initial_import_done_(true),
90 is_shutdown_(false), 109 is_shutdown_(false),
91 commit_batches_in_flight_(0) { 110 commit_batches_in_flight_(0),
111 start_time_(base::Time::Now()),
112 data_rate_limiter_(kMaxDataPerHour, base::TimeDelta::FromHours(1)),
113 commit_rate_limiter_(kMaxCommitsPerHour, base::TimeDelta::FromHours(1)) {
92 DCHECK(namespace_id != kLocalStorageNamespaceId); 114 DCHECK(namespace_id != kLocalStorageNamespaceId);
93 if (session_storage_backing) { 115 if (session_storage_backing) {
94 backing_.reset(new SessionStorageDatabaseAdapter( 116 backing_.reset(new SessionStorageDatabaseAdapter(
95 session_storage_backing, persistent_namespace_id, origin)); 117 session_storage_backing, persistent_namespace_id, origin));
96 is_initial_import_done_ = false; 118 is_initial_import_done_ = false;
97 } 119 }
98 } 120 }
99 121
100 DOMStorageArea::~DOMStorageArea() { 122 DOMStorageArea::~DOMStorageArea() {
101 } 123 }
(...skipping 28 matching lines...) Expand all
130 152
131 bool DOMStorageArea::SetItem(const base::string16& key, 153 bool DOMStorageArea::SetItem(const base::string16& key,
132 const base::string16& value, 154 const base::string16& value,
133 base::NullableString16* old_value) { 155 base::NullableString16* old_value) {
134 if (is_shutdown_) 156 if (is_shutdown_)
135 return false; 157 return false;
136 InitialImportIfNeeded(); 158 InitialImportIfNeeded();
137 if (!map_->HasOneRef()) 159 if (!map_->HasOneRef())
138 map_ = map_->DeepCopy(); 160 map_ = map_->DeepCopy();
139 bool success = map_->SetItem(key, value, old_value); 161 bool success = map_->SetItem(key, value, old_value);
140 if (success && backing_) { 162 if (success && backing_ &&
163 (old_value->is_null() || old_value->string() != value)) {
141 CommitBatch* commit_batch = CreateCommitBatchIfNeeded(); 164 CommitBatch* commit_batch = CreateCommitBatchIfNeeded();
142 commit_batch->changed_values[key] = base::NullableString16(value, false); 165 commit_batch->changed_values[key] = base::NullableString16(value, false);
143 } 166 }
144 return success; 167 return success;
145 } 168 }
146 169
147 bool DOMStorageArea::RemoveItem(const base::string16& key, 170 bool DOMStorageArea::RemoveItem(const base::string16& key,
148 base::string16* old_value) { 171 base::string16* old_value) {
149 if (is_shutdown_) 172 if (is_shutdown_)
150 return false; 173 return false;
(...skipping 171 matching lines...) Expand 10 before | Expand all | Expand 10 after
322 if (!commit_batch_) { 345 if (!commit_batch_) {
323 commit_batch_.reset(new CommitBatch()); 346 commit_batch_.reset(new CommitBatch());
324 347
325 // Start a timer to commit any changes that accrue in the batch, but only if 348 // Start a timer to commit any changes that accrue in the batch, but only if
326 // no commits are currently in flight. In that case the timer will be 349 // no commits are currently in flight. In that case the timer will be
327 // started after the commits have happened. 350 // started after the commits have happened.
328 if (!commit_batches_in_flight_) { 351 if (!commit_batches_in_flight_) {
329 task_runner_->PostDelayedTask( 352 task_runner_->PostDelayedTask(
330 FROM_HERE, 353 FROM_HERE,
331 base::Bind(&DOMStorageArea::OnCommitTimer, this), 354 base::Bind(&DOMStorageArea::OnCommitTimer, this),
332 base::TimeDelta::FromSeconds(kCommitTimerSeconds)); 355 ComputeCommitDelay());
333 } 356 }
334 } 357 }
335 return commit_batch_.get(); 358 return commit_batch_.get();
336 } 359 }
337 360
361 base::TimeDelta DOMStorageArea::ComputeCommitDelay() {
362 base::TimeDelta elapsed_time = base::Time::Now() - start_time_;
363 return std::max(
364 base::TimeDelta::FromSeconds(kCommitTimerDefaultDelay),
365 std::max(commit_rate_limiter_.ComputeDelayNeeded(elapsed_time),
366 data_rate_limiter_.ComputeDelayNeeded(elapsed_time)));
367 }
368
338 void DOMStorageArea::OnCommitTimer() { 369 void DOMStorageArea::OnCommitTimer() {
339 if (is_shutdown_) 370 if (is_shutdown_)
340 return; 371 return;
341 372
342 DCHECK(backing_.get()); 373 DCHECK(backing_.get());
343 374
344 // It's possible that there is nothing to commit, since a shallow copy occured 375 // It's possible that there is nothing to commit, since a shallow copy occured
345 // before the timer fired. 376 // before the timer fired.
346 if (!commit_batch_) 377 if (!commit_batch_)
347 return; 378 return;
348 379
380 commit_rate_limiter_.AddSamples(1);
381 data_rate_limiter_.AddSamples(commit_batch_->GetDataSize());
382
349 // This method executes on the primary sequence, we schedule 383 // This method executes on the primary sequence, we schedule
350 // a task for immediate execution on the commit sequence. 384 // a task for immediate execution on the commit sequence.
351 DCHECK(task_runner_->IsRunningOnPrimarySequence()); 385 DCHECK(task_runner_->IsRunningOnPrimarySequence());
352 bool success = task_runner_->PostShutdownBlockingTask( 386 bool success = task_runner_->PostShutdownBlockingTask(
353 FROM_HERE, 387 FROM_HERE,
354 DOMStorageTaskRunner::COMMIT_SEQUENCE, 388 DOMStorageTaskRunner::COMMIT_SEQUENCE,
355 base::Bind(&DOMStorageArea::CommitChanges, this, 389 base::Bind(&DOMStorageArea::CommitChanges, this,
356 base::Owned(commit_batch_.release()))); 390 base::Owned(commit_batch_.release())));
357 ++commit_batches_in_flight_; 391 ++commit_batches_in_flight_;
358 DCHECK(success); 392 DCHECK(success);
(...skipping 15 matching lines...) Expand all
374 // We're back on the primary sequence in this method. 408 // We're back on the primary sequence in this method.
375 DCHECK(task_runner_->IsRunningOnPrimarySequence()); 409 DCHECK(task_runner_->IsRunningOnPrimarySequence());
376 --commit_batches_in_flight_; 410 --commit_batches_in_flight_;
377 if (is_shutdown_) 411 if (is_shutdown_)
378 return; 412 return;
379 if (commit_batch_.get() && !commit_batches_in_flight_) { 413 if (commit_batch_.get() && !commit_batches_in_flight_) {
380 // More changes have accrued, restart the timer. 414 // More changes have accrued, restart the timer.
381 task_runner_->PostDelayedTask( 415 task_runner_->PostDelayedTask(
382 FROM_HERE, 416 FROM_HERE,
383 base::Bind(&DOMStorageArea::OnCommitTimer, this), 417 base::Bind(&DOMStorageArea::OnCommitTimer, this),
384 base::TimeDelta::FromSeconds(kCommitTimerSeconds)); 418 ComputeCommitDelay());
385 } 419 }
386 } 420 }
387 421
388 void DOMStorageArea::ShutdownInCommitSequence() { 422 void DOMStorageArea::ShutdownInCommitSequence() {
389 // This method executes on the commit sequence. 423 // This method executes on the commit sequence.
390 DCHECK(task_runner_->IsRunningOnCommitSequence()); 424 DCHECK(task_runner_->IsRunningOnCommitSequence());
391 DCHECK(backing_.get()); 425 DCHECK(backing_.get());
392 if (commit_batch_) { 426 if (commit_batch_) {
393 // Commit any changes that accrued prior to the timer firing. 427 // Commit any changes that accrued prior to the timer firing.
394 bool success = backing_->CommitChanges( 428 bool success = backing_->CommitChanges(
395 commit_batch_->clear_all_first, 429 commit_batch_->clear_all_first,
396 commit_batch_->changed_values); 430 commit_batch_->changed_values);
397 DCHECK(success); 431 DCHECK(success);
398 } 432 }
399 commit_batch_.reset(); 433 commit_batch_.reset();
400 backing_.reset(); 434 backing_.reset();
401 session_storage_backing_ = NULL; 435 session_storage_backing_ = NULL;
402 } 436 }
403 437
404 } // namespace content 438 } // namespace content
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698