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

Side by Side Diff: webkit/dom_storage/dom_storage_area.cc

Issue 9718029: DomStorage commit task sequencing. (Closed) Base URL: svn://chrome-svn/chrome/trunk/src/
Patch Set: Created 8 years, 9 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 | Annotate | Revision Log
OLDNEW
1 // Copyright (c) 2012 The Chromium Authors. All rights reserved. 1 // Copyright (c) 2012 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 "webkit/dom_storage/dom_storage_area.h" 5 #include "webkit/dom_storage/dom_storage_area.h"
6 6
7 #include "base/bind.h" 7 #include "base/bind.h"
8 #include "base/logging.h" 8 #include "base/logging.h"
9 #include "base/time.h" 9 #include "base/time.h"
10 #include "base/tracked_objects.h" 10 #include "base/tracked_objects.h"
11 #include "webkit/dom_storage/dom_storage_map.h" 11 #include "webkit/dom_storage/dom_storage_map.h"
12 #include "webkit/dom_storage/dom_storage_namespace.h" 12 #include "webkit/dom_storage/dom_storage_namespace.h"
13 #include "webkit/dom_storage/dom_storage_task_runner.h" 13 #include "webkit/dom_storage/dom_storage_task_runner.h"
14 #include "webkit/dom_storage/dom_storage_types.h" 14 #include "webkit/dom_storage/dom_storage_types.h"
15 #include "webkit/fileapi/file_system_util.h" 15 #include "webkit/fileapi/file_system_util.h"
16 16
17 namespace dom_storage { 17 namespace dom_storage {
18 18
19 struct DomStorageArea::CommitBatch {
20 bool clear_all_first;
21 ValuesMap changed_values;
22 CommitBatch() : clear_all_first(false) {}
23 };
24
25 // static
26 const FilePath::CharType DomStorageArea::kDatabaseFileExtension[] =
27 FILE_PATH_LITERAL(".localstorage");
28
29 // static
30 FilePath DomStorageArea::DatabaseFileNameFromOrigin(const GURL& origin) {
31 std::string filename = fileapi::GetOriginIdentifierFromURL(origin);
32 return FilePath().Append(kDatabaseFileExtension).
33 InsertBeforeExtensionASCII(filename);
34 }
35
19 DomStorageArea::DomStorageArea( 36 DomStorageArea::DomStorageArea(
20 int64 namespace_id, const GURL& origin, 37 int64 namespace_id, const GURL& origin,
21 const FilePath& directory, DomStorageTaskRunner* task_runner) 38 const FilePath& directory, DomStorageTaskRunner* task_runner)
22 : namespace_id_(namespace_id), origin_(origin), 39 : namespace_id_(namespace_id), origin_(origin),
23 directory_(directory), 40 directory_(directory),
24 task_runner_(task_runner), 41 task_runner_(task_runner),
25 map_(new DomStorageMap(kPerAreaQuota)), 42 map_(new DomStorageMap(kPerAreaQuota)),
26 backing_(NULL), 43 backing_(NULL),
27 initial_import_done_(false), 44 is_initial_import_done_(true),
28 clear_all_next_commit_(false), 45 is_shutdown_(false) {
29 commit_in_flight_(false) {
30
31 if (namespace_id == kLocalStorageNamespaceId && !directory.empty()) { 46 if (namespace_id == kLocalStorageNamespaceId && !directory.empty()) {
32 FilePath path = directory.Append(DatabaseFileNameFromOrigin(origin_)); 47 FilePath path = directory.Append(DatabaseFileNameFromOrigin(origin_));
33 backing_.reset(new DomStorageDatabase(path)); 48 backing_.reset(new DomStorageDatabase(path));
34 } else { 49 is_initial_import_done_ = false;
35 // Not a local storage area or no directory specified for backing
36 // database, (i.e. it's an incognito profile).
37 initial_import_done_ = true;
38 } 50 }
39 } 51 }
40 52
41 DomStorageArea::~DomStorageArea() { 53 DomStorageArea::~DomStorageArea() {
42 if (clear_all_next_commit_ || !changed_values_.empty()) {
43 // Still some data left that was not committed to disk, try now.
44 // We do this regardless of whether we think a commit is in flight
45 // as there is no guarantee that that commit will actually get
46 // processed. For example the task_runner_'s message loop could
47 // unexpectedly quit before the delayed task is fired and leave the
48 // commit_in_flight_ flag set. But there's no way for us to determine
49 // that has happened so force a commit now.
50
51 CommitChanges();
52
53 // TODO(benm): It's possible that the commit failed, and in
54 // that case we're going to lose data. Integrate with UMA
55 // to gather stats about how often this actually happens,
56 // so that we can figure out a contingency plan.
57 }
58 } 54 }
59 55
60 unsigned DomStorageArea::Length() { 56 unsigned DomStorageArea::Length() {
57 if (is_shutdown_)
58 return 0;
61 InitialImportIfNeeded(); 59 InitialImportIfNeeded();
62 return map_->Length(); 60 return map_->Length();
63 } 61 }
64 62
65 NullableString16 DomStorageArea::Key(unsigned index) { 63 NullableString16 DomStorageArea::Key(unsigned index) {
64 if (is_shutdown_)
65 return NullableString16(true);
66 InitialImportIfNeeded(); 66 InitialImportIfNeeded();
67 return map_->Key(index); 67 return map_->Key(index);
68 } 68 }
69 69
70 NullableString16 DomStorageArea::GetItem(const string16& key) { 70 NullableString16 DomStorageArea::GetItem(const string16& key) {
71 if (is_shutdown_)
72 return NullableString16(true);
71 InitialImportIfNeeded(); 73 InitialImportIfNeeded();
72 return map_->GetItem(key); 74 return map_->GetItem(key);
73 } 75 }
74 76
75 bool DomStorageArea::SetItem(const string16& key, 77 bool DomStorageArea::SetItem(const string16& key,
76 const string16& value, 78 const string16& value,
77 NullableString16* old_value) { 79 NullableString16* old_value) {
80 if (is_shutdown_)
81 return false;
78 InitialImportIfNeeded(); 82 InitialImportIfNeeded();
79
80 if (!map_->HasOneRef()) 83 if (!map_->HasOneRef())
81 map_ = map_->DeepCopy(); 84 map_ = map_->DeepCopy();
82 bool success = map_->SetItem(key, value, old_value); 85 bool success = map_->SetItem(key, value, old_value);
83 if (success && backing_.get()) { 86 if (success && backing_.get()) {
84 changed_values_[key] = NullableString16(value, false); 87 CommitBatch* commit_batch = GetCommitBatch();
85 ScheduleCommitChanges(); 88 commit_batch->changed_values[key] = NullableString16(value, false);
86 } 89 }
87 return success; 90 return success;
88 } 91 }
89 92
90 bool DomStorageArea::RemoveItem(const string16& key, string16* old_value) { 93 bool DomStorageArea::RemoveItem(const string16& key, string16* old_value) {
94 if (is_shutdown_)
95 return false;
91 InitialImportIfNeeded(); 96 InitialImportIfNeeded();
92 if (!map_->HasOneRef()) 97 if (!map_->HasOneRef())
93 map_ = map_->DeepCopy(); 98 map_ = map_->DeepCopy();
94 bool success = map_->RemoveItem(key, old_value); 99 bool success = map_->RemoveItem(key, old_value);
95 if (success && backing_.get()) { 100 if (success && backing_.get()) {
96 changed_values_[key] = NullableString16(true); 101 CommitBatch* commit_batch = GetCommitBatch();
97 ScheduleCommitChanges(); 102 commit_batch->changed_values[key] = NullableString16(true);
98 } 103 }
99 return success; 104 return success;
100 } 105 }
101 106
102 bool DomStorageArea::Clear() { 107 bool DomStorageArea::Clear() {
108 if (is_shutdown_)
109 return false;
103 InitialImportIfNeeded(); 110 InitialImportIfNeeded();
104 if (map_->Length() == 0) 111 if (map_->Length() == 0)
105 return false; 112 return false;
106 113
107 map_ = new DomStorageMap(kPerAreaQuota); 114 map_ = new DomStorageMap(kPerAreaQuota);
108 115
109 if (backing_.get()) { 116 if (backing_.get()) {
110 changed_values_.clear(); 117 CommitBatch* commit_batch = GetCommitBatch();
111 clear_all_next_commit_ = true; 118 commit_batch->clear_all_first = true;
112 ScheduleCommitChanges(); 119 commit_batch->changed_values.clear();
113 } 120 }
114 121
115 return true; 122 return true;
116 } 123 }
117 124
118 DomStorageArea* DomStorageArea::ShallowCopy(int64 destination_namespace_id) { 125 DomStorageArea* DomStorageArea::ShallowCopy(int64 destination_namespace_id) {
126 DCHECK(!is_shutdown_);
119 DCHECK_NE(kLocalStorageNamespaceId, namespace_id_); 127 DCHECK_NE(kLocalStorageNamespaceId, namespace_id_);
120 DCHECK_NE(kLocalStorageNamespaceId, destination_namespace_id); 128 DCHECK_NE(kLocalStorageNamespaceId, destination_namespace_id);
121 // SessionNamespaces aren't backed by files on disk. 129 // SessionNamespaces aren't backed by files on disk.
122 DCHECK(!backing_.get()); 130 DCHECK(!backing_.get());
123 131
124 DomStorageArea* copy = new DomStorageArea(destination_namespace_id, origin_, 132 DomStorageArea* copy = new DomStorageArea(destination_namespace_id, origin_,
125 FilePath(), task_runner_); 133 FilePath(), task_runner_);
126 copy->map_ = map_; 134 copy->map_ = map_;
127 return copy; 135 return copy;
128 } 136 }
129 137
138 void DomStorageArea::Shutdown() {
139 DCHECK(!is_shutdown_);
140 is_shutdown_ = true;
141
142 // If there are changes for which a commit is not yet in flight,
143 // we post a task now to flush them out.
144 if (!commit_batch_.get())
145 return;
146 bool success = task_runner_->PostShutdownBlockingCommitTask(
147 FROM_HERE, base::Bind(&DomStorageArea::CommitShutdownChanges, this));
148 DCHECK(success);
149 }
150
130 void DomStorageArea::InitialImportIfNeeded() { 151 void DomStorageArea::InitialImportIfNeeded() {
131 if (initial_import_done_) 152 if (is_initial_import_done_)
132 return; 153 return;
133 154
134 DCHECK_EQ(kLocalStorageNamespaceId, namespace_id_); 155 DCHECK_EQ(kLocalStorageNamespaceId, namespace_id_);
135 DCHECK(backing_.get()); 156 DCHECK(backing_.get());
136 157
137 ValuesMap initial_values; 158 ValuesMap initial_values;
138 backing_->ReadAllValues(&initial_values); 159 backing_->ReadAllValues(&initial_values);
139 map_->SwapValues(&initial_values); 160 map_->SwapValues(&initial_values);
140 initial_import_done_ = true; 161 is_initial_import_done_ = true;
141 } 162 }
142 163
143 void DomStorageArea::ScheduleCommitChanges() { 164 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
165 DCHECK(is_shutdown_);
benm (inactive) 2012/03/19 14:22:53 !is_shutdown?
michaeln 2012/03/19 16:04:42 Done.
166 if (!commit_batch_.get()) {
167 commit_batch_.reset(new CommitBatch());
168
169 // Start a timer to commit any changes that accrue in the batch,
170 // but only if a commit is not currently in flight. In that case
171 // the timer will be started after the current commit has happened.
172 if (!in_flight_commit_batch_.get()) {
173 task_runner_->PostDelayedTask(
174 FROM_HERE,
175 base::Bind(&DomStorageArea::OnCommitTimer, this),
176 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.
177 }
178 }
179 return commit_batch_.get();
180 }
181
182 void DomStorageArea::OnCommitTimer() {
144 DCHECK_EQ(kLocalStorageNamespaceId, namespace_id_); 183 DCHECK_EQ(kLocalStorageNamespaceId, namespace_id_);
145 DCHECK(backing_.get()); 184 DCHECK(backing_.get());
146 DCHECK(clear_all_next_commit_ || !changed_values_.empty()); 185 DCHECK(commit_batch_.get());
147 DCHECK(task_runner_.get()); 186 DCHECK(!in_flight_commit_batch_.get());
148 187 if (is_shutdown_)
149 if (commit_in_flight_)
150 return; 188 return;
151 189
152 commit_in_flight_ = task_runner_->PostDelayedTask( 190 // This method executes on the 'read' sequence, we schedule
153 FROM_HERE, base::Bind(&DomStorageArea::CommitChanges, this), 191 // 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.
154 base::TimeDelta::FromSeconds(1)); 192 in_flight_commit_batch_ = commit_batch_.Pass();
155 DCHECK(commit_in_flight_); 193 bool success = task_runner_->PostShutdownBlockingCommitTask(
194 FROM_HERE, base::Bind(&DomStorageArea::CommitChanges, this));
195 DCHECK(success);
156 } 196 }
157 197
158 void DomStorageArea::CommitChanges() { 198 void DomStorageArea::CommitChanges() {
159 DCHECK(backing_.get()); 199 // 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
160 if (backing_->CommitChanges(clear_all_next_commit_, changed_values_)) { 200 DCHECK(in_flight_commit_batch_.get());
161 clear_all_next_commit_ = false; 201 bool success = backing_->CommitChanges(
162 changed_values_.clear(); 202 in_flight_commit_batch_->clear_all_first,
163 } 203 in_flight_commit_batch_->changed_values);
164 commit_in_flight_ = false; 204 DCHECK(success); // TODO(michaeln): what if it fails?
205 task_runner_->PostTask(
206 FROM_HERE,
207 base::Bind(&DomStorageArea::OnCommitComplete, this));
165 } 208 }
166 209
167 // static 210 void DomStorageArea::OnCommitComplete() {
168 FilePath DomStorageArea::DatabaseFileNameFromOrigin(const GURL& origin) { 211 in_flight_commit_batch_.reset();
169 std::string filename = fileapi::GetOriginIdentifierFromURL(origin) 212 if (commit_batch_.get() && !is_shutdown_) {
170 + ".localstorage"; 213 // More changes have accrued, restart the timer.
171 return FilePath().AppendASCII(filename); 214 task_runner_->PostDelayedTask(
215 FROM_HERE,
216 base::Bind(&DomStorageArea::OnCommitTimer, this),
217 base::TimeDelta::FromSeconds(1));
218 }
219 }
220
221 void DomStorageArea::CommitShutdownChanges() {
222 // This method executes on the 'write' sequence.
223 DCHECK(commit_batch_.get());
224 bool success = backing_->CommitChanges(
225 commit_batch_->clear_all_first,
226 commit_batch_->changed_values);
227 DCHECK(success);
172 } 228 }
173 229
174 } // namespace dom_storage 230 } // namespace dom_storage
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698