leveldb_database.cc revision eb525c5499e34cc9c4b825d6d9e75bb07cc06ace
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#include <string> 9 10#include "base/basictypes.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/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_slice.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/comparator.h" 26#include "third_party/leveldatabase/src/include/leveldb/db.h" 27#include "third_party/leveldatabase/src/include/leveldb/env.h" 28#include "third_party/leveldatabase/src/include/leveldb/slice.h" 29 30namespace content { 31 32static leveldb::Slice MakeSlice(const std::vector<char>& value) { 33 DCHECK_GT(value.size(), static_cast<size_t>(0)); 34 return leveldb::Slice(&*value.begin(), value.size()); 35} 36 37static leveldb::Slice MakeSlice(const LevelDBSlice& s) { 38 return leveldb::Slice(s.begin(), s.end() - s.begin()); 39} 40 41static LevelDBSlice MakeLevelDBSlice(const leveldb::Slice& s) { 42 return LevelDBSlice(s.data(), s.data() + s.size()); 43} 44 45class ComparatorAdapter : public leveldb::Comparator { 46 public: 47 explicit ComparatorAdapter(const LevelDBComparator* comparator) 48 : comparator_(comparator) {} 49 50 virtual int Compare(const leveldb::Slice& a, const leveldb::Slice& b) const 51 OVERRIDE { 52 return comparator_->Compare(MakeLevelDBSlice(a), MakeLevelDBSlice(b)); 53 } 54 55 virtual const char* Name() const OVERRIDE { return comparator_->Name(); } 56 57 // TODO(jsbell): Support the methods below in the future. 58 virtual void FindShortestSeparator(std::string* start, 59 const leveldb::Slice& limit) const 60 OVERRIDE {} 61 virtual void FindShortSuccessor(std::string* key) const OVERRIDE {} 62 63 private: 64 const LevelDBComparator* comparator_; 65}; 66 67LevelDBSnapshot::LevelDBSnapshot(LevelDBDatabase* db) 68 : db_(db->db_.get()), snapshot_(db_->GetSnapshot()) {} 69 70LevelDBSnapshot::~LevelDBSnapshot() { db_->ReleaseSnapshot(snapshot_); } 71 72LevelDBDatabase::LevelDBDatabase() {} 73 74LevelDBDatabase::~LevelDBDatabase() { 75 // db_'s destructor uses comparator_adapter_; order of deletion is important. 76 db_.reset(); 77 comparator_adapter_.reset(); 78 env_.reset(); 79} 80 81static leveldb::Status OpenDB(leveldb::Comparator* comparator, 82 leveldb::Env* env, 83 const base::FilePath& path, 84 leveldb::DB** db) { 85 leveldb::Options options; 86 options.comparator = comparator; 87 options.create_if_missing = true; 88 options.paranoid_checks = true; 89 90 // Marking compression as explicitly off so snappy support can be 91 // compiled in for other leveldb clients without implicitly enabling 92 // it for IndexedDB. http://crbug.com/81384 93 options.compression = leveldb::kNoCompression; 94 95 // For info about the troubles we've run into with this parameter, 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* const type, 114 const base::FilePath& file_name) { 115 std::string name = 116 std::string("WebCore.IndexedDB.LevelDB.Open") + type + "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(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 ParseAndHistogramIOErrorDetails(const std::string& histogram_name, 143 const leveldb::Status& s) { 144 leveldb_env::MethodID method; 145 int error = -1; 146 leveldb_env::ErrorParsingResult result = 147 leveldb_env::ParseMethodAndError(s.ToString().c_str(), &method, &error); 148 if (result == leveldb_env::NONE) 149 return; 150 std::string method_histogram_name(histogram_name); 151 method_histogram_name.append(".EnvMethod"); 152 base::LinearHistogram::FactoryGet( 153 method_histogram_name, 154 1, 155 leveldb_env::kNumEntries, 156 leveldb_env::kNumEntries + 1, 157 base::HistogramBase::kUmaTargetedHistogramFlag)->Add(method); 158 159 std::string error_histogram_name(histogram_name); 160 161 if (result == leveldb_env::METHOD_AND_PFE) { 162 DCHECK(error < 0); 163 error_histogram_name.append(std::string(".PFE.") + 164 leveldb_env::MethodIDToString(method)); 165 base::LinearHistogram::FactoryGet( 166 error_histogram_name, 167 1, 168 -base::PLATFORM_FILE_ERROR_MAX, 169 -base::PLATFORM_FILE_ERROR_MAX + 1, 170 base::HistogramBase::kUmaTargetedHistogramFlag)->Add(-error); 171 } else if (result == leveldb_env::METHOD_AND_ERRNO) { 172 error_histogram_name.append(std::string(".Errno.") + 173 leveldb_env::MethodIDToString(method)); 174 base::LinearHistogram::FactoryGet( 175 error_histogram_name, 176 1, 177 ERANGE + 1, 178 ERANGE + 2, 179 base::HistogramBase::kUmaTargetedHistogramFlag)->Add(error); 180 } 181} 182 183static void ParseAndHistogramCorruptionDetails( 184 const std::string& histogram_name, 185 const leveldb::Status& status) { 186 DCHECK(!status.IsIOError()); 187 DCHECK(!status.ok()); 188 const int kOtherError = 0; 189 int error = kOtherError; 190 const std::string& str_error = status.ToString(); 191 // Keep in sync with LevelDBCorruptionTypes in histograms.xml. 192 const char* patterns[] = { 193 "missing files", 194 "log record too small", 195 "corrupted internal key", 196 "partial record", 197 "missing start of fragmented record", 198 "error in middle of record", 199 "unknown record type", 200 "truncated record at end", 201 "bad record length", 202 "VersionEdit", 203 "FileReader invoked with unexpected value", 204 "corrupted key", 205 "CURRENT file does not end with newline", 206 "no meta-nextfile entry", 207 "no meta-lognumber entry", 208 "no last-sequence-number entry", 209 "malformed WriteBatch", 210 "bad WriteBatch Put", 211 "bad WriteBatch Delete", 212 "unknown WriteBatch tag", 213 "WriteBatch has wrong count", 214 "bad entry in block", 215 "bad block contents", 216 "bad block handle", 217 "truncated block read", 218 "block checksum mismatch", 219 "checksum mismatch", 220 "corrupted compressed block contents", 221 "bad block type", 222 "bad magic number", 223 "file is too short", 224 }; 225 const size_t kNumPatterns = arraysize(patterns); 226 for (size_t i = 0; i < kNumPatterns; ++i) { 227 if (str_error.find(patterns[i]) != std::string::npos) { 228 error = i + 1; 229 break; 230 } 231 } 232 DCHECK(error >= 0); 233 std::string corruption_histogram_name(histogram_name); 234 corruption_histogram_name.append(".Corruption"); 235 base::LinearHistogram::FactoryGet( 236 corruption_histogram_name, 237 1, 238 kNumPatterns + 1, 239 kNumPatterns + 2, 240 base::HistogramBase::kUmaTargetedHistogramFlag)->Add(error); 241} 242 243static void HistogramLevelDBError(const std::string& histogram_name, 244 const leveldb::Status& s) { 245 if (s.ok()) { 246 NOTREACHED(); 247 return; 248 } 249 enum { 250 LEVEL_DB_NOT_FOUND, 251 LEVEL_DB_CORRUPTION, 252 LEVEL_DB_IO_ERROR, 253 LEVEL_DB_OTHER, 254 LEVEL_DB_MAX_ERROR 255 }; 256 int leveldb_error = LEVEL_DB_OTHER; 257 if (s.IsNotFound()) 258 leveldb_error = LEVEL_DB_NOT_FOUND; 259 else if (s.IsCorruption()) 260 leveldb_error = LEVEL_DB_CORRUPTION; 261 else if (s.IsIOError()) 262 leveldb_error = LEVEL_DB_IO_ERROR; 263 base::Histogram::FactoryGet(histogram_name, 264 1, 265 LEVEL_DB_MAX_ERROR, 266 LEVEL_DB_MAX_ERROR + 1, 267 base::HistogramBase::kUmaTargetedHistogramFlag) 268 ->Add(leveldb_error); 269 if (s.IsIOError()) 270 ParseAndHistogramIOErrorDetails(histogram_name, s); 271 else 272 ParseAndHistogramCorruptionDetails(histogram_name, s); 273} 274 275scoped_ptr<LevelDBDatabase> LevelDBDatabase::Open( 276 const base::FilePath& file_name, 277 const LevelDBComparator* comparator, 278 bool* is_disk_full) { 279 scoped_ptr<ComparatorAdapter> comparator_adapter( 280 new ComparatorAdapter(comparator)); 281 282 leveldb::DB* db; 283 const leveldb::Status s = 284 OpenDB(comparator_adapter.get(), leveldb::IDBEnv(), file_name, &db); 285 286 if (!s.ok()) { 287 HistogramLevelDBError("WebCore.IndexedDB.LevelDBOpenErrors", s); 288 int free_space_k_bytes = CheckFreeSpace("Failure", file_name); 289 // Disks with <100k of free space almost never succeed in opening a 290 // leveldb database. 291 if (is_disk_full) 292 *is_disk_full = free_space_k_bytes >= 0 && free_space_k_bytes < 100; 293 294 LOG(ERROR) << "Failed to open LevelDB database from " 295 << file_name.AsUTF8Unsafe() << "," << s.ToString(); 296 return scoped_ptr<LevelDBDatabase>(); 297 } 298 299 CheckFreeSpace("Success", file_name); 300 301 scoped_ptr<LevelDBDatabase> result(new LevelDBDatabase); 302 result->db_ = make_scoped_ptr(db); 303 result->comparator_adapter_ = comparator_adapter.Pass(); 304 result->comparator_ = comparator; 305 306 return result.Pass(); 307} 308 309scoped_ptr<LevelDBDatabase> LevelDBDatabase::OpenInMemory( 310 const LevelDBComparator* comparator) { 311 scoped_ptr<ComparatorAdapter> comparator_adapter( 312 new ComparatorAdapter(comparator)); 313 scoped_ptr<leveldb::Env> in_memory_env(leveldb::NewMemEnv(leveldb::IDBEnv())); 314 315 leveldb::DB* db; 316 const leveldb::Status s = OpenDB( 317 comparator_adapter.get(), in_memory_env.get(), base::FilePath(), &db); 318 319 if (!s.ok()) { 320 LOG(ERROR) << "Failed to open in-memory LevelDB database: " << s.ToString(); 321 return scoped_ptr<LevelDBDatabase>(); 322 } 323 324 scoped_ptr<LevelDBDatabase> result(new LevelDBDatabase); 325 result->env_ = in_memory_env.Pass(); 326 result->db_ = make_scoped_ptr(db); 327 result->comparator_adapter_ = comparator_adapter.Pass(); 328 result->comparator_ = comparator; 329 330 return result.Pass(); 331} 332 333bool LevelDBDatabase::Put(const LevelDBSlice& key, 334 std::vector<char>* value) { 335 leveldb::WriteOptions write_options; 336 write_options.sync = true; 337 338 const leveldb::Status s = 339 db_->Put(write_options, MakeSlice(key), MakeSlice(*value)); 340 if (s.ok()) 341 return true; 342 LOG(ERROR) << "LevelDB put failed: " << s.ToString(); 343 return false; 344} 345 346bool LevelDBDatabase::Remove(const LevelDBSlice& key) { 347 leveldb::WriteOptions write_options; 348 write_options.sync = true; 349 350 const leveldb::Status s = db_->Delete(write_options, MakeSlice(key)); 351 if (s.ok()) 352 return true; 353 if (s.IsNotFound()) 354 return false; 355 LOG(ERROR) << "LevelDB remove failed: " << s.ToString(); 356 return false; 357} 358 359bool LevelDBDatabase::Get(const LevelDBSlice& key, 360 std::string* value, 361 bool* found, 362 const LevelDBSnapshot* snapshot) { 363 *found = false; 364 leveldb::ReadOptions read_options; 365 read_options.verify_checksums = true; // TODO(jsbell): Disable this if the 366 // performance impact is too great. 367 read_options.snapshot = snapshot ? snapshot->snapshot_ : 0; 368 369 const leveldb::Status s = db_->Get(read_options, MakeSlice(key), value); 370 if (s.ok()) { 371 *found = true; 372 return true; 373 } 374 if (s.IsNotFound()) 375 return true; 376 LOG(ERROR) << "LevelDB get failed: " << s.ToString(); 377 return false; 378} 379 380bool LevelDBDatabase::Write(const LevelDBWriteBatch& write_batch) { 381 leveldb::WriteOptions write_options; 382 write_options.sync = true; 383 384 const leveldb::Status s = 385 db_->Write(write_options, write_batch.write_batch_.get()); 386 if (s.ok()) 387 return true; 388 HistogramLevelDBError("WebCore.IndexedDB.LevelDBWriteErrors", s); 389 LOG(ERROR) << "LevelDB write failed: " << s.ToString(); 390 return false; 391} 392 393namespace { 394class IteratorImpl : public LevelDBIterator { 395 public: 396 virtual ~IteratorImpl() {} 397 398 virtual bool IsValid() const OVERRIDE; 399 virtual void SeekToLast() OVERRIDE; 400 virtual void Seek(const LevelDBSlice& target) OVERRIDE; 401 virtual void Next() OVERRIDE; 402 virtual void Prev() OVERRIDE; 403 virtual LevelDBSlice Key() const OVERRIDE; 404 virtual LevelDBSlice Value() const OVERRIDE; 405 406 private: 407 friend class content::LevelDBDatabase; 408 explicit IteratorImpl(scoped_ptr<leveldb::Iterator> iterator); 409 void CheckStatus(); 410 411 scoped_ptr<leveldb::Iterator> iterator_; 412}; 413} 414 415IteratorImpl::IteratorImpl(scoped_ptr<leveldb::Iterator> it) 416 : iterator_(it.Pass()) {} 417 418void IteratorImpl::CheckStatus() { 419 const leveldb::Status s = iterator_->status(); 420 if (!s.ok()) 421 LOG(ERROR) << "LevelDB iterator error: " << s.ToString(); 422} 423 424bool IteratorImpl::IsValid() const { return iterator_->Valid(); } 425 426void IteratorImpl::SeekToLast() { 427 iterator_->SeekToLast(); 428 CheckStatus(); 429} 430 431void IteratorImpl::Seek(const LevelDBSlice& target) { 432 iterator_->Seek(MakeSlice(target)); 433 CheckStatus(); 434} 435 436void IteratorImpl::Next() { 437 DCHECK(IsValid()); 438 iterator_->Next(); 439 CheckStatus(); 440} 441 442void IteratorImpl::Prev() { 443 DCHECK(IsValid()); 444 iterator_->Prev(); 445 CheckStatus(); 446} 447 448LevelDBSlice IteratorImpl::Key() const { 449 DCHECK(IsValid()); 450 return MakeLevelDBSlice(iterator_->key()); 451} 452 453LevelDBSlice IteratorImpl::Value() const { 454 DCHECK(IsValid()); 455 return MakeLevelDBSlice(iterator_->value()); 456} 457 458scoped_ptr<LevelDBIterator> LevelDBDatabase::CreateIterator( 459 const LevelDBSnapshot* snapshot) { 460 leveldb::ReadOptions read_options; 461 read_options.verify_checksums = true; // TODO(jsbell): Disable this if the 462 // performance impact is too great. 463 read_options.snapshot = snapshot ? snapshot->snapshot_ : 0; 464 465 scoped_ptr<leveldb::Iterator> i(db_->NewIterator(read_options)); 466 return scoped_ptr<LevelDBIterator>(new IteratorImpl(i.Pass())); 467} 468 469const LevelDBComparator* LevelDBDatabase::Comparator() const { 470 return comparator_; 471} 472 473} // namespace content 474