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