| Index: content/browser/indexed_db/indexed_db_backing_store.cc
|
| diff --git a/content/browser/indexed_db/indexed_db_backing_store.cc b/content/browser/indexed_db/indexed_db_backing_store.cc
|
| index dd539debdb9eae8af15ba5d7ea7b37fb01401bac..3c5aa2d9537b13b0a734d8ca98ec963d2f55ac60 100644
|
| --- a/content/browser/indexed_db/indexed_db_backing_store.cc
|
| +++ b/content/browser/indexed_db/indexed_db_backing_store.cc
|
| @@ -126,6 +126,8 @@ enum IndexedDBBackingStoreErrorSource {
|
| GET_DATABASE_NAMES,
|
| DELETE_INDEX,
|
| CLEAR_OBJECT_STORE,
|
| + READ_BLOB_JOURNAL,
|
| + DECODE_BLOB_JOURNAL,
|
| INTERNAL_ERROR_MAX,
|
| };
|
|
|
| @@ -192,6 +194,10 @@ static leveldb::Status InvalidDBKeyStatus() {
|
| return leveldb::Status::InvalidArgument("Invalid database key ID");
|
| }
|
|
|
| +static leveldb::Status IOErrorStatus() {
|
| + return leveldb::Status::IOError("IO Error");
|
| +}
|
| +
|
| template <typename DBOrTransaction>
|
| static leveldb::Status GetInt(DBOrTransaction* db,
|
| const StringPiece& key,
|
| @@ -474,6 +480,99 @@ class DefaultLevelDBFactory : public LevelDBFactory {
|
| }
|
| };
|
|
|
| +// TODO(ericu): Error recovery. If we persistently can't read the
|
| +// blob journal, the safe thing to do is to clear it and leak the blobs,
|
| +// though that may be costly. Still, database/directory deletion should always
|
| +// clean things up, and we can write an fsck that will do a full correction if
|
| +// need be.
|
| +template <typename T>
|
| +static leveldb::Status GetBlobJournal(const StringPiece& leveldb_key,
|
| + T* leveldb_transaction,
|
| + BlobJournalType* journal) {
|
| + std::string data;
|
| + bool found = false;
|
| + leveldb::Status s = leveldb_transaction->Get(leveldb_key, &data, &found);
|
| + if (!s.ok()) {
|
| + INTERNAL_READ_ERROR_UNTESTED(READ_BLOB_JOURNAL);
|
| + return s;
|
| + }
|
| + journal->clear();
|
| + if (!found || !data.size())
|
| + return leveldb::Status::OK();
|
| + StringPiece slice(data);
|
| + if (!DecodeBlobJournal(&slice, journal)) {
|
| + INTERNAL_READ_ERROR_UNTESTED(DECODE_BLOB_JOURNAL);
|
| + s = InternalInconsistencyStatus();
|
| + }
|
| + return s;
|
| +}
|
| +
|
| +static void ClearBlobJournal(LevelDBTransaction* leveldb_transaction,
|
| + const std::string& level_db_key) {
|
| + leveldb_transaction->Remove(level_db_key);
|
| +}
|
| +
|
| +static void UpdatePrimaryJournalWithBlobList(
|
| + LevelDBTransaction* leveldb_transaction,
|
| + const BlobJournalType& journal) {
|
| + const std::string leveldb_key = BlobJournalKey::Encode();
|
| + std::string data;
|
| + EncodeBlobJournal(journal, &data);
|
| + leveldb_transaction->Put(leveldb_key, &data);
|
| +}
|
| +
|
| +static void UpdateLiveBlobJournalWithBlobList(
|
| + LevelDBTransaction* leveldb_transaction,
|
| + const BlobJournalType& journal) {
|
| + const std::string leveldb_key = LiveBlobJournalKey::Encode();
|
| + std::string data;
|
| + EncodeBlobJournal(journal, &data);
|
| + leveldb_transaction->Put(leveldb_key, &data);
|
| +}
|
| +
|
| +static leveldb::Status MergeBlobsIntoLiveBlobJournal(
|
| + LevelDBTransaction* leveldb_transaction,
|
| + const BlobJournalType& journal) {
|
| + BlobJournalType old_journal;
|
| + const std::string key = LiveBlobJournalKey::Encode();
|
| + leveldb::Status s = GetBlobJournal(key, leveldb_transaction, &old_journal);
|
| + if (!s.ok())
|
| + return s;
|
| +
|
| + old_journal.insert(old_journal.end(), journal.begin(), journal.end());
|
| +
|
| + UpdateLiveBlobJournalWithBlobList(leveldb_transaction, old_journal);
|
| + return leveldb::Status::OK();
|
| +}
|
| +
|
| +static void UpdateBlobJournalWithDatabase(
|
| + LevelDBDirectTransaction* leveldb_transaction,
|
| + int64 database_id) {
|
| + BlobJournalType journal;
|
| + journal.push_back(
|
| + std::make_pair(database_id, DatabaseMetaDataKey::kAllBlobsKey));
|
| + const std::string key = BlobJournalKey::Encode();
|
| + std::string data;
|
| + EncodeBlobJournal(journal, &data);
|
| + leveldb_transaction->Put(key, &data);
|
| +}
|
| +
|
| +static leveldb::Status MergeDatabaseIntoLiveBlobJournal(
|
| + LevelDBDirectTransaction* leveldb_transaction,
|
| + int64 database_id) {
|
| + BlobJournalType journal;
|
| + const std::string key = LiveBlobJournalKey::Encode();
|
| + leveldb::Status s = GetBlobJournal(key, leveldb_transaction, &journal);
|
| + if (!s.ok())
|
| + return s;
|
| + journal.push_back(
|
| + std::make_pair(database_id, DatabaseMetaDataKey::kAllBlobsKey));
|
| + std::string data;
|
| + EncodeBlobJournal(journal, &data);
|
| + leveldb_transaction->Put(key, &data);
|
| + return leveldb::Status::OK();
|
| +}
|
| +
|
| IndexedDBBackingStore::IndexedDBBackingStore(
|
| IndexedDBFactory* indexed_db_factory,
|
| const GURL& origin_url,
|
| @@ -1098,6 +1197,18 @@ leveldb::Status IndexedDBBackingStore::DeleteDatabase(
|
| const std::string key = DatabaseNameKey::Encode(origin_identifier_, name);
|
| transaction->Remove(key);
|
|
|
| + // TODO(ericu): Put the real calls to the blob journal code here. For now,
|
| + // I've inserted fake calls so that we don't get "you didn't use this static
|
| + // function" compiler errors.
|
| + if (false) {
|
| + scoped_refptr<LevelDBTransaction> fake_transaction =
|
| + new LevelDBTransaction(NULL);
|
| + BlobJournalType fake_journal;
|
| + MergeDatabaseIntoLiveBlobJournal(transaction.get(), metadata.id);
|
| + UpdateBlobJournalWithDatabase(transaction.get(), metadata.id);
|
| + MergeBlobsIntoLiveBlobJournal(fake_transaction.get(), fake_journal);
|
| + }
|
| +
|
| s = transaction->Commit();
|
| if (!s.ok()) {
|
| INTERNAL_WRITE_ERROR_UNTESTED(DELETE_DATABASE);
|
| @@ -1934,6 +2045,85 @@ bool IndexedDBBackingStore::WriteBlobFile(
|
| return true;
|
| }
|
|
|
| +void IndexedDBBackingStore::ReportBlobUnused(int64 database_id,
|
| + int64 blob_key) {
|
| + DCHECK(KeyPrefix::IsValidDatabaseId(database_id));
|
| + bool all_blobs = blob_key == DatabaseMetaDataKey::kAllBlobsKey;
|
| + DCHECK(all_blobs || DatabaseMetaDataKey::IsValidBlobKey(blob_key));
|
| + scoped_refptr<LevelDBTransaction> transaction =
|
| + new LevelDBTransaction(db_.get());
|
| +
|
| + std::string live_blob_key = LiveBlobJournalKey::Encode();
|
| + BlobJournalType live_blob_journal;
|
| + if (!GetBlobJournal(live_blob_key, transaction.get(), &live_blob_journal)
|
| + .ok())
|
| + return;
|
| + DCHECK(live_blob_journal.size());
|
| +
|
| + std::string primary_key = BlobJournalKey::Encode();
|
| + BlobJournalType primary_journal;
|
| + if (!GetBlobJournal(primary_key, transaction.get(), &primary_journal).ok())
|
| + return;
|
| +
|
| + // There are several cases to handle. If blob_key is kAllBlobsKey, we want to
|
| + // remove all entries with database_id from the live_blob journal and add only
|
| + // kAllBlobsKey to the primary journal. Otherwise if IsValidBlobKey(blob_key)
|
| + // and we hit kAllBlobsKey for the right database_id in the journal, we leave
|
| + // the kAllBlobsKey entry in the live_blob journal but add the specific blob
|
| + // to the primary. Otherwise if IsValidBlobKey(blob_key) and we find a
|
| + // matching (database_id, blob_key) tuple, we should move it to the primary
|
| + // journal.
|
| + BlobJournalType new_live_blob_journal;
|
| + for (BlobJournalType::iterator journal_iter = live_blob_journal.begin();
|
| + journal_iter != live_blob_journal.end();
|
| + ++journal_iter) {
|
| + int64 current_database_id = journal_iter->first;
|
| + int64 current_blob_key = journal_iter->second;
|
| + bool current_all_blobs =
|
| + current_blob_key == DatabaseMetaDataKey::kAllBlobsKey;
|
| + DCHECK(KeyPrefix::IsValidDatabaseId(current_database_id) ||
|
| + current_all_blobs);
|
| + if (current_database_id == database_id &&
|
| + (all_blobs || current_all_blobs || blob_key == current_blob_key)) {
|
| + if (!all_blobs) {
|
| + primary_journal.push_back(
|
| + std::make_pair(database_id, current_blob_key));
|
| + if (current_all_blobs)
|
| + new_live_blob_journal.push_back(*journal_iter);
|
| + new_live_blob_journal.insert(new_live_blob_journal.end(),
|
| + ++journal_iter,
|
| + live_blob_journal.end()); // All the rest.
|
| + break;
|
| + }
|
| + } else {
|
| + new_live_blob_journal.push_back(*journal_iter);
|
| + }
|
| + }
|
| + if (all_blobs) {
|
| + primary_journal.push_back(
|
| + std::make_pair(database_id, DatabaseMetaDataKey::kAllBlobsKey));
|
| + }
|
| + UpdatePrimaryJournalWithBlobList(transaction.get(), primary_journal);
|
| + UpdateLiveBlobJournalWithBlobList(transaction.get(), new_live_blob_journal);
|
| + transaction->Commit();
|
| + // We could just do the deletions/cleaning here, but if there are a lot of
|
| + // blobs about to be garbage collected, it'd be better to wait and do them all
|
| + // at once.
|
| + StartJournalCleaningTimer();
|
| +}
|
| +
|
| +// The this reference is a raw pointer that's declared Unretained inside the
|
| +// timer code, so this won't confuse IndexedDBFactory's check for
|
| +// HasLastBackingStoreReference. It's safe because if the backing store is
|
| +// deleted, the timer will automatically be canceled on destruction.
|
| +void IndexedDBBackingStore::StartJournalCleaningTimer() {
|
| + journal_cleaning_timer_.Start(
|
| + FROM_HERE,
|
| + base::TimeDelta::FromSeconds(5),
|
| + this,
|
| + &IndexedDBBackingStore::CleanPrimaryJournalIgnoreReturn);
|
| +}
|
| +
|
| // This assumes a file path of dbId/second-to-LSB-of-counter/counter.
|
| FilePath IndexedDBBackingStore::GetBlobFileName(int64 database_id, int64 key) {
|
| return GetBlobFileNameForKey(blob_path_, database_id, key);
|
| @@ -2065,6 +2255,40 @@ bool IndexedDBBackingStore::RemoveBlobDirectory(int64 database_id) {
|
| return base::DeleteFile(dirName, true);
|
| }
|
|
|
| +leveldb::Status IndexedDBBackingStore::CleanUpBlobJournal(
|
| + const std::string& level_db_key) {
|
| + scoped_refptr<LevelDBTransaction> journal_transaction =
|
| + new LevelDBTransaction(db_.get());
|
| + BlobJournalType journal;
|
| + leveldb::Status s =
|
| + GetBlobJournal(level_db_key, journal_transaction.get(), &journal);
|
| + if (!s.ok())
|
| + return s;
|
| + if (!journal.size())
|
| + return leveldb::Status::OK();
|
| + BlobJournalType::iterator journal_iter;
|
| + for (journal_iter = journal.begin(); journal_iter != journal.end();
|
| + ++journal_iter) {
|
| + int64 database_id = journal_iter->first;
|
| + int64 blob_key = journal_iter->second;
|
| + DCHECK(KeyPrefix::IsValidDatabaseId(database_id));
|
| + if (blob_key == DatabaseMetaDataKey::kAllBlobsKey) {
|
| + if (!RemoveBlobDirectory(database_id))
|
| + return IOErrorStatus();
|
| + } else {
|
| + DCHECK(DatabaseMetaDataKey::IsValidBlobKey(blob_key));
|
| + if (!RemoveBlobFile(database_id, blob_key))
|
| + return IOErrorStatus();
|
| + }
|
| + }
|
| + ClearBlobJournal(journal_transaction.get(), level_db_key);
|
| + return journal_transaction->Commit();
|
| +}
|
| +
|
| +void IndexedDBBackingStore::CleanPrimaryJournalIgnoreReturn() {
|
| + CleanUpBlobJournal(BlobJournalKey::Encode());
|
| +}
|
| +
|
| WARN_UNUSED_RESULT static leveldb::Status SetMaxIndexId(
|
| LevelDBTransaction* transaction,
|
| int64 database_id,
|
| @@ -2382,11 +2606,6 @@ leveldb::Status IndexedDBBackingStore::KeyExistsInIndex(
|
| return InvalidDBKeyStatus();
|
| }
|
|
|
| -void IndexedDBBackingStore::ReportBlobUnused(int64 database_id,
|
| - int64 blob_key) {
|
| - // TODO(ericu)
|
| -}
|
| -
|
| IndexedDBBackingStore::Cursor::Cursor(
|
| const IndexedDBBackingStore::Cursor* other)
|
| : transaction_(other->transaction_),
|
|
|