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