OLD | NEW |
---|---|
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 Loading... | |
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 Loading... | |
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 Loading... | |
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 Loading... | |
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 |
OLD | NEW |