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