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" |
| 13 #include "base/process/process_info.h" |
11 #include "base/strings/utf_string_conversions.h" | 14 #include "base/strings/utf_string_conversions.h" |
12 #include "base/time/time.h" | 15 #include "base/time/time.h" |
13 #include "content/browser/dom_storage/dom_storage_namespace.h" | 16 #include "content/browser/dom_storage/dom_storage_namespace.h" |
14 #include "content/browser/dom_storage/dom_storage_task_runner.h" | 17 #include "content/browser/dom_storage/dom_storage_task_runner.h" |
15 #include "content/browser/dom_storage/local_storage_database_adapter.h" | 18 #include "content/browser/dom_storage/local_storage_database_adapter.h" |
16 #include "content/browser/dom_storage/session_storage_database.h" | 19 #include "content/browser/dom_storage/session_storage_database.h" |
17 #include "content/browser/dom_storage/session_storage_database_adapter.h" | 20 #include "content/browser/dom_storage/session_storage_database_adapter.h" |
18 #include "content/common/dom_storage/dom_storage_map.h" | 21 #include "content/common/dom_storage/dom_storage_map.h" |
19 #include "content/common/dom_storage/dom_storage_types.h" | 22 #include "content/common/dom_storage/dom_storage_types.h" |
20 #include "storage/browser/database/database_util.h" | 23 #include "storage/browser/database/database_util.h" |
21 #include "storage/common/database/database_identifier.h" | 24 #include "storage/common/database/database_identifier.h" |
22 #include "storage/common/fileapi/file_system_util.h" | 25 #include "storage/common/fileapi/file_system_util.h" |
23 | 26 |
24 using storage::DatabaseUtil; | 27 using storage::DatabaseUtil; |
25 | 28 |
26 namespace content { | 29 namespace content { |
27 | 30 |
28 static const int kCommitTimerSeconds = 1; | 31 namespace { |
29 | 32 |
30 DOMStorageArea::CommitBatch::CommitBatch() | 33 // Delay for a moment after a value is set in anticipation |
31 : clear_all_first(false) { | 34 // of other values being set, so changes are batched. |
| 35 const int kCommitDefaultDelaySecs = 5; |
| 36 |
| 37 // To avoid excessive IO we apply limits to the amount of data being written |
| 38 // and the frequency of writes. The specific values used are somewhat arbitrary. |
| 39 const int kMaxBytesPerDay = kPerStorageAreaQuota * 2; |
| 40 const int kMaxCommitsPerHour = 6; |
| 41 |
| 42 } // namespace |
| 43 |
| 44 DOMStorageArea::RateLimiter::RateLimiter(size_t desired_rate, |
| 45 base::TimeDelta time_quantum) |
| 46 : rate_(desired_rate), samples_(0), time_quantum_(time_quantum) { |
| 47 DCHECK_GT(desired_rate, 0ul); |
| 48 } |
| 49 |
| 50 base::TimeDelta DOMStorageArea::RateLimiter::ComputeTimeNeeded() const { |
| 51 return time_quantum_.multiply_by(samples_ / rate_); |
| 52 } |
| 53 |
| 54 base::TimeDelta DOMStorageArea::RateLimiter::ComputeDelayNeeded( |
| 55 const base::TimeDelta elapsed_time) const { |
| 56 base::TimeDelta time_needed = ComputeTimeNeeded(); |
| 57 if (time_needed > elapsed_time) |
| 58 return time_needed - elapsed_time; |
| 59 return base::TimeDelta(); |
| 60 } |
| 61 |
| 62 DOMStorageArea::CommitBatch::CommitBatch() : clear_all_first(false) { |
32 } | 63 } |
33 DOMStorageArea::CommitBatch::~CommitBatch() {} | 64 DOMStorageArea::CommitBatch::~CommitBatch() {} |
34 | 65 |
| 66 size_t DOMStorageArea::CommitBatch::GetDataSize() const { |
| 67 return DOMStorageMap::CountBytes(changed_values); |
| 68 } |
35 | 69 |
36 // static | 70 // static |
37 const base::FilePath::CharType DOMStorageArea::kDatabaseFileExtension[] = | 71 const base::FilePath::CharType DOMStorageArea::kDatabaseFileExtension[] = |
38 FILE_PATH_LITERAL(".localstorage"); | 72 FILE_PATH_LITERAL(".localstorage"); |
39 | 73 |
40 // static | 74 // static |
41 base::FilePath DOMStorageArea::DatabaseFileNameFromOrigin(const GURL& origin) { | 75 base::FilePath DOMStorageArea::DatabaseFileNameFromOrigin(const GURL& origin) { |
42 std::string filename = storage::GetIdentifierFromOrigin(origin); | 76 std::string filename = storage::GetIdentifierFromOrigin(origin); |
43 // There is no base::FilePath.AppendExtension() method, so start with just the | 77 // There is no base::FilePath.AppendExtension() method, so start with just the |
44 // extension as the filename, and then InsertBeforeExtension the desired | 78 // extension as the filename, and then InsertBeforeExtension the desired |
45 // name. | 79 // name. |
46 return base::FilePath().Append(kDatabaseFileExtension). | 80 return base::FilePath().Append(kDatabaseFileExtension). |
47 InsertBeforeExtensionASCII(filename); | 81 InsertBeforeExtensionASCII(filename); |
48 } | 82 } |
49 | 83 |
50 // static | 84 // static |
51 GURL DOMStorageArea::OriginFromDatabaseFileName(const base::FilePath& name) { | 85 GURL DOMStorageArea::OriginFromDatabaseFileName(const base::FilePath& name) { |
52 DCHECK(name.MatchesExtension(kDatabaseFileExtension)); | 86 DCHECK(name.MatchesExtension(kDatabaseFileExtension)); |
53 std::string origin_id = | 87 std::string origin_id = |
54 name.BaseName().RemoveExtension().MaybeAsASCII(); | 88 name.BaseName().RemoveExtension().MaybeAsASCII(); |
55 return storage::GetOriginFromIdentifier(origin_id); | 89 return storage::GetOriginFromIdentifier(origin_id); |
56 } | 90 } |
57 | 91 |
58 DOMStorageArea::DOMStorageArea( | 92 DOMStorageArea::DOMStorageArea(const GURL& origin, |
59 const GURL& origin, const base::FilePath& directory, | 93 const base::FilePath& directory, |
60 DOMStorageTaskRunner* task_runner) | 94 DOMStorageTaskRunner* task_runner) |
61 : namespace_id_(kLocalStorageNamespaceId), origin_(origin), | 95 : namespace_id_(kLocalStorageNamespaceId), |
| 96 origin_(origin), |
62 directory_(directory), | 97 directory_(directory), |
63 task_runner_(task_runner), | 98 task_runner_(task_runner), |
64 map_(new DOMStorageMap(kPerStorageAreaQuota + | 99 map_(new DOMStorageMap(kPerStorageAreaQuota + |
65 kPerStorageAreaOverQuotaAllowance)), | 100 kPerStorageAreaOverQuotaAllowance)), |
66 is_initial_import_done_(true), | 101 is_initial_import_done_(true), |
67 is_shutdown_(false), | 102 is_shutdown_(false), |
68 commit_batches_in_flight_(0) { | 103 commit_batches_in_flight_(0), |
| 104 start_time_(base::TimeTicks::Now()), |
| 105 data_rate_limiter_(kMaxBytesPerDay, base::TimeDelta::FromHours(24)), |
| 106 commit_rate_limiter_(kMaxCommitsPerHour, base::TimeDelta::FromHours(1)) { |
69 if (!directory.empty()) { | 107 if (!directory.empty()) { |
70 base::FilePath path = directory.Append(DatabaseFileNameFromOrigin(origin_)); | 108 base::FilePath path = directory.Append(DatabaseFileNameFromOrigin(origin_)); |
71 backing_.reset(new LocalStorageDatabaseAdapter(path)); | 109 backing_.reset(new LocalStorageDatabaseAdapter(path)); |
72 is_initial_import_done_ = false; | 110 is_initial_import_done_ = false; |
73 } | 111 } |
74 } | 112 } |
75 | 113 |
76 DOMStorageArea::DOMStorageArea( | 114 DOMStorageArea::DOMStorageArea(int64 namespace_id, |
77 int64 namespace_id, | 115 const std::string& persistent_namespace_id, |
78 const std::string& persistent_namespace_id, | 116 const GURL& origin, |
79 const GURL& origin, | 117 SessionStorageDatabase* session_storage_backing, |
80 SessionStorageDatabase* session_storage_backing, | 118 DOMStorageTaskRunner* task_runner) |
81 DOMStorageTaskRunner* task_runner) | |
82 : namespace_id_(namespace_id), | 119 : namespace_id_(namespace_id), |
83 persistent_namespace_id_(persistent_namespace_id), | 120 persistent_namespace_id_(persistent_namespace_id), |
84 origin_(origin), | 121 origin_(origin), |
85 task_runner_(task_runner), | 122 task_runner_(task_runner), |
86 map_(new DOMStorageMap(kPerStorageAreaQuota + | 123 map_(new DOMStorageMap(kPerStorageAreaQuota + |
87 kPerStorageAreaOverQuotaAllowance)), | 124 kPerStorageAreaOverQuotaAllowance)), |
88 session_storage_backing_(session_storage_backing), | 125 session_storage_backing_(session_storage_backing), |
89 is_initial_import_done_(true), | 126 is_initial_import_done_(true), |
90 is_shutdown_(false), | 127 is_shutdown_(false), |
91 commit_batches_in_flight_(0) { | 128 commit_batches_in_flight_(0), |
| 129 start_time_(base::TimeTicks::Now()), |
| 130 data_rate_limiter_(kMaxBytesPerDay, base::TimeDelta::FromHours(24)), |
| 131 commit_rate_limiter_(kMaxCommitsPerHour, base::TimeDelta::FromHours(1)) { |
92 DCHECK(namespace_id != kLocalStorageNamespaceId); | 132 DCHECK(namespace_id != kLocalStorageNamespaceId); |
93 if (session_storage_backing) { | 133 if (session_storage_backing) { |
94 backing_.reset(new SessionStorageDatabaseAdapter( | 134 backing_.reset(new SessionStorageDatabaseAdapter( |
95 session_storage_backing, persistent_namespace_id, origin)); | 135 session_storage_backing, persistent_namespace_id, origin)); |
96 is_initial_import_done_ = false; | 136 is_initial_import_done_ = false; |
97 } | 137 } |
98 } | 138 } |
99 | 139 |
100 DOMStorageArea::~DOMStorageArea() { | 140 DOMStorageArea::~DOMStorageArea() { |
101 } | 141 } |
(...skipping 28 matching lines...) Expand all Loading... |
130 | 170 |
131 bool DOMStorageArea::SetItem(const base::string16& key, | 171 bool DOMStorageArea::SetItem(const base::string16& key, |
132 const base::string16& value, | 172 const base::string16& value, |
133 base::NullableString16* old_value) { | 173 base::NullableString16* old_value) { |
134 if (is_shutdown_) | 174 if (is_shutdown_) |
135 return false; | 175 return false; |
136 InitialImportIfNeeded(); | 176 InitialImportIfNeeded(); |
137 if (!map_->HasOneRef()) | 177 if (!map_->HasOneRef()) |
138 map_ = map_->DeepCopy(); | 178 map_ = map_->DeepCopy(); |
139 bool success = map_->SetItem(key, value, old_value); | 179 bool success = map_->SetItem(key, value, old_value); |
140 if (success && backing_) { | 180 if (success && backing_ && |
| 181 (old_value->is_null() || old_value->string() != value)) { |
141 CommitBatch* commit_batch = CreateCommitBatchIfNeeded(); | 182 CommitBatch* commit_batch = CreateCommitBatchIfNeeded(); |
142 commit_batch->changed_values[key] = base::NullableString16(value, false); | 183 commit_batch->changed_values[key] = base::NullableString16(value, false); |
143 } | 184 } |
144 return success; | 185 return success; |
145 } | 186 } |
146 | 187 |
147 bool DOMStorageArea::RemoveItem(const base::string16& key, | 188 bool DOMStorageArea::RemoveItem(const base::string16& key, |
148 base::string16* old_value) { | 189 base::string16* old_value) { |
149 if (is_shutdown_) | 190 if (is_shutdown_) |
150 return false; | 191 return false; |
(...skipping 54 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
205 DCHECK_NE(kLocalStorageNamespaceId, destination_namespace_id); | 246 DCHECK_NE(kLocalStorageNamespaceId, destination_namespace_id); |
206 | 247 |
207 DOMStorageArea* copy = new DOMStorageArea( | 248 DOMStorageArea* copy = new DOMStorageArea( |
208 destination_namespace_id, destination_persistent_namespace_id, origin_, | 249 destination_namespace_id, destination_persistent_namespace_id, origin_, |
209 session_storage_backing_.get(), task_runner_.get()); | 250 session_storage_backing_.get(), task_runner_.get()); |
210 copy->map_ = map_; | 251 copy->map_ = map_; |
211 copy->is_shutdown_ = is_shutdown_; | 252 copy->is_shutdown_ = is_shutdown_; |
212 copy->is_initial_import_done_ = true; | 253 copy->is_initial_import_done_ = true; |
213 | 254 |
214 // All the uncommitted changes to this area need to happen before the actual | 255 // All the uncommitted changes to this area need to happen before the actual |
215 // shallow copy is made (scheduled by the upper layer). Another OnCommitTimer | 256 // shallow copy is made (scheduled by the upper layer sometime after return). |
216 // call might be in the event queue at this point, but it's handled gracefully | |
217 // when it fires. | |
218 if (commit_batch_) | 257 if (commit_batch_) |
219 OnCommitTimer(); | 258 ScheduleImmediateCommit(); |
220 return copy; | 259 return copy; |
221 } | 260 } |
222 | 261 |
223 bool DOMStorageArea::HasUncommittedChanges() const { | 262 bool DOMStorageArea::HasUncommittedChanges() const { |
224 DCHECK(!is_shutdown_); | |
225 return commit_batch_.get() || commit_batches_in_flight_; | 263 return commit_batch_.get() || commit_batches_in_flight_; |
226 } | 264 } |
227 | 265 |
| 266 void DOMStorageArea::ScheduleImmediateCommit() { |
| 267 DCHECK(HasUncommittedChanges()); |
| 268 PostCommitTask(); |
| 269 } |
| 270 |
228 void DOMStorageArea::DeleteOrigin() { | 271 void DOMStorageArea::DeleteOrigin() { |
229 DCHECK(!is_shutdown_); | 272 DCHECK(!is_shutdown_); |
230 // This function shouldn't be called for sessionStorage. | 273 // This function shouldn't be called for sessionStorage. |
231 DCHECK(!session_storage_backing_.get()); | 274 DCHECK(!session_storage_backing_.get()); |
232 if (HasUncommittedChanges()) { | 275 if (HasUncommittedChanges()) { |
233 // TODO(michaeln): This logically deletes the data immediately, | 276 // TODO(michaeln): This logically deletes the data immediately, |
234 // and in a matter of a second, deletes the rows from the backing | 277 // and in a matter of a second, deletes the rows from the backing |
235 // database file, but the file itself will linger until shutdown | 278 // database file, but the file itself will linger until shutdown |
236 // or purge time. Ideally, this should delete the file more | 279 // or purge time. Ideally, this should delete the file more |
237 // quickly. | 280 // quickly. |
(...skipping 82 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
320 DOMStorageArea::CommitBatch* DOMStorageArea::CreateCommitBatchIfNeeded() { | 363 DOMStorageArea::CommitBatch* DOMStorageArea::CreateCommitBatchIfNeeded() { |
321 DCHECK(!is_shutdown_); | 364 DCHECK(!is_shutdown_); |
322 if (!commit_batch_) { | 365 if (!commit_batch_) { |
323 commit_batch_.reset(new CommitBatch()); | 366 commit_batch_.reset(new CommitBatch()); |
324 | 367 |
325 // Start a timer to commit any changes that accrue in the batch, but only if | 368 // 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 | 369 // no commits are currently in flight. In that case the timer will be |
327 // started after the commits have happened. | 370 // started after the commits have happened. |
328 if (!commit_batches_in_flight_) { | 371 if (!commit_batches_in_flight_) { |
329 task_runner_->PostDelayedTask( | 372 task_runner_->PostDelayedTask( |
330 FROM_HERE, | 373 FROM_HERE, base::Bind(&DOMStorageArea::OnCommitTimer, this), |
331 base::Bind(&DOMStorageArea::OnCommitTimer, this), | 374 ComputeCommitDelay()); |
332 base::TimeDelta::FromSeconds(kCommitTimerSeconds)); | |
333 } | 375 } |
334 } | 376 } |
335 return commit_batch_.get(); | 377 return commit_batch_.get(); |
336 } | 378 } |
337 | 379 |
| 380 base::TimeDelta DOMStorageArea::ComputeCommitDelay() const { |
| 381 base::TimeDelta elapsed_time = base::TimeTicks::Now() - start_time_; |
| 382 base::TimeDelta delay = std::max( |
| 383 base::TimeDelta::FromSeconds(kCommitDefaultDelaySecs), |
| 384 std::max(commit_rate_limiter_.ComputeDelayNeeded(elapsed_time), |
| 385 data_rate_limiter_.ComputeDelayNeeded(elapsed_time))); |
| 386 UMA_HISTOGRAM_LONG_TIMES("LocalStorage.CommitDelay", delay); |
| 387 return delay; |
| 388 } |
| 389 |
338 void DOMStorageArea::OnCommitTimer() { | 390 void DOMStorageArea::OnCommitTimer() { |
339 if (is_shutdown_) | 391 if (is_shutdown_) |
340 return; | 392 return; |
341 | 393 |
| 394 // It's possible that there is nothing to commit if an immediate |
| 395 // commit occured after the timer was scheduled but before it fired. |
| 396 if (!commit_batch_) |
| 397 return; |
| 398 |
| 399 PostCommitTask(); |
| 400 } |
| 401 |
| 402 void DOMStorageArea::PostCommitTask() { |
| 403 if (is_shutdown_ || !commit_batch_) |
| 404 return; |
| 405 |
342 DCHECK(backing_.get()); | 406 DCHECK(backing_.get()); |
343 | 407 |
344 // It's possible that there is nothing to commit, since a shallow copy occured | 408 commit_rate_limiter_.add_samples(1); |
345 // before the timer fired. | 409 data_rate_limiter_.add_samples(commit_batch_->GetDataSize()); |
346 if (!commit_batch_) | |
347 return; | |
348 | 410 |
349 // This method executes on the primary sequence, we schedule | 411 // This method executes on the primary sequence, we schedule |
350 // a task for immediate execution on the commit sequence. | 412 // a task for immediate execution on the commit sequence. |
351 DCHECK(task_runner_->IsRunningOnPrimarySequence()); | 413 DCHECK(task_runner_->IsRunningOnPrimarySequence()); |
352 bool success = task_runner_->PostShutdownBlockingTask( | 414 bool success = task_runner_->PostShutdownBlockingTask( |
353 FROM_HERE, | 415 FROM_HERE, |
354 DOMStorageTaskRunner::COMMIT_SEQUENCE, | 416 DOMStorageTaskRunner::COMMIT_SEQUENCE, |
355 base::Bind(&DOMStorageArea::CommitChanges, this, | 417 base::Bind(&DOMStorageArea::CommitChanges, this, |
356 base::Owned(commit_batch_.release()))); | 418 base::Owned(commit_batch_.release()))); |
357 ++commit_batches_in_flight_; | 419 ++commit_batches_in_flight_; |
358 DCHECK(success); | 420 DCHECK(success); |
359 } | 421 } |
360 | 422 |
361 void DOMStorageArea::CommitChanges(const CommitBatch* commit_batch) { | 423 void DOMStorageArea::CommitChanges(const CommitBatch* commit_batch) { |
362 // This method executes on the commit sequence. | 424 // This method executes on the commit sequence. |
363 DCHECK(task_runner_->IsRunningOnCommitSequence()); | 425 DCHECK(task_runner_->IsRunningOnCommitSequence()); |
364 backing_->CommitChanges(commit_batch->clear_all_first, | 426 backing_->CommitChanges(commit_batch->clear_all_first, |
365 commit_batch->changed_values); | 427 commit_batch->changed_values); |
366 // TODO(michaeln): what if CommitChanges returns false (e.g., we're trying to | 428 // TODO(michaeln): what if CommitChanges returns false (e.g., we're trying to |
367 // commit to a DB which is in an inconsistent state?) | 429 // commit to a DB which is in an inconsistent state?) |
368 task_runner_->PostTask( | 430 task_runner_->PostTask( |
369 FROM_HERE, | 431 FROM_HERE, |
370 base::Bind(&DOMStorageArea::OnCommitComplete, this)); | 432 base::Bind(&DOMStorageArea::OnCommitComplete, this)); |
371 } | 433 } |
372 | 434 |
373 void DOMStorageArea::OnCommitComplete() { | 435 void DOMStorageArea::OnCommitComplete() { |
374 // We're back on the primary sequence in this method. | 436 // We're back on the primary sequence in this method. |
375 DCHECK(task_runner_->IsRunningOnPrimarySequence()); | 437 DCHECK(task_runner_->IsRunningOnPrimarySequence()); |
376 --commit_batches_in_flight_; | 438 --commit_batches_in_flight_; |
377 if (is_shutdown_) | 439 if (is_shutdown_) |
378 return; | 440 return; |
379 if (commit_batch_.get() && !commit_batches_in_flight_) { | 441 if (commit_batch_.get() && !commit_batches_in_flight_) { |
380 // More changes have accrued, restart the timer. | 442 // More changes have accrued, restart the timer. |
381 task_runner_->PostDelayedTask( | 443 task_runner_->PostDelayedTask( |
382 FROM_HERE, | 444 FROM_HERE, base::Bind(&DOMStorageArea::OnCommitTimer, this), |
383 base::Bind(&DOMStorageArea::OnCommitTimer, this), | 445 ComputeCommitDelay()); |
384 base::TimeDelta::FromSeconds(kCommitTimerSeconds)); | |
385 } | 446 } |
386 } | 447 } |
387 | 448 |
388 void DOMStorageArea::ShutdownInCommitSequence() { | 449 void DOMStorageArea::ShutdownInCommitSequence() { |
389 // This method executes on the commit sequence. | 450 // This method executes on the commit sequence. |
390 DCHECK(task_runner_->IsRunningOnCommitSequence()); | 451 DCHECK(task_runner_->IsRunningOnCommitSequence()); |
391 DCHECK(backing_.get()); | 452 DCHECK(backing_.get()); |
392 if (commit_batch_) { | 453 if (commit_batch_) { |
393 // Commit any changes that accrued prior to the timer firing. | 454 // Commit any changes that accrued prior to the timer firing. |
394 bool success = backing_->CommitChanges( | 455 bool success = backing_->CommitChanges( |
395 commit_batch_->clear_all_first, | 456 commit_batch_->clear_all_first, |
396 commit_batch_->changed_values); | 457 commit_batch_->changed_values); |
397 DCHECK(success); | 458 DCHECK(success); |
398 } | 459 } |
399 commit_batch_.reset(); | 460 commit_batch_.reset(); |
400 backing_.reset(); | 461 backing_.reset(); |
401 session_storage_backing_ = NULL; | 462 session_storage_backing_ = NULL; |
402 } | 463 } |
403 | 464 |
404 } // namespace content | 465 } // namespace content |
OLD | NEW |