leveldb_database.cc revision 1e9bf3e0803691d0a228da41fc608347b6db4340
1// Copyright (c) 2013 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 "content/browser/indexed_db/leveldb/leveldb_database.h"
6
7#include <cerrno>
8
9#include "base/basictypes.h"
10#include "base/logging.h"
11#include "base/memory/scoped_ptr.h"
12#include "base/metrics/histogram.h"
13#include "base/strings/string16.h"
14#include "base/strings/string_piece.h"
15#include "base/strings/stringprintf.h"
16#include "base/strings/utf_string_conversions.h"
17#include "base/sys_info.h"
18#include "content/browser/indexed_db/leveldb/leveldb_comparator.h"
19#include "content/browser/indexed_db/leveldb/leveldb_iterator.h"
20#include "content/browser/indexed_db/leveldb/leveldb_write_batch.h"
21#include "third_party/leveldatabase/env_chromium.h"
22#include "third_party/leveldatabase/env_idb.h"
23#include "third_party/leveldatabase/src/helpers/memenv/memenv.h"
24#include "third_party/leveldatabase/src/include/leveldb/comparator.h"
25#include "third_party/leveldatabase/src/include/leveldb/db.h"
26#include "third_party/leveldatabase/src/include/leveldb/env.h"
27#include "third_party/leveldatabase/src/include/leveldb/slice.h"
28
29using base::StringPiece;
30
31namespace content {
32
33// Forcing flushes to disk at the end of a transaction guarantees that the
34// data hit disk, but drastically impacts throughput when the filesystem is
35// busy with background compactions. Not syncing trades off reliability for
36// performance. Note that background compactions which move data from the
37// log to SSTs are still done with reliable writes.
38//
39// Sync writes are necessary on Windows for quota calculations; POSIX
40// calculates file sizes correctly even when not synced to disk.
41#if defined(OS_WIN)
42static const bool kSyncWrites = true;
43#else
44static const bool kSyncWrites = false;
45#endif
46
47static leveldb::Slice MakeSlice(const StringPiece& s) {
48  return leveldb::Slice(s.begin(), s.size());
49}
50
51static StringPiece MakeStringPiece(const leveldb::Slice& s) {
52  return StringPiece(s.data(), s.size());
53}
54
55class ComparatorAdapter : public leveldb::Comparator {
56 public:
57  explicit ComparatorAdapter(const LevelDBComparator* comparator)
58      : comparator_(comparator) {}
59
60  virtual int Compare(const leveldb::Slice& a, const leveldb::Slice& b) const
61      OVERRIDE {
62    return comparator_->Compare(MakeStringPiece(a), MakeStringPiece(b));
63  }
64
65  virtual const char* Name() const OVERRIDE { return comparator_->Name(); }
66
67  // TODO(jsbell): Support the methods below in the future.
68  virtual void FindShortestSeparator(std::string* start,
69                                     const leveldb::Slice& limit) const
70      OVERRIDE {}
71  virtual void FindShortSuccessor(std::string* key) const OVERRIDE {}
72
73 private:
74  const LevelDBComparator* comparator_;
75};
76
77LevelDBSnapshot::LevelDBSnapshot(LevelDBDatabase* db)
78    : db_(db->db_.get()), snapshot_(db_->GetSnapshot()) {}
79
80LevelDBSnapshot::~LevelDBSnapshot() { db_->ReleaseSnapshot(snapshot_); }
81
82LevelDBDatabase::LevelDBDatabase() {}
83
84LevelDBDatabase::~LevelDBDatabase() {
85  // db_'s destructor uses comparator_adapter_; order of deletion is important.
86  db_.reset();
87  comparator_adapter_.reset();
88  env_.reset();
89}
90
91static leveldb::Status OpenDB(leveldb::Comparator* comparator,
92                              leveldb::Env* env,
93                              const base::FilePath& path,
94                              leveldb::DB** db) {
95  leveldb::Options options;
96  options.comparator = comparator;
97  options.create_if_missing = true;
98  options.paranoid_checks = true;
99  options.compression = leveldb::kSnappyCompression;
100
101  // For info about the troubles we've run into with this parameter, see:
102  // https://code.google.com/p/chromium/issues/detail?id=227313#c11
103  options.max_open_files = 80;
104  options.env = env;
105
106  // ChromiumEnv assumes UTF8, converts back to FilePath before using.
107  return leveldb::DB::Open(options, path.AsUTF8Unsafe(), db);
108}
109
110bool LevelDBDatabase::Destroy(const base::FilePath& file_name) {
111  leveldb::Options options;
112  options.env = leveldb::IDBEnv();
113  // ChromiumEnv assumes UTF8, converts back to FilePath before using.
114  const leveldb::Status s =
115      leveldb::DestroyDB(file_name.AsUTF8Unsafe(), options);
116  return s.ok();
117}
118
119namespace {
120class LockImpl : public LevelDBLock {
121 public:
122  explicit LockImpl(leveldb::Env* env, leveldb::FileLock* lock)
123      : env_(env), lock_(lock) {}
124  virtual ~LockImpl() { env_->UnlockFile(lock_); }
125 private:
126  leveldb::Env* env_;
127  leveldb::FileLock* lock_;
128};
129}
130
131scoped_ptr<LevelDBLock> LevelDBDatabase::LockForTesting(
132    const base::FilePath& file_name) {
133  leveldb::Env* env = leveldb::IDBEnv();
134  base::FilePath lock_path = file_name.AppendASCII("LOCK");
135  leveldb::FileLock* lock = NULL;
136  leveldb::Status status = env->LockFile(lock_path.AsUTF8Unsafe(), &lock);
137  if (!status.ok())
138    return scoped_ptr<LevelDBLock>();
139  DCHECK(lock);
140  return scoped_ptr<LevelDBLock>(new LockImpl(env, lock));
141}
142
143static int CheckFreeSpace(const char* const type,
144                          const base::FilePath& file_name) {
145  std::string name =
146      std::string("WebCore.IndexedDB.LevelDB.Open") + type + "FreeDiskSpace";
147  int64 free_disk_space_in_k_bytes =
148      base::SysInfo::AmountOfFreeDiskSpace(file_name) / 1024;
149  if (free_disk_space_in_k_bytes < 0) {
150    base::Histogram::FactoryGet(
151        "WebCore.IndexedDB.LevelDB.FreeDiskSpaceFailure",
152        1,
153        2 /*boundary*/,
154        2 /*boundary*/ + 1,
155        base::HistogramBase::kUmaTargetedHistogramFlag)->Add(1 /*sample*/);
156    return -1;
157  }
158  int clamped_disk_space_k_bytes = free_disk_space_in_k_bytes > INT_MAX
159                                       ? INT_MAX
160                                       : free_disk_space_in_k_bytes;
161  const uint64 histogram_max = static_cast<uint64>(1e9);
162  COMPILE_ASSERT(histogram_max <= INT_MAX, histogram_max_too_big);
163  base::Histogram::FactoryGet(name,
164                              1,
165                              histogram_max,
166                              11 /*buckets*/,
167                              base::HistogramBase::kUmaTargetedHistogramFlag)
168      ->Add(clamped_disk_space_k_bytes);
169  return clamped_disk_space_k_bytes;
170}
171
172static void ParseAndHistogramIOErrorDetails(const std::string& histogram_name,
173                                            const leveldb::Status& s) {
174  leveldb_env::MethodID method;
175  int error = -1;
176  leveldb_env::ErrorParsingResult result =
177      leveldb_env::ParseMethodAndError(s.ToString().c_str(), &method, &error);
178  if (result == leveldb_env::NONE)
179    return;
180  std::string method_histogram_name(histogram_name);
181  method_histogram_name.append(".EnvMethod");
182  base::LinearHistogram::FactoryGet(
183      method_histogram_name,
184      1,
185      leveldb_env::kNumEntries,
186      leveldb_env::kNumEntries + 1,
187      base::HistogramBase::kUmaTargetedHistogramFlag)->Add(method);
188
189  std::string error_histogram_name(histogram_name);
190
191  if (result == leveldb_env::METHOD_AND_PFE) {
192    DCHECK(error < 0);
193    error_histogram_name.append(std::string(".PFE.") +
194                                leveldb_env::MethodIDToString(method));
195    base::LinearHistogram::FactoryGet(
196        error_histogram_name,
197        1,
198        -base::PLATFORM_FILE_ERROR_MAX,
199        -base::PLATFORM_FILE_ERROR_MAX + 1,
200        base::HistogramBase::kUmaTargetedHistogramFlag)->Add(-error);
201  } else if (result == leveldb_env::METHOD_AND_ERRNO) {
202    error_histogram_name.append(std::string(".Errno.") +
203                                leveldb_env::MethodIDToString(method));
204    base::LinearHistogram::FactoryGet(
205        error_histogram_name,
206        1,
207        ERANGE + 1,
208        ERANGE + 2,
209        base::HistogramBase::kUmaTargetedHistogramFlag)->Add(error);
210  }
211}
212
213static void ParseAndHistogramCorruptionDetails(
214    const std::string& histogram_name,
215    const leveldb::Status& status) {
216  int error = leveldb_env::ParseCorruptionMessage(status);
217  DCHECK(error >= 0);
218  std::string corruption_histogram_name(histogram_name);
219  corruption_histogram_name.append(".Corruption");
220  const int kNumPatterns = leveldb_env::GetNumCorruptionPatterns();
221  base::LinearHistogram::FactoryGet(
222      corruption_histogram_name,
223      1,
224      kNumPatterns,
225      kNumPatterns + 1,
226      base::HistogramBase::kUmaTargetedHistogramFlag)->Add(error);
227}
228
229static void HistogramLevelDBError(const std::string& histogram_name,
230                                  const leveldb::Status& s) {
231  if (s.ok()) {
232    NOTREACHED();
233    return;
234  }
235  enum {
236    LEVEL_DB_NOT_FOUND,
237    LEVEL_DB_CORRUPTION,
238    LEVEL_DB_IO_ERROR,
239    LEVEL_DB_OTHER,
240    LEVEL_DB_MAX_ERROR
241  };
242  int leveldb_error = LEVEL_DB_OTHER;
243  if (s.IsNotFound())
244    leveldb_error = LEVEL_DB_NOT_FOUND;
245  else if (s.IsCorruption())
246    leveldb_error = LEVEL_DB_CORRUPTION;
247  else if (s.IsIOError())
248    leveldb_error = LEVEL_DB_IO_ERROR;
249  base::Histogram::FactoryGet(histogram_name,
250                              1,
251                              LEVEL_DB_MAX_ERROR,
252                              LEVEL_DB_MAX_ERROR + 1,
253                              base::HistogramBase::kUmaTargetedHistogramFlag)
254      ->Add(leveldb_error);
255  if (s.IsIOError())
256    ParseAndHistogramIOErrorDetails(histogram_name, s);
257  else
258    ParseAndHistogramCorruptionDetails(histogram_name, s);
259}
260
261leveldb::Status LevelDBDatabase::Open(const base::FilePath& file_name,
262                                      const LevelDBComparator* comparator,
263                                      scoped_ptr<LevelDBDatabase>* result,
264                                      bool* is_disk_full) {
265  scoped_ptr<ComparatorAdapter> comparator_adapter(
266      new ComparatorAdapter(comparator));
267
268  leveldb::DB* db;
269  const leveldb::Status s =
270      OpenDB(comparator_adapter.get(), leveldb::IDBEnv(), file_name, &db);
271
272  if (!s.ok()) {
273    HistogramLevelDBError("WebCore.IndexedDB.LevelDBOpenErrors", s);
274    int free_space_k_bytes = CheckFreeSpace("Failure", file_name);
275    // Disks with <100k of free space almost never succeed in opening a
276    // leveldb database.
277    if (is_disk_full)
278      *is_disk_full = free_space_k_bytes >= 0 && free_space_k_bytes < 100;
279
280    LOG(ERROR) << "Failed to open LevelDB database from "
281               << file_name.AsUTF8Unsafe() << "," << s.ToString();
282    return s;
283  }
284
285  CheckFreeSpace("Success", file_name);
286
287  (*result).reset(new LevelDBDatabase);
288  (*result)->db_ = make_scoped_ptr(db);
289  (*result)->comparator_adapter_ = comparator_adapter.Pass();
290  (*result)->comparator_ = comparator;
291
292  return s;
293}
294
295scoped_ptr<LevelDBDatabase> LevelDBDatabase::OpenInMemory(
296    const LevelDBComparator* comparator) {
297  scoped_ptr<ComparatorAdapter> comparator_adapter(
298      new ComparatorAdapter(comparator));
299  scoped_ptr<leveldb::Env> in_memory_env(leveldb::NewMemEnv(leveldb::IDBEnv()));
300
301  leveldb::DB* db;
302  const leveldb::Status s = OpenDB(
303      comparator_adapter.get(), in_memory_env.get(), base::FilePath(), &db);
304
305  if (!s.ok()) {
306    LOG(ERROR) << "Failed to open in-memory LevelDB database: " << s.ToString();
307    return scoped_ptr<LevelDBDatabase>();
308  }
309
310  scoped_ptr<LevelDBDatabase> result(new LevelDBDatabase);
311  result->env_ = in_memory_env.Pass();
312  result->db_ = make_scoped_ptr(db);
313  result->comparator_adapter_ = comparator_adapter.Pass();
314  result->comparator_ = comparator;
315
316  return result.Pass();
317}
318
319bool LevelDBDatabase::Put(const StringPiece& key, std::string* value) {
320  leveldb::WriteOptions write_options;
321  write_options.sync = kSyncWrites;
322
323  const leveldb::Status s =
324      db_->Put(write_options, MakeSlice(key), MakeSlice(*value));
325  if (s.ok())
326    return true;
327  LOG(ERROR) << "LevelDB put failed: " << s.ToString();
328  return false;
329}
330
331bool LevelDBDatabase::Remove(const StringPiece& key) {
332  leveldb::WriteOptions write_options;
333  write_options.sync = kSyncWrites;
334
335  const leveldb::Status s = db_->Delete(write_options, MakeSlice(key));
336  if (s.ok())
337    return true;
338  if (s.IsNotFound())
339    return false;
340  LOG(ERROR) << "LevelDB remove failed: " << s.ToString();
341  return false;
342}
343
344bool LevelDBDatabase::Get(const StringPiece& key,
345                          std::string* value,
346                          bool* found,
347                          const LevelDBSnapshot* snapshot) {
348  *found = false;
349  leveldb::ReadOptions read_options;
350  read_options.verify_checksums = true;  // TODO(jsbell): Disable this if the
351                                         // performance impact is too great.
352  read_options.snapshot = snapshot ? snapshot->snapshot_ : 0;
353
354  const leveldb::Status s = db_->Get(read_options, MakeSlice(key), value);
355  if (s.ok()) {
356    *found = true;
357    return true;
358  }
359  if (s.IsNotFound())
360    return true;
361  HistogramLevelDBError("WebCore.IndexedDB.LevelDBReadErrors", s);
362  LOG(ERROR) << "LevelDB get failed: " << s.ToString();
363  return false;
364}
365
366bool LevelDBDatabase::Write(const LevelDBWriteBatch& write_batch) {
367  leveldb::WriteOptions write_options;
368  write_options.sync = kSyncWrites;
369
370  const leveldb::Status s =
371      db_->Write(write_options, write_batch.write_batch_.get());
372  if (s.ok())
373    return true;
374  HistogramLevelDBError("WebCore.IndexedDB.LevelDBWriteErrors", s);
375  LOG(ERROR) << "LevelDB write failed: " << s.ToString();
376  return false;
377}
378
379namespace {
380class IteratorImpl : public LevelDBIterator {
381 public:
382  virtual ~IteratorImpl() {}
383
384  virtual bool IsValid() const OVERRIDE;
385  virtual void SeekToLast() OVERRIDE;
386  virtual void Seek(const StringPiece& target) OVERRIDE;
387  virtual void Next() OVERRIDE;
388  virtual void Prev() OVERRIDE;
389  virtual StringPiece Key() const OVERRIDE;
390  virtual StringPiece Value() const OVERRIDE;
391
392 private:
393  friend class content::LevelDBDatabase;
394  explicit IteratorImpl(scoped_ptr<leveldb::Iterator> iterator);
395  void CheckStatus();
396
397  scoped_ptr<leveldb::Iterator> iterator_;
398};
399}
400
401IteratorImpl::IteratorImpl(scoped_ptr<leveldb::Iterator> it)
402    : iterator_(it.Pass()) {}
403
404void IteratorImpl::CheckStatus() {
405  const leveldb::Status s = iterator_->status();
406  if (!s.ok())
407    LOG(ERROR) << "LevelDB iterator error: " << s.ToString();
408}
409
410bool IteratorImpl::IsValid() const { return iterator_->Valid(); }
411
412void IteratorImpl::SeekToLast() {
413  iterator_->SeekToLast();
414  CheckStatus();
415}
416
417void IteratorImpl::Seek(const StringPiece& target) {
418  iterator_->Seek(MakeSlice(target));
419  CheckStatus();
420}
421
422void IteratorImpl::Next() {
423  DCHECK(IsValid());
424  iterator_->Next();
425  CheckStatus();
426}
427
428void IteratorImpl::Prev() {
429  DCHECK(IsValid());
430  iterator_->Prev();
431  CheckStatus();
432}
433
434StringPiece IteratorImpl::Key() const {
435  DCHECK(IsValid());
436  return MakeStringPiece(iterator_->key());
437}
438
439StringPiece IteratorImpl::Value() const {
440  DCHECK(IsValid());
441  return MakeStringPiece(iterator_->value());
442}
443
444scoped_ptr<LevelDBIterator> LevelDBDatabase::CreateIterator(
445    const LevelDBSnapshot* snapshot) {
446  leveldb::ReadOptions read_options;
447  read_options.verify_checksums = true;  // TODO(jsbell): Disable this if the
448                                         // performance impact is too great.
449  read_options.snapshot = snapshot ? snapshot->snapshot_ : 0;
450
451  scoped_ptr<leveldb::Iterator> i(db_->NewIterator(read_options));
452  return scoped_ptr<LevelDBIterator>(new IteratorImpl(i.Pass()));
453}
454
455const LevelDBComparator* LevelDBDatabase::Comparator() const {
456  return comparator_;
457}
458
459}  // namespace content
460