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

Side by Side Diff: base/prefs/leveldb_pref_store.cc

Issue 169323003: Implementation of leveldb-backed PrefStore (Closed) Base URL: svn://svn.chromium.org/chrome/trunk/src
Patch Set: fix pref service factory error Created 6 years, 8 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
« no previous file with comments | « base/prefs/leveldb_pref_store.h ('k') | base/prefs/leveldb_pref_store_unittest.cc » ('j') | no next file with comments »
Toggle Intra-line Diffs ('i') | Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
OLDNEW
(Empty)
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
3 // found in the LICENSE file.
4
5 #include "base/prefs/leveldb_pref_store.h"
6
7 #include "base/bind.h"
8 #include "base/callback.h"
9 #include "base/file_util.h"
10 #include "base/json/json_string_value_serializer.h"
11 #include "base/location.h"
12 #include "base/metrics/sparse_histogram.h"
13 #include "base/sequenced_task_runner.h"
14 #include "base/task_runner_util.h"
15 #include "base/threading/thread_restrictions.h"
16 #include "base/time/time.h"
17 #include "base/values.h"
18 #include "third_party/leveldatabase/env_chromium.h"
19 #include "third_party/leveldatabase/src/include/leveldb/db.h"
20 #include "third_party/leveldatabase/src/include/leveldb/write_batch.h"
21
22 namespace {
23
24 enum ErrorMasks {
25 OPENED = 1,
Mattias Nissler (ping if slow) 2014/04/25 12:28:24 nit: write this as 1 << 0 for consistency
Mattias Nissler (ping if slow) 2014/04/25 12:28:24 Do we need the OPENED constant? Isn't that redunda
dgrogan 2014/04/30 09:36:48 Done.
dgrogan 2014/04/30 09:36:48 I want it for the histogram so that we'll know whe
26 DESTROYED = 1 << 1,
27 REPAIRED = 1 << 2,
28 OPEN_FAILED = 1 << 3,
29 DESTROY_FAILED = 1 << 4,
30 REPAIR_FAILED = 1 << 5,
31 DATA_LOST = 1 << 6,
32 IO_ERROR = 1 << 7,
33 ITER_NOT_OK = 1 << 8,
34 FILE_NOT_SPECIFIED = 1 << 9
Mattias Nissler (ping if slow) 2014/04/25 12:28:24 nit: add trailing comma, that leads to simpler edi
dgrogan 2014/04/30 09:36:48 Done.
35 };
36
37 PersistentPrefStore::PrefReadError IntToPrefReadError(int error) {
38 DCHECK(error);
39 if (error == FILE_NOT_SPECIFIED)
40 return PersistentPrefStore::PREF_READ_ERROR_FILE_NOT_SPECIFIED;
41 if (error == OPENED)
42 return PersistentPrefStore::PREF_READ_ERROR_NONE;
43 if (error & IO_ERROR)
44 return PersistentPrefStore::PREF_READ_ERROR_LEVELDB_IO;
45 if (error & OPENED)
46 return PersistentPrefStore::PREF_READ_ERROR_LEVELDB_CORRUPTION;
47 DCHECK(error & OPEN_FAILED);
48 return PersistentPrefStore::PREF_READ_ERROR_LEVELDB_CORRUPTION_READ_ONLY;
49 }
50
51 }
Mattias Nissler (ping if slow) 2014/04/25 12:28:24 nit: // namespace
dgrogan 2014/04/30 09:36:48 Done.
52
53 struct LevelDBPrefStore::ReadingResults {
54 ReadingResults() : error(0) {}
55 bool no_dir;
56 scoped_ptr<leveldb::DB> db;
57 scoped_ptr<PrefValueMap> value_map;
58 int error;
59 };
60
61 // An instance of this class is created on the UI thread but is used
62 // exclusively on the FILE thread.
63 class LevelDBPrefStore::FileThreadSerializer {
64 public:
65 FileThreadSerializer(scoped_ptr<leveldb::DB> db) : db_(db.Pass()) {}
66 void WriteToDatabase(
67 std::map<std::string, std::string>* keys_to_set,
68 std::set<std::string>* keys_to_delete) {
69 DCHECK(keys_to_set->size() > 0 || keys_to_delete->size() > 0);
70 leveldb::WriteBatch batch;
71 for (std::map<std::string, std::string>::iterator iter =
72 keys_to_set->begin();
73 iter != keys_to_set->end();
74 iter++) {
75 batch.Put(iter->first, iter->second);
76 }
77
78 for (std::set<std::string>::iterator iter = keys_to_delete->begin();
79 iter != keys_to_delete->end();
80 iter++) {
81 batch.Delete(*iter);
82 }
83
84 leveldb::Status status = db_->Write(leveldb::WriteOptions(), &batch);
85
86 // DCHECK is fine; the corresponding error is ignored in JsonPrefStore.
87 // There's also no API available to surface the error back up to the caller.
88 // TODO(dgrogan): UMA?
89 DCHECK(status.ok()) << status.ToString();
90 }
91
92 private:
93 scoped_ptr<leveldb::DB> db_;
94 DISALLOW_COPY_AND_ASSIGN(FileThreadSerializer);
95 };
96
97 bool MoveDirectoryAside(const base::FilePath& path) {
98 base::FilePath bad_path = path.AppendASCII(".bad");
99 if (!base::Move(path, bad_path)) {
100 base::DeleteFile(bad_path, true);
101 return false;
102 }
103 return true;
104 }
105
106 /* static */
107 void LevelDBPrefStore::OpenDB(const base::FilePath& path,
108 ReadingResults* reading_results) {
Mattias Nissler (ping if slow) 2014/04/25 12:28:24 Hm, I'm wondering whether we can rewrite this func
dgrogan 2014/04/30 09:36:48 I like the loop idea. Adopted a variant of it.
109 DCHECK_EQ(0, reading_results->error);
110 leveldb::Options options;
111 options.create_if_missing = true;
112 leveldb::DB* db;
113 leveldb::Status status = leveldb::DB::Open(options, path.AsUTF8Unsafe(), &db);
114 if (status.ok()) {
115 reading_results->db.reset(db);
116 reading_results->error = OPENED;
117 return;
118 }
119 reading_results->error = OPEN_FAILED;
120 if (leveldb_env::IsIOError(status)) {
121 reading_results->error |= IO_ERROR;
122 return;
123 }
124 // If it's not an IO error, it's corruption that we can try to repair.
125 status = leveldb::RepairDB(path.AsUTF8Unsafe(), options);
126 if (!status.ok()) {
127 reading_results->error |= REPAIR_FAILED;
128 if (!MoveDirectoryAside(path)) {
129 status = leveldb::DestroyDB(path.AsUTF8Unsafe(), options);
130 if (!status.ok()) {
131 reading_results->error |= DESTROY_FAILED;
132 return;
133 }
134 }
135 reading_results->error |= DESTROYED;
136 } else {
137 reading_results->error |= REPAIRED;
138 }
139 // Either repair or destroy succeeded, now try to open again.
140 status = leveldb::DB::Open(options, path.AsUTF8Unsafe(), &db);
141 if (status.ok()) {
142 reading_results->db.reset(db);
143 reading_results->error |= OPENED;
144 return;
145 }
146 if (reading_results->error & DESTROYED)
147 return;
148 status = leveldb::DestroyDB(path.AsUTF8Unsafe(), options);
149 if (!status.ok()) {
150 reading_results->error |= DESTROY_FAILED;
151 return;
152 }
153 reading_results->error |= DESTROYED;
154 status = leveldb::DB::Open(options, path.AsUTF8Unsafe(), &db);
155 if (!status.ok()) {
156 reading_results->error |= OPEN_FAILED;
157 return;
158 }
159 reading_results->error |= OPENED;
160 reading_results->db.reset(db);
161 }
162
163 /* static */
164 scoped_ptr<LevelDBPrefStore::ReadingResults> LevelDBPrefStore::DoReading(
165 const base::FilePath& path) {
166 base::ThreadRestrictions::AssertIOAllowed();
167
168 scoped_ptr<ReadingResults> reading_results(new ReadingResults);
169
170 reading_results->no_dir = !base::PathExists(path.DirName());
171 OpenDB(path, reading_results.get());
172 if (!reading_results->db) {
173 DCHECK(!(reading_results->error & OPENED));
174 return reading_results.Pass();
175 }
176
177 DCHECK(reading_results->error & OPENED);
178 reading_results->value_map.reset(new PrefValueMap);
179 scoped_ptr<leveldb::Iterator> it(
180 reading_results->db->NewIterator(leveldb::ReadOptions()));
181 // TODO(dgrogan): Is it really necessary to check it->status() each iteration?
182 for (it->SeekToFirst(); it->Valid() && it->status().ok(); it->Next()) {
183 const std::string value_string = it->value().ToString();
184 JSONStringValueSerializer deserializer(value_string);
185 std::string error_message;
186 int error_code;
187 base::Value* json_value =
188 deserializer.Deserialize(&error_code, &error_message);
189 if (json_value) {
190 reading_results->value_map->SetValue(it->key().ToString(), json_value);
191 } else {
192 DLOG(ERROR) << "Invalid json for key " << it->key().ToString()
193 << ": " << error_message;
194 reading_results->error |= DATA_LOST;
195 }
196 }
197
198 if (!it->status().ok())
199 reading_results->error |= ITER_NOT_OK;
200
201 return reading_results.Pass();
202 }
203
204 LevelDBPrefStore::LevelDBPrefStore(
205 const base::FilePath& filename,
206 base::SequencedTaskRunner* sequenced_task_runner)
207 : path_(filename),
208 sequenced_task_runner_(sequenced_task_runner),
209 original_task_runner_(base::MessageLoopProxy::current()),
210 read_only_(false),
211 initialized_(false),
212 read_error_(PREF_READ_ERROR_OTHER),
213 weak_ptr_factory_(this) {}
214
215 LevelDBPrefStore::~LevelDBPrefStore() {
216 CommitPendingWrite();
217 sequenced_task_runner_->DeleteSoon(FROM_HERE, serializer_.release());
218 }
219
220 bool LevelDBPrefStore::GetValue(const std::string& key,
221 const base::Value** result) const {
222 DCHECK(initialized_);
223 const base::Value* tmp = NULL;
224 if (!prefs_.GetValue(key, &tmp)) {
225 return false;
226 }
227
228 if (result)
229 *result = tmp;
230 return true;
231 }
232
233 // Callers of GetMutableValue have to also call ReportValueChanged.
234 bool LevelDBPrefStore::GetMutableValue(const std::string& key,
235 base::Value** result) {
236 DCHECK(initialized_);
237 return prefs_.GetValue(key, result);
238 }
239
240 void LevelDBPrefStore::AddObserver(PrefStore::Observer* observer) {
241 observers_.AddObserver(observer);
242 }
243
244 void LevelDBPrefStore::RemoveObserver(PrefStore::Observer* observer) {
245 observers_.RemoveObserver(observer);
246 }
247
248 bool LevelDBPrefStore::HasObservers() const {
249 return observers_.might_have_observers();
250 }
251
252 bool LevelDBPrefStore::IsInitializationComplete() const { return initialized_; }
253
254 void LevelDBPrefStore::PersistFromUIThread() {
255 if (read_only_)
256 return;
257 DCHECK(serializer_);
258
259 scoped_ptr<std::set<std::string> > keys_to_delete(new std::set<std::string>);
260 keys_to_delete->swap(keys_to_delete_);
261
262 scoped_ptr<std::map<std::string, std::string> > keys_to_set(
263 new std::map<std::string, std::string>);
264 keys_to_set->swap(keys_to_set_);
265
266 sequenced_task_runner_->PostTask(
267 FROM_HERE,
268 base::Bind(&LevelDBPrefStore::FileThreadSerializer::WriteToDatabase,
269 base::Unretained(serializer_.get()),
270 base::Owned(keys_to_set.release()),
271 base::Owned(keys_to_delete.release())));
272 }
273
274 void LevelDBPrefStore::ScheduleWrite() {
275 if (!timer_.IsRunning()) {
276 timer_.Start(FROM_HERE,
277 base::TimeDelta::FromSeconds(10),
278 this,
279 &LevelDBPrefStore::PersistFromUIThread);
280 }
281 }
282
283 void LevelDBPrefStore::SetValue(const std::string& key, base::Value* value) {
284 SetValueInternal(key, value, true /*notify*/);
285 }
286
287 void LevelDBPrefStore::SetValueSilently(const std::string& key,
288 base::Value* value) {
289 SetValueInternal(key, value, false /*notify*/);
290 }
291
292 static std::string Serialize(base::Value* value) {
293 std::string value_string;
294 JSONStringValueSerializer serializer(&value_string);
295 bool serialized_ok = serializer.Serialize(*value);
296 DCHECK(serialized_ok);
297 return value_string;
298 }
299
300 void LevelDBPrefStore::SetValueInternal(const std::string& key,
301 base::Value* value,
302 bool notify) {
303 DCHECK(initialized_);
304 DCHECK(value);
305 scoped_ptr<base::Value> new_value(value);
306 base::Value* old_value = NULL;
307 prefs_.GetValue(key, &old_value);
308 if (!old_value || !value->Equals(old_value)) {
309 std::string value_string = Serialize(value);
310 prefs_.SetValue(key, new_value.release());
311 MarkForInsertion(key, value_string);
312 if (notify)
313 NotifyObservers(key);
314 }
315 }
316
317 void LevelDBPrefStore::RemoveValue(const std::string& key) {
318 DCHECK(initialized_);
319 if (prefs_.RemoveValue(key)) {
320 MarkForDeletion(key);
321 NotifyObservers(key);
322 }
323 }
324
325 bool LevelDBPrefStore::ReadOnly() const { return read_only_; }
326
327 PersistentPrefStore::PrefReadError LevelDBPrefStore::GetReadError() const {
328 return read_error_;
329 }
330
331 PersistentPrefStore::PrefReadError LevelDBPrefStore::ReadPrefs() {
332 DCHECK(!initialized_);
333 scoped_ptr<ReadingResults> reading_results;
334 if (path_.empty()) {
335 reading_results.reset(new ReadingResults);
336 reading_results->error = FILE_NOT_SPECIFIED;
337 } else {
338 reading_results = DoReading(path_);
339 }
340
341 PrefReadError error = IntToPrefReadError(reading_results->error);
342 OnStorageRead(reading_results.Pass());
343 return error;
344 }
345
346 void LevelDBPrefStore::ReadPrefsAsync(ReadErrorDelegate* error_delegate) {
347 DCHECK_EQ(false, initialized_);
348 error_delegate_.reset(error_delegate);
349 if (path_.empty()) {
350 scoped_ptr<ReadingResults> reading_results(new ReadingResults);
351 reading_results->error = FILE_NOT_SPECIFIED;
352 OnStorageRead(reading_results.Pass());
353 return;
354 }
355 PostTaskAndReplyWithResult(sequenced_task_runner_,
356 FROM_HERE,
357 base::Bind(&LevelDBPrefStore::DoReading, path_),
358 base::Bind(&LevelDBPrefStore::OnStorageRead,
359 weak_ptr_factory_.GetWeakPtr()));
360 }
361
362 void LevelDBPrefStore::CommitPendingWrite() {
363 if (timer_.IsRunning()) {
364 timer_.Stop();
365 PersistFromUIThread();
366 }
367 }
368
369 void LevelDBPrefStore::MarkForDeletion(const std::string& key) {
370 if (read_only_)
371 return;
372 keys_to_delete_.insert(key);
373 // Need to erase in case there's a set operation in the same batch that would
374 // clobber this delete.
375 keys_to_set_.erase(key);
376 ScheduleWrite();
377 }
378
379 void LevelDBPrefStore::MarkForInsertion(const std::string& key,
380 const std::string& value) {
381 if (read_only_)
382 return;
383 keys_to_set_[key] = value;
384 // Need to erase in case there's a delete operation in the same batch that
385 // would clobber this set.
386 keys_to_delete_.erase(key);
387 ScheduleWrite();
388 }
389
390 void LevelDBPrefStore::ReportValueChanged(const std::string& key) {
391 base::Value* new_value = NULL;
392 DCHECK(prefs_.GetValue(key, &new_value));
393 std::string value_string = Serialize(new_value);
394 MarkForInsertion(key, value_string);
395 NotifyObservers(key);
396 }
397
398 void LevelDBPrefStore::NotifyObservers(const std::string& key) {
399 FOR_EACH_OBSERVER(PrefStore::Observer, observers_, OnPrefValueChanged(key));
400 }
401
402 void LevelDBPrefStore::OnStorageRead(
403 scoped_ptr<LevelDBPrefStore::ReadingResults> reading_results) {
404 UMA_HISTOGRAM_SPARSE_SLOWLY("LevelDBPrefStore.ReadErrors",
405 reading_results->error);
406 read_error_ = IntToPrefReadError(reading_results->error);
407
408 if (reading_results->no_dir) {
409 FOR_EACH_OBSERVER(
410 PrefStore::Observer, observers_, OnInitializationCompleted(false));
411 return;
412 }
413
414 initialized_ = true;
415
416 switch (read_error_) {
417 case PREF_READ_ERROR_FILE_NOT_SPECIFIED:
418 case PREF_READ_ERROR_LEVELDB_IO:
419 case PREF_READ_ERROR_LEVELDB_CORRUPTION_READ_ONLY:
Mattias Nissler (ping if slow) 2014/04/25 12:28:24 Do we actually need the distinction between CORRUP
dgrogan 2014/04/30 09:36:48 Without the distinction HandleReadError in chrome_
420 DCHECK(reading_results->value_map.get() == NULL);
Mattias Nissler (ping if slow) 2014/04/25 12:28:24 No need to test for .get() == NULL, scoped_ptr wor
dgrogan 2014/04/30 09:36:48 Done.
421 DCHECK(reading_results->db.get() == NULL);
422 read_only_ = true;
423 break;
424 case PREF_READ_ERROR_NONE:
425 case PREF_READ_ERROR_LEVELDB_CORRUPTION:
426 serializer_.reset(new FileThreadSerializer(reading_results->db.Pass()));
427 prefs_.Swap(reading_results->value_map.get());
428 break;
429 default:
430 NOTREACHED() << "Unknown error: " << read_error_;
431 }
432
433 // TODO(dgrogan): Call pref_filter_->FilterOnLoad
434
435 if (error_delegate_.get() && read_error_ != PREF_READ_ERROR_NONE)
436 error_delegate_->OnError(read_error_);
437
438 FOR_EACH_OBSERVER(
439 PrefStore::Observer, observers_, OnInitializationCompleted(true));
440 }
OLDNEW
« no previous file with comments | « base/prefs/leveldb_pref_store.h ('k') | base/prefs/leveldb_pref_store_unittest.cc » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698