Chromium Code Reviews| Index: webkit/dom_storage/dom_storage_area.cc |
| =================================================================== |
| --- webkit/dom_storage/dom_storage_area.cc (revision 127221) |
| +++ webkit/dom_storage/dom_storage_area.cc (working copy) |
| @@ -16,6 +16,23 @@ |
| namespace dom_storage { |
| +struct DomStorageArea::CommitBatch { |
| + bool clear_all_first; |
| + ValuesMap changed_values; |
| + CommitBatch() : clear_all_first(false) {} |
| +}; |
| + |
| +// static |
| +const FilePath::CharType DomStorageArea::kDatabaseFileExtension[] = |
| + FILE_PATH_LITERAL(".localstorage"); |
| + |
| +// static |
| +FilePath DomStorageArea::DatabaseFileNameFromOrigin(const GURL& origin) { |
| + std::string filename = fileapi::GetOriginIdentifierFromURL(origin); |
| + return FilePath().Append(kDatabaseFileExtension). |
| + InsertBeforeExtensionASCII(filename); |
| +} |
| + |
| DomStorageArea::DomStorageArea( |
| int64 namespace_id, const GURL& origin, |
| const FilePath& directory, DomStorageTaskRunner* task_runner) |
| @@ -24,50 +41,35 @@ |
| task_runner_(task_runner), |
| map_(new DomStorageMap(kPerAreaQuota)), |
| backing_(NULL), |
| - initial_import_done_(false), |
| - clear_all_next_commit_(false), |
| - commit_in_flight_(false) { |
| - |
| + is_initial_import_done_(true), |
| + is_shutdown_(false) { |
| if (namespace_id == kLocalStorageNamespaceId && !directory.empty()) { |
| FilePath path = directory.Append(DatabaseFileNameFromOrigin(origin_)); |
| backing_.reset(new DomStorageDatabase(path)); |
| - } else { |
| - // Not a local storage area or no directory specified for backing |
| - // database, (i.e. it's an incognito profile). |
| - initial_import_done_ = true; |
| + is_initial_import_done_ = false; |
| } |
| } |
| DomStorageArea::~DomStorageArea() { |
| - if (clear_all_next_commit_ || !changed_values_.empty()) { |
| - // Still some data left that was not committed to disk, try now. |
| - // We do this regardless of whether we think a commit is in flight |
| - // as there is no guarantee that that commit will actually get |
| - // processed. For example the task_runner_'s message loop could |
| - // unexpectedly quit before the delayed task is fired and leave the |
| - // commit_in_flight_ flag set. But there's no way for us to determine |
| - // that has happened so force a commit now. |
| - |
| - CommitChanges(); |
| - |
| - // TODO(benm): It's possible that the commit failed, and in |
| - // that case we're going to lose data. Integrate with UMA |
| - // to gather stats about how often this actually happens, |
| - // so that we can figure out a contingency plan. |
| - } |
| } |
| unsigned DomStorageArea::Length() { |
| + if (is_shutdown_) |
| + return 0; |
| InitialImportIfNeeded(); |
| return map_->Length(); |
| } |
| NullableString16 DomStorageArea::Key(unsigned index) { |
| + if (is_shutdown_) |
| + return NullableString16(true); |
| InitialImportIfNeeded(); |
| return map_->Key(index); |
| } |
| NullableString16 DomStorageArea::GetItem(const string16& key) { |
| + if (is_shutdown_) |
| + return NullableString16(true); |
| InitialImportIfNeeded(); |
| return map_->GetItem(key); |
| } |
| @@ -75,31 +77,36 @@ |
| bool DomStorageArea::SetItem(const string16& key, |
| const string16& value, |
| NullableString16* old_value) { |
| + if (is_shutdown_) |
| + return false; |
| InitialImportIfNeeded(); |
| - |
| if (!map_->HasOneRef()) |
| map_ = map_->DeepCopy(); |
| bool success = map_->SetItem(key, value, old_value); |
| if (success && backing_.get()) { |
| - changed_values_[key] = NullableString16(value, false); |
| - ScheduleCommitChanges(); |
| + CommitBatch* commit_batch = GetCommitBatch(); |
| + commit_batch->changed_values[key] = NullableString16(value, false); |
| } |
| return success; |
| } |
| bool DomStorageArea::RemoveItem(const string16& key, string16* old_value) { |
| + if (is_shutdown_) |
| + return false; |
| InitialImportIfNeeded(); |
| if (!map_->HasOneRef()) |
| map_ = map_->DeepCopy(); |
| bool success = map_->RemoveItem(key, old_value); |
| if (success && backing_.get()) { |
| - changed_values_[key] = NullableString16(true); |
| - ScheduleCommitChanges(); |
| + CommitBatch* commit_batch = GetCommitBatch(); |
| + commit_batch->changed_values[key] = NullableString16(true); |
| } |
| return success; |
| } |
| bool DomStorageArea::Clear() { |
| + if (is_shutdown_) |
| + return false; |
| InitialImportIfNeeded(); |
| if (map_->Length() == 0) |
| return false; |
| @@ -107,15 +114,16 @@ |
| map_ = new DomStorageMap(kPerAreaQuota); |
| if (backing_.get()) { |
| - changed_values_.clear(); |
| - clear_all_next_commit_ = true; |
| - ScheduleCommitChanges(); |
| + CommitBatch* commit_batch = GetCommitBatch(); |
| + commit_batch->clear_all_first = true; |
| + commit_batch->changed_values.clear(); |
| } |
| return true; |
| } |
| DomStorageArea* DomStorageArea::ShallowCopy(int64 destination_namespace_id) { |
| + DCHECK(!is_shutdown_); |
| DCHECK_NE(kLocalStorageNamespaceId, namespace_id_); |
| DCHECK_NE(kLocalStorageNamespaceId, destination_namespace_id); |
| // SessionNamespaces aren't backed by files on disk. |
| @@ -127,8 +135,21 @@ |
| return copy; |
| } |
| +void DomStorageArea::Shutdown() { |
| + DCHECK(!is_shutdown_); |
| + is_shutdown_ = true; |
| + |
| + // If there are changes for which a commit is not yet in flight, |
| + // we post a task now to flush them out. |
| + if (!commit_batch_.get()) |
| + return; |
| + bool success = task_runner_->PostShutdownBlockingCommitTask( |
| + FROM_HERE, base::Bind(&DomStorageArea::CommitShutdownChanges, this)); |
| + DCHECK(success); |
| +} |
| + |
| void DomStorageArea::InitialImportIfNeeded() { |
| - if (initial_import_done_) |
| + if (is_initial_import_done_) |
| return; |
| DCHECK_EQ(kLocalStorageNamespaceId, namespace_id_); |
| @@ -137,38 +158,73 @@ |
| ValuesMap initial_values; |
| backing_->ReadAllValues(&initial_values); |
| map_->SwapValues(&initial_values); |
| - initial_import_done_ = true; |
| + is_initial_import_done_ = true; |
| } |
| -void DomStorageArea::ScheduleCommitChanges() { |
| +DomStorageArea::CommitBatch* DomStorageArea::GetCommitBatch() { |
|
benm (inactive)
2012/03/19 14:22:53
As this method is more than a getter (it may kick
michaeln
2012/03/19 16:26:52
maybe StartCommitBatchIfNeeded()?
benm (inactive)
2012/03/19 17:08:45
sg, or maybe Schedule instead of Start, you decide
|
| + DCHECK(is_shutdown_); |
|
benm (inactive)
2012/03/19 14:22:53
!is_shutdown?
michaeln
2012/03/19 16:04:42
Done.
|
| + if (!commit_batch_.get()) { |
| + commit_batch_.reset(new CommitBatch()); |
| + |
| + // Start a timer to commit any changes that accrue in the batch, |
| + // but only if a commit is not currently in flight. In that case |
| + // the timer will be started after the current commit has happened. |
| + if (!in_flight_commit_batch_.get()) { |
| + task_runner_->PostDelayedTask( |
| + FROM_HERE, |
| + base::Bind(&DomStorageArea::OnCommitTimer, this), |
| + base::TimeDelta::FromSeconds(1)); |
|
benm (inactive)
2012/03/19 14:22:53
might be worth putting a constant in dom_storage_t
michaeln
2012/03/19 16:04:42
Done.
|
| + } |
| + } |
| + return commit_batch_.get(); |
| +} |
| + |
| +void DomStorageArea::OnCommitTimer() { |
| DCHECK_EQ(kLocalStorageNamespaceId, namespace_id_); |
| DCHECK(backing_.get()); |
| - DCHECK(clear_all_next_commit_ || !changed_values_.empty()); |
| - DCHECK(task_runner_.get()); |
| - |
| - if (commit_in_flight_) |
| + DCHECK(commit_batch_.get()); |
| + DCHECK(!in_flight_commit_batch_.get()); |
| + if (is_shutdown_) |
| return; |
| - commit_in_flight_ = task_runner_->PostDelayedTask( |
| - FROM_HERE, base::Bind(&DomStorageArea::CommitChanges, this), |
| - base::TimeDelta::FromSeconds(1)); |
| - DCHECK(commit_in_flight_); |
| + // This method executes on the 'read' sequence, we schedule |
| + // a task for immediate exeuction on the 'write' sequence. |
|
benm (inactive)
2012/03/19 14:22:53
execution
michaeln
2012/03/19 16:04:42
Done.
|
| + in_flight_commit_batch_ = commit_batch_.Pass(); |
| + bool success = task_runner_->PostShutdownBlockingCommitTask( |
| + FROM_HERE, base::Bind(&DomStorageArea::CommitChanges, this)); |
| + DCHECK(success); |
| } |
| void DomStorageArea::CommitChanges() { |
| - DCHECK(backing_.get()); |
| - if (backing_->CommitChanges(clear_all_next_commit_, changed_values_)) { |
| - clear_all_next_commit_ = false; |
| - changed_values_.clear(); |
| + // This method executes on the 'write' sequence. |
|
benm (inactive)
2012/03/19 14:22:53
Can we DCHECK that this is the case?
michaeln
2012/03/19 16:04:42
I wish we could, but right now there is no way to
|
| + DCHECK(in_flight_commit_batch_.get()); |
| + bool success = backing_->CommitChanges( |
| + in_flight_commit_batch_->clear_all_first, |
| + in_flight_commit_batch_->changed_values); |
| + DCHECK(success); // TODO(michaeln): what if it fails? |
| + task_runner_->PostTask( |
| + FROM_HERE, |
| + base::Bind(&DomStorageArea::OnCommitComplete, this)); |
| +} |
| + |
| +void DomStorageArea::OnCommitComplete() { |
| + in_flight_commit_batch_.reset(); |
| + if (commit_batch_.get() && !is_shutdown_) { |
| + // More changes have accrued, restart the timer. |
| + task_runner_->PostDelayedTask( |
| + FROM_HERE, |
| + base::Bind(&DomStorageArea::OnCommitTimer, this), |
| + base::TimeDelta::FromSeconds(1)); |
| } |
| - commit_in_flight_ = false; |
| } |
| -// static |
| -FilePath DomStorageArea::DatabaseFileNameFromOrigin(const GURL& origin) { |
| - std::string filename = fileapi::GetOriginIdentifierFromURL(origin) |
| - + ".localstorage"; |
| - return FilePath().AppendASCII(filename); |
| +void DomStorageArea::CommitShutdownChanges() { |
| + // This method executes on the 'write' sequence. |
| + DCHECK(commit_batch_.get()); |
| + bool success = backing_->CommitChanges( |
| + commit_batch_->clear_all_first, |
| + commit_batch_->changed_values); |
| + DCHECK(success); |
| } |
| } // namespace dom_storage |