leveldb_database.cc revision 868fa2fe829687343ffae624259930155e16dbd8
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 <string>
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/utf_string_conversions.h"
15#include "base/sys_info.h"
16#include "content/browser/indexed_db/leveldb/leveldb_comparator.h"
17#include "content/browser/indexed_db/leveldb/leveldb_iterator.h"
18#include "content/browser/indexed_db/leveldb/leveldb_slice.h"
19#include "content/browser/indexed_db/leveldb/leveldb_write_batch.h"
20#include "third_party/leveldatabase/env_idb.h"
21#include "third_party/leveldatabase/src/helpers/memenv/memenv.h"
22#include "third_party/leveldatabase/src/include/leveldb/comparator.h"
23#include "third_party/leveldatabase/src/include/leveldb/db.h"
24#include "third_party/leveldatabase/src/include/leveldb/env.h"
25#include "third_party/leveldatabase/src/include/leveldb/slice.h"
26
27namespace content {
28
29static leveldb::Slice MakeSlice(const std::vector<char>& value) {
30  DCHECK_GT(value.size(), static_cast<size_t>(0));
31  return leveldb::Slice(&*value.begin(), value.size());
32}
33
34static leveldb::Slice MakeSlice(const LevelDBSlice& s) {
35  return leveldb::Slice(s.begin(), s.end() - s.begin());
36}
37
38static LevelDBSlice MakeLevelDBSlice(const leveldb::Slice& s) {
39  return LevelDBSlice(s.data(), s.data() + s.size());
40}
41
42class ComparatorAdapter : public leveldb::Comparator {
43 public:
44  explicit ComparatorAdapter(const LevelDBComparator* comparator)
45      : comparator_(comparator) {}
46
47  virtual int Compare(const leveldb::Slice& a, const leveldb::Slice& b) const
48      OVERRIDE {
49    return comparator_->Compare(MakeLevelDBSlice(a), MakeLevelDBSlice(b));
50  }
51
52  virtual const char* Name() const OVERRIDE { return comparator_->Name(); }
53
54  // TODO(jsbell): Support the methods below in the future.
55  virtual void FindShortestSeparator(std::string* start,
56                                     const leveldb::Slice& limit) const
57      OVERRIDE {}
58  virtual void FindShortSuccessor(std::string* key) const OVERRIDE {}
59
60 private:
61  const LevelDBComparator* comparator_;
62};
63
64LevelDBSnapshot::LevelDBSnapshot(LevelDBDatabase* db)
65    : db_(db->db_.get()), snapshot_(db_->GetSnapshot()) {}
66
67LevelDBSnapshot::~LevelDBSnapshot() { db_->ReleaseSnapshot(snapshot_); }
68
69LevelDBDatabase::LevelDBDatabase() {}
70
71LevelDBDatabase::~LevelDBDatabase() {
72  // db_'s destructor uses comparator_adapter_; order of deletion is important.
73  db_.reset();
74  comparator_adapter_.reset();
75  env_.reset();
76}
77
78static leveldb::Status OpenDB(leveldb::Comparator* comparator,
79                              leveldb::Env* env,
80                              const base::FilePath& path,
81                              leveldb::DB** db) {
82  leveldb::Options options;
83  options.comparator = comparator;
84  options.create_if_missing = true;
85  options.paranoid_checks = true;
86
87  // Marking compression as explicitly off so snappy support can be
88  // compiled in for other leveldb clients without implicitly enabling
89  // it for IndexedDB. http://crbug.com/81384
90  options.compression = leveldb::kNoCompression;
91
92  // 20 max_open_files is the minimum LevelDB allows but its cache behaves
93  // poorly with less than 4 files per shard. As of this writing the latest
94  // leveldb (1.9) hardcodes 16 shards. See
95  // https://code.google.com/p/chromium/issues/detail?id=227313#c11
96  options.max_open_files = 80;
97  options.env = env;
98
99  // ChromiumEnv assumes UTF8, converts back to FilePath before using.
100  return leveldb::DB::Open(options, path.AsUTF8Unsafe(), db);
101}
102
103bool LevelDBDatabase::Destroy(const base::FilePath& file_name) {
104  leveldb::Options options;
105  options.env = leveldb::IDBEnv();
106  // ChromiumEnv assumes UTF8, converts back to FilePath before using.
107  const leveldb::Status s =
108      leveldb::DestroyDB(file_name.AsUTF8Unsafe(), options);
109  return s.ok();
110}
111
112static int CheckFreeSpace(const char* type, const base::FilePath& file_name) {
113  // TODO(dgrogan): Change string16 -> std::string.
114  string16 name = ASCIIToUTF16("WebCore.IndexedDB.LevelDB.Open") +
115                  ASCIIToUTF16(type) + ASCIIToUTF16("FreeDiskSpace");
116  int64 free_disk_space_in_k_bytes =
117      base::SysInfo::AmountOfFreeDiskSpace(file_name) / 1024;
118  if (free_disk_space_in_k_bytes < 0) {
119    base::Histogram::FactoryGet(
120        "WebCore.IndexedDB.LevelDB.FreeDiskSpaceFailure",
121        1,
122        2 /*boundary*/,
123        2 /*boundary*/ + 1,
124        base::HistogramBase::kUmaTargetedHistogramFlag)->Add(1 /*sample*/);
125    return -1;
126  }
127  int clamped_disk_space_k_bytes =
128      free_disk_space_in_k_bytes > INT_MAX ? INT_MAX
129                                           : free_disk_space_in_k_bytes;
130  const uint64 histogram_max = static_cast<uint64>(1e9);
131  COMPILE_ASSERT(histogram_max <= INT_MAX, histogram_max_too_big);
132  base::Histogram::FactoryGet(UTF16ToUTF8(name),
133                              1,
134                              histogram_max,
135                              11 /*buckets*/,
136                              base::HistogramBase::kUmaTargetedHistogramFlag)
137      ->Add(clamped_disk_space_k_bytes);
138  return clamped_disk_space_k_bytes;
139}
140
141static void HistogramLevelDBError(const char* histogram_name,
142                                  const leveldb::Status& s) {
143  DCHECK(!s.ok());
144  enum {
145    LEVEL_DB_NOT_FOUND,
146    LEVEL_DB_CORRUPTION,
147    LEVEL_DB_IO_ERROR,
148    LEVEL_DB_OTHER,
149    LEVEL_DB_MAX_ERROR
150  };
151  int leveldb_error = LEVEL_DB_OTHER;
152  if (s.IsNotFound())
153    leveldb_error = LEVEL_DB_NOT_FOUND;
154  else if (s.IsCorruption())
155    leveldb_error = LEVEL_DB_CORRUPTION;
156  else if (s.IsIOError())
157    leveldb_error = LEVEL_DB_IO_ERROR;
158  base::Histogram::FactoryGet(histogram_name,
159                              1,
160                              LEVEL_DB_MAX_ERROR,
161                              LEVEL_DB_MAX_ERROR + 1,
162                              base::HistogramBase::kUmaTargetedHistogramFlag)
163      ->Add(leveldb_error);
164}
165
166scoped_ptr<LevelDBDatabase> LevelDBDatabase::Open(
167    const base::FilePath& file_name,
168    const LevelDBComparator* comparator,
169    bool* is_disk_full) {
170  scoped_ptr<ComparatorAdapter> comparator_adapter(
171      new ComparatorAdapter(comparator));
172
173  leveldb::DB* db;
174  const leveldb::Status s =
175      OpenDB(comparator_adapter.get(), leveldb::IDBEnv(), file_name, &db);
176
177  if (!s.ok()) {
178    HistogramLevelDBError("WebCore.IndexedDB.LevelDBOpenErrors", s);
179    int free_space_k_bytes = CheckFreeSpace("Failure", file_name);
180    // Disks with <100k of free space almost never succeed in opening a
181    // leveldb database.
182    if (is_disk_full)
183      *is_disk_full = free_space_k_bytes >= 0 && free_space_k_bytes < 100;
184
185    LOG(ERROR) << "Failed to open LevelDB database from "
186               << file_name.AsUTF8Unsafe() << "," << s.ToString();
187    return scoped_ptr<LevelDBDatabase>();
188  }
189
190  CheckFreeSpace("Success", file_name);
191
192  scoped_ptr<LevelDBDatabase> result(new LevelDBDatabase);
193  result->db_ = make_scoped_ptr(db);
194  result->comparator_adapter_ = comparator_adapter.Pass();
195  result->comparator_ = comparator;
196
197  return result.Pass();
198}
199
200scoped_ptr<LevelDBDatabase> LevelDBDatabase::OpenInMemory(
201    const LevelDBComparator* comparator) {
202  scoped_ptr<ComparatorAdapter> comparator_adapter(
203      new ComparatorAdapter(comparator));
204  scoped_ptr<leveldb::Env> in_memory_env(leveldb::NewMemEnv(leveldb::IDBEnv()));
205
206  leveldb::DB* db;
207  const leveldb::Status s = OpenDB(
208      comparator_adapter.get(), in_memory_env.get(), base::FilePath(), &db);
209
210  if (!s.ok()) {
211    LOG(ERROR) << "Failed to open in-memory LevelDB database: " << s.ToString();
212    return scoped_ptr<LevelDBDatabase>();
213  }
214
215  scoped_ptr<LevelDBDatabase> result(new LevelDBDatabase);
216  result->env_ = in_memory_env.Pass();
217  result->db_ = make_scoped_ptr(db);
218  result->comparator_adapter_ = comparator_adapter.Pass();
219  result->comparator_ = comparator;
220
221  return result.Pass();
222}
223
224bool LevelDBDatabase::Put(const LevelDBSlice& key,
225                          std::vector<char>* value) {
226  leveldb::WriteOptions write_options;
227  write_options.sync = true;
228
229  const leveldb::Status s =
230      db_->Put(write_options, MakeSlice(key), MakeSlice(*value));
231  if (s.ok())
232    return true;
233  LOG(ERROR) << "LevelDB put failed: " << s.ToString();
234  return false;
235}
236
237bool LevelDBDatabase::Remove(const LevelDBSlice& key) {
238  leveldb::WriteOptions write_options;
239  write_options.sync = true;
240
241  const leveldb::Status s = db_->Delete(write_options, MakeSlice(key));
242  if (s.ok())
243    return true;
244  if (s.IsNotFound())
245    return false;
246  LOG(ERROR) << "LevelDB remove failed: " << s.ToString();
247  return false;
248}
249
250bool LevelDBDatabase::Get(const LevelDBSlice& key,
251                          std::string* value,
252                          bool* found,
253                          const LevelDBSnapshot* snapshot) {
254  *found = false;
255  leveldb::ReadOptions read_options;
256  read_options.verify_checksums = true;  // TODO(jsbell): Disable this if the
257                                         // performance impact is too great.
258  read_options.snapshot = snapshot ? snapshot->snapshot_ : 0;
259
260  const leveldb::Status s = db_->Get(read_options, MakeSlice(key), value);
261  if (s.ok()) {
262    *found = true;
263    return true;
264  }
265  if (s.IsNotFound())
266    return true;
267  LOG(ERROR) << "LevelDB get failed: " << s.ToString();
268  return false;
269}
270
271bool LevelDBDatabase::Write(const LevelDBWriteBatch& write_batch) {
272  leveldb::WriteOptions write_options;
273  write_options.sync = true;
274
275  const leveldb::Status s =
276      db_->Write(write_options, write_batch.write_batch_.get());
277  if (s.ok())
278    return true;
279  HistogramLevelDBError("WebCore.IndexedDB.LevelDBWriteErrors", s);
280  LOG(ERROR) << "LevelDB write failed: " << s.ToString();
281  return false;
282}
283
284namespace {
285class IteratorImpl : public LevelDBIterator {
286 public:
287  virtual ~IteratorImpl() {}
288
289  virtual bool IsValid() const OVERRIDE;
290  virtual void SeekToLast() OVERRIDE;
291  virtual void Seek(const LevelDBSlice& target) OVERRIDE;
292  virtual void Next() OVERRIDE;
293  virtual void Prev() OVERRIDE;
294  virtual LevelDBSlice Key() const OVERRIDE;
295  virtual LevelDBSlice Value() const OVERRIDE;
296
297 private:
298  friend class content::LevelDBDatabase;
299  explicit IteratorImpl(scoped_ptr<leveldb::Iterator> iterator);
300  void CheckStatus();
301
302  scoped_ptr<leveldb::Iterator> iterator_;
303};
304}
305
306IteratorImpl::IteratorImpl(scoped_ptr<leveldb::Iterator> it)
307    : iterator_(it.Pass()) {}
308
309void IteratorImpl::CheckStatus() {
310  const leveldb::Status s = iterator_->status();
311  if (!s.ok())
312    LOG(ERROR) << "LevelDB iterator error: " << s.ToString();
313}
314
315bool IteratorImpl::IsValid() const { return iterator_->Valid(); }
316
317void IteratorImpl::SeekToLast() {
318  iterator_->SeekToLast();
319  CheckStatus();
320}
321
322void IteratorImpl::Seek(const LevelDBSlice& target) {
323  iterator_->Seek(MakeSlice(target));
324  CheckStatus();
325}
326
327void IteratorImpl::Next() {
328  DCHECK(IsValid());
329  iterator_->Next();
330  CheckStatus();
331}
332
333void IteratorImpl::Prev() {
334  DCHECK(IsValid());
335  iterator_->Prev();
336  CheckStatus();
337}
338
339LevelDBSlice IteratorImpl::Key() const {
340  DCHECK(IsValid());
341  return MakeLevelDBSlice(iterator_->key());
342}
343
344LevelDBSlice IteratorImpl::Value() const {
345  DCHECK(IsValid());
346  return MakeLevelDBSlice(iterator_->value());
347}
348
349scoped_ptr<LevelDBIterator> LevelDBDatabase::CreateIterator(
350    const LevelDBSnapshot* snapshot) {
351  leveldb::ReadOptions read_options;
352  read_options.verify_checksums = true;  // TODO(jsbell): Disable this if the
353                                         // performance impact is too great.
354  read_options.snapshot = snapshot ? snapshot->snapshot_ : 0;
355  scoped_ptr<leveldb::Iterator> i(db_->NewIterator(read_options));
356  if (!i)  // TODO(jsbell): Double check if we actually need to check this.
357    return scoped_ptr<LevelDBIterator>();
358  return scoped_ptr<LevelDBIterator>(new IteratorImpl(i.Pass()));
359}
360
361const LevelDBComparator* LevelDBDatabase::Comparator() const {
362  return comparator_;
363}
364
365}  // namespace content
366