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