Index: webkit/dom_storage/dom_storage_area.cc |
=================================================================== |
--- webkit/dom_storage/dom_storage_area.cc (revision 127736) |
+++ webkit/dom_storage/dom_storage_area.cc (working copy) |
@@ -16,6 +16,14 @@ |
namespace dom_storage { |
+static const int kCommitTimerSeconds = 1; |
+ |
+DomStorageArea::CommitBatch::CommitBatch() |
+ : clear_all_first(false) { |
+} |
+DomStorageArea::CommitBatch::~CommitBatch() {} |
+ |
+ |
// static |
const FilePath::CharType DomStorageArea::kDatabaseFileExtension[] = |
FILE_PATH_LITERAL(".localstorage"); |
@@ -37,51 +45,35 @@ |
directory_(directory), |
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); |
} |
@@ -89,31 +81,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 = CreateCommitBatchIfNeeded(); |
+ 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 = CreateCommitBatchIfNeeded(); |
+ commit_batch->changed_values[key] = NullableString16(true); |
} |
return success; |
} |
bool DomStorageArea::Clear() { |
+ if (is_shutdown_) |
+ return false; |
InitialImportIfNeeded(); |
if (map_->Length() == 0) |
return false; |
@@ -121,9 +118,9 @@ |
map_ = new DomStorageMap(kPerAreaQuota); |
if (backing_.get()) { |
- changed_values_.clear(); |
- clear_all_next_commit_ = true; |
- ScheduleCommitChanges(); |
+ CommitBatch* commit_batch = CreateCommitBatchIfNeeded(); |
+ commit_batch->clear_all_first = true; |
+ commit_batch->changed_values.clear(); |
} |
return true; |
@@ -132,17 +129,31 @@ |
DomStorageArea* DomStorageArea::ShallowCopy(int64 destination_namespace_id) { |
DCHECK_NE(kLocalStorageNamespaceId, namespace_id_); |
DCHECK_NE(kLocalStorageNamespaceId, destination_namespace_id); |
- // SessionNamespaces aren't backed by files on disk. |
- DCHECK(!backing_.get()); |
+ DCHECK(!backing_.get()); // SessionNamespaces aren't stored on disk. |
DomStorageArea* copy = new DomStorageArea(destination_namespace_id, origin_, |
FilePath(), task_runner_); |
copy->map_ = map_; |
+ copy->is_shutdown_ = is_shutdown_; |
return copy; |
} |
+void DomStorageArea::Shutdown() { |
+ DCHECK(!is_shutdown_); |
+ is_shutdown_ = true; |
+ map_ = NULL; |
+ if (!backing_.get()) |
+ return; |
+ |
+ bool success = task_runner_->PostShutdownBlockingTask( |
+ FROM_HERE, |
+ DomStorageTaskRunner::COMMIT_SEQUENCE, |
+ base::Bind(&DomStorageArea::ShutdownInCommitSequence, this)); |
+ DCHECK(success); |
+} |
+ |
void DomStorageArea::InitialImportIfNeeded() { |
- if (initial_import_done_) |
+ if (is_initial_import_done_) |
return; |
DCHECK_EQ(kLocalStorageNamespaceId, namespace_id_); |
@@ -151,31 +162,85 @@ |
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::CreateCommitBatchIfNeeded() { |
+ DCHECK(!is_shutdown_); |
+ 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(kCommitTimerSeconds)); |
+ } |
+ } |
+ return commit_batch_.get(); |
+} |
+ |
+void DomStorageArea::OnCommitTimer() { |
DCHECK_EQ(kLocalStorageNamespaceId, namespace_id_); |
+ if (is_shutdown_) |
+ return; |
+ |
DCHECK(backing_.get()); |
- DCHECK(clear_all_next_commit_ || !changed_values_.empty()); |
- DCHECK(task_runner_.get()); |
+ DCHECK(commit_batch_.get()); |
+ DCHECK(!in_flight_commit_batch_.get()); |
- if (commit_in_flight_) |
- return; |
+ // This method executes on the primary sequence, we schedule |
+ // a task for immediate execution on the commit sequence. |
+ in_flight_commit_batch_ = commit_batch_.Pass(); |
+ bool success = task_runner_->PostShutdownBlockingTask( |
+ FROM_HERE, |
+ DomStorageTaskRunner::COMMIT_SEQUENCE, |
+ base::Bind(&DomStorageArea::CommitChanges, this)); |
+ DCHECK(success); |
+} |
- commit_in_flight_ = task_runner_->PostDelayedTask( |
- FROM_HERE, base::Bind(&DomStorageArea::CommitChanges, this), |
- base::TimeDelta::FromSeconds(1)); |
- DCHECK(commit_in_flight_); |
+void DomStorageArea::CommitChanges() { |
+ // This method executes on the commit sequence. |
+ 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::CommitChanges() { |
+void DomStorageArea::OnCommitComplete() { |
+ // We're back on the primary sequence in this method. |
+ if (is_shutdown_) |
+ return; |
+ in_flight_commit_batch_.reset(); |
+ if (commit_batch_.get()) { |
+ // More changes have accrued, restart the timer. |
+ task_runner_->PostDelayedTask( |
+ FROM_HERE, |
+ base::Bind(&DomStorageArea::OnCommitTimer, this), |
+ base::TimeDelta::FromSeconds(kCommitTimerSeconds)); |
+ } |
+} |
+ |
+void DomStorageArea::ShutdownInCommitSequence() { |
+ // This method executes on the commit sequence. |
DCHECK(backing_.get()); |
- if (backing_->CommitChanges(clear_all_next_commit_, changed_values_)) { |
- clear_all_next_commit_ = false; |
- changed_values_.clear(); |
+ if (commit_batch_.get()) { |
+ // Commit any changes that accrued prior to the timer firing. |
+ bool success = backing_->CommitChanges( |
+ commit_batch_->clear_all_first, |
+ commit_batch_->changed_values); |
+ DCHECK(success); |
} |
- commit_in_flight_ = false; |
+ commit_batch_.reset(); |
+ in_flight_commit_batch_.reset(); |
+ backing_.reset(); |
} |
} // namespace dom_storage |