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